Skip to main content

rust_rocksdb/transactions/
options.rs

1// Copyright 2021 Yiyuan Liu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use std::ptr::NonNull;
17use std::sync::Arc;
18
19use libc::c_int;
20
21use crate::ffi;
22
23pub struct TransactionOptions {
24    pub(crate) inner: *mut ffi::rocksdb_transaction_options_t,
25}
26
27unsafe impl Send for TransactionOptions {}
28unsafe impl Sync for TransactionOptions {}
29
30impl Default for TransactionOptions {
31    fn default() -> Self {
32        let txn_opts = unsafe { ffi::rocksdb_transaction_options_create() };
33        assert!(
34            !txn_opts.is_null(),
35            "Could not create RocksDB transaction options"
36        );
37        Self { inner: txn_opts }
38    }
39}
40
41impl TransactionOptions {
42    pub fn new() -> TransactionOptions {
43        TransactionOptions::default()
44    }
45
46    pub fn set_skip_prepare(&mut self, skip_prepare: bool) {
47        unsafe {
48            ffi::rocksdb_transaction_options_set_skip_prepare(self.inner, u8::from(skip_prepare));
49        }
50    }
51
52    /// Specifies use snapshot or not.
53    ///
54    /// Default: false.
55    ///
56    /// If a transaction has a snapshot set, the transaction will ensure that
57    /// any keys successfully written(or fetched via `get_for_update`) have not
58    /// been modified outside this transaction since the time the snapshot was
59    /// set.
60    /// If a snapshot has not been set, the transaction guarantees that keys have
61    /// not been modified since the time each key was first written (or fetched via
62    /// `get_for_update`).
63    ///
64    /// Using snapshot will provide stricter isolation guarantees at the
65    /// expense of potentially more transaction failures due to conflicts with
66    /// other writes.
67    ///
68    /// Calling `set_snapshot` will not affect the version of Data returned by `get`
69    /// methods.
70    pub fn set_snapshot(&mut self, snapshot: bool) {
71        unsafe {
72            ffi::rocksdb_transaction_options_set_set_snapshot(self.inner, u8::from(snapshot));
73        }
74    }
75
76    /// Specifies whether detect deadlock or not.
77    ///
78    /// Setting to true means that before acquiring locks, this transaction will
79    /// check if doing so will cause a deadlock. If so, it will return with
80    /// Status::Busy.  The user should retry their transaction.
81    ///
82    /// Default: false.
83    pub fn set_deadlock_detect(&mut self, deadlock_detect: bool) {
84        unsafe {
85            ffi::rocksdb_transaction_options_set_deadlock_detect(
86                self.inner,
87                u8::from(deadlock_detect),
88            );
89        }
90    }
91
92    /// Specifies the wait timeout in milliseconds when a transaction attempts to lock a key.
93    ///
94    /// If 0, no waiting is done if a lock cannot instantly be acquired.
95    /// If negative, transaction lock timeout in `TransactionDBOptions` will be used.
96    ///
97    /// Default: -1.
98    pub fn set_lock_timeout(&mut self, lock_timeout: i64) {
99        unsafe {
100            ffi::rocksdb_transaction_options_set_lock_timeout(self.inner, lock_timeout);
101        }
102    }
103
104    /// Specifies expiration duration in milliseconds.
105    ///
106    /// If non-negative, transactions that last longer than this many milliseconds will fail to commit.
107    /// If not set, a forgotten transaction that is never committed, rolled back, or deleted
108    /// will never relinquish any locks it holds.  This could prevent keys from being accessed by other writers.
109    ///
110    /// Default: -1.
111    pub fn set_expiration(&mut self, expiration: i64) {
112        unsafe {
113            ffi::rocksdb_transaction_options_set_expiration(self.inner, expiration);
114        }
115    }
116
117    /// Specifies the number of traversals to make during deadlock detection.
118    ///
119    /// Default: 50.
120    pub fn set_deadlock_detect_depth(&mut self, depth: i64) {
121        unsafe {
122            ffi::rocksdb_transaction_options_set_deadlock_detect_depth(self.inner, depth);
123        }
124    }
125
126    /// Specifies the maximum number of bytes used for the write batch. 0 means no limit.
127    ///
128    /// Default: 0.
129    pub fn set_max_write_batch_size(&mut self, size: usize) {
130        unsafe {
131            ffi::rocksdb_transaction_options_set_max_write_batch_size(self.inner, size);
132        }
133    }
134
135    /// The following three options enable optimizations for large transaction commit to
136    /// bypass memtable write.
137    /// - If any transaction's commit should bybass memtable write, set
138    ///   commit_bypass_memtable to true.
139    /// - If only bypass memtable write for transactions with >= n operations, set
140    ///   commit_bypass_memtable to false, large_txn_commit_optimize_threshold to n, and
141    ///   large_txn_commit_optimize_byte_threshold to 0. Similarly for only optimize when a
142    ///   transaction's write batch size is >= n.
143    /// - If bypass memtable write for transactions with >= n operations or >= x bytes, set
144    ///   commit_bypass_memtable to false, large_txn_commit_optimize_threshold to n, and
145    ///   large_txn_commit_optimize_byte_threshold to x.
146    ///
147    /// EXPERIMENTAL, SUBJECT TO CHANGE Only supports write-committed policy. If set to true,
148    /// the transaction will skip memtable write and ingest into the DB directly during
149    /// Commit(). This makes Commit() much faster for transactions with many operations.
150    /// Transaction neeeds to call Prepare() before Commit() for this option to take effect.
151    /// Transactions with Merge() or PutEntity() is not supported yet.
152    ///
153    /// Note that the transaction will be ingested as an immutable memtable for CFs it
154    /// updates, and the current memtable will be switched to a new one. So ingesting many
155    /// transactions in a short period of time may cause stall due to too many memtables. Note
156    /// that the ingestion relies on the transaction's underlying index,
157    /// (WriteBatchWithIndex), so updates that are added to the transaction without indexing
158    /// (i.e. added directly to the transaction underlying write batch through
159    /// Transaction::GetWriteBatch()->GetWriteBatch()) are not supported, and the optimization
160    /// will not apply in that case.
161    ///
162    /// NOTE: since WBWI keep track of the most recent update per key, a Put followed by a
163    /// SingleDelete will be written to DB as a SingleDelete. This can cause flush/compaction
164    /// to report `num_single_del_mismatch` due to consecutive SingleDeletes.
165    pub fn set_commit_bypass_memtable(&mut self, val: bool) {
166        unsafe {
167            ffi::rocksdb_transaction_options_set_commit_bypass_memtable(self.inner, u8::from(val));
168        }
169    }
170
171    /// Returns the value of the `commit_bypass_memtable` option.
172    pub fn get_commit_bypass_memtable(&self) -> bool {
173        unsafe { ffi::rocksdb_transaction_options_get_commit_bypass_memtable(self.inner) != 0 }
174    }
175
176    /// Setting to true means that before acquiring locks, this transaction will check if
177    /// doing so will cause a deadlock. If so, it will return with Status::Busy.  The user
178    /// should retry their transaction.
179    pub fn get_deadlock_detect(&self) -> bool {
180        unsafe { ffi::rocksdb_transaction_options_get_deadlock_detect(self.inner) != 0 }
181    }
182
183    /// EXPERIMENTAL, SUBJECT TO CHANGE When the size of a transaction's write batch is at
184    /// least this threshold, we will enable optimizations for commiting a large transaction.
185    /// See comment for `commit_bypass_memtable` for more optimization detail.
186    ///
187    /// Default: 0 (disabled).
188    pub fn set_large_txn_commit_optimize_byte_threshold(&mut self, val: u64) {
189        unsafe {
190            ffi::rocksdb_transaction_options_set_large_txn_commit_optimize_byte_threshold(
191                self.inner, val,
192            );
193        }
194    }
195
196    /// Returns the value of the `large_txn_commit_optimize_byte_threshold` option.
197    pub fn get_large_txn_commit_optimize_byte_threshold(&self) -> u64 {
198        unsafe {
199            ffi::rocksdb_transaction_options_get_large_txn_commit_optimize_byte_threshold(
200                self.inner,
201            )
202        }
203    }
204
205    /// EXPERIMENTAL, SUBJECT TO CHANGE When the number of updates in a transaction is at
206    /// least this threshold, we will enable optimizations for commiting a large transaction.
207    /// See comment for `commit_bypass_memtable` for more optimization detail.
208    ///
209    /// Default: 0 (disabled).
210    pub fn set_large_txn_commit_optimize_threshold(&mut self, val: u32) {
211        unsafe {
212            ffi::rocksdb_transaction_options_set_large_txn_commit_optimize_threshold(
213                self.inner, val,
214            );
215        }
216    }
217
218    /// Returns the value of the `large_txn_commit_optimize_threshold` option.
219    pub fn get_large_txn_commit_optimize_threshold(&self) -> u32 {
220        unsafe {
221            ffi::rocksdb_transaction_options_get_large_txn_commit_optimize_threshold(self.inner)
222        }
223    }
224
225    /// The maximum number of bytes used for the write batch. 0 means no limit.
226    pub fn get_max_write_batch_size(&self) -> usize {
227        unsafe { ffi::rocksdb_transaction_options_get_max_write_batch_size(self.inner) }
228    }
229
230    /// Setting set_snapshot=true is the same as calling Transaction::SetSnapshot().
231    pub fn get_set_snapshot(&self) -> bool {
232        unsafe { ffi::rocksdb_transaction_options_get_set_snapshot(self.inner) != 0 }
233    }
234
235    /// If true, the TransactionDB implementation might skip concurrency control unless it is
236    /// overridden by TransactionOptions or TransactionDBWriteOptimizations. This can be used
237    /// in conjunction with DBOptions::unordered_write when the TransactionDB is used solely
238    /// for write ordering rather than concurrency control.
239    pub fn set_skip_concurrency_control(&mut self, val: bool) {
240        unsafe {
241            ffi::rocksdb_transaction_options_set_skip_concurrency_control(
242                self.inner,
243                u8::from(val),
244            );
245        }
246    }
247
248    /// Returns the value of the `skip_concurrency_control` option.
249    pub fn get_skip_concurrency_control(&self) -> bool {
250        unsafe { ffi::rocksdb_transaction_options_get_skip_concurrency_control(self.inner) != 0 }
251    }
252
253    /// In pessimistic transaction, if this is true, then you can skip Prepare before Commit,
254    /// otherwise, you must Prepare before Commit.
255    pub fn get_skip_prepare(&self) -> bool {
256        unsafe { ffi::rocksdb_transaction_options_get_skip_prepare(self.inner) != 0 }
257    }
258
259    /// If set, it states that the CommitTimeWriteBatch represents the latest state of the
260    /// application, has only one sub-batch, i.e., no duplicate keys,  and meant to be used
261    /// later during recovery. It enables an optimization to postpone updating the memtable
262    /// with CommitTimeWriteBatch to only SwitchMemtable or recovery. This option does not
263    /// affect write-committed. Only write-prepared/write-unprepared transactions will be
264    /// affected.
265    pub fn set_use_only_the_last_commit_time_batch_for_recovery(&mut self, val: bool) {
266        unsafe {
267            ffi::rocksdb_transaction_options_set_use_only_the_last_commit_time_batch_for_recovery(
268                self.inner,
269                u8::from(val),
270            );
271        }
272    }
273
274    /// Returns the value of the `use_only_the_last_commit_time_batch_for_recovery` option.
275    pub fn get_use_only_the_last_commit_time_batch_for_recovery(&self) -> bool {
276        unsafe {
277            ffi::rocksdb_transaction_options_get_use_only_the_last_commit_time_batch_for_recovery(
278                self.inner,
279            ) != 0
280        }
281    }
282
283    /// DO NOT USE. This is only a temporary option dedicated for MyRocks that will soon be
284    /// removed. In normal use cases, meta info like column family's timestamp size is tracked
285    /// at the transaction layer, so it's not necessary and even detrimental to track such
286    /// info inside the internal WriteBatch because it may let anti-patterns like bypassing
287    /// Transaction write APIs and directly write to its internal `WriteBatch` retrieved like
288    /// this:
289    /// <https://github.com/facebook/mysql-5.6/blob/fb-mysql-8.0.32/storage/rocksdb/ha_rocksdb.cc#L4949-L4950>
290    /// Setting this option to true will keep aforementioned use case continue to work before
291    /// it's refactored out. When this flag is enabled, we also intentionally only track the
292    /// timestamp size in APIs that MyRocks currently are using, including Put, Merge, Delete
293    /// DeleteRange, SingleDelete.
294    pub fn set_write_batch_track_timestamp_size(&mut self, val: bool) {
295        unsafe {
296            ffi::rocksdb_transaction_options_set_write_batch_track_timestamp_size(
297                self.inner,
298                u8::from(val),
299            );
300        }
301    }
302
303    /// Returns the value of the `write_batch_track_timestamp_size` option.
304    pub fn get_write_batch_track_timestamp_size(&self) -> bool {
305        unsafe {
306            ffi::rocksdb_transaction_options_get_write_batch_track_timestamp_size(self.inner) != 0
307        }
308    }
309
310    /// Returns the current `deadlock_detect_depth` setting.
311    ///
312    /// See [`Self::set_deadlock_detect_depth`] for what this controls.
313    pub fn get_deadlock_detect_depth(&self) -> i64 {
314        unsafe { ffi::rocksdb_transaction_options_get_deadlock_detect_depth(self.inner) }
315    }
316
317    /// Returns the current `deadlock_timeout_us` setting.
318    ///
319    /// See [`Self::set_deadlock_timeout_us`] for what this controls.
320    pub fn get_deadlock_timeout_us(&self) -> i64 {
321        unsafe { ffi::rocksdb_transaction_options_get_deadlock_timeout_us(self.inner) }
322    }
323
324    /// Returns the current `expiration` setting.
325    ///
326    /// See [`Self::set_expiration`] for what this controls.
327    pub fn get_expiration(&self) -> i64 {
328        unsafe { ffi::rocksdb_transaction_options_get_expiration(self.inner) }
329    }
330
331    /// Returns the current `lock_timeout` setting.
332    ///
333    /// See [`Self::set_lock_timeout`] for what this controls.
334    pub fn get_lock_timeout(&self) -> i64 {
335        unsafe { ffi::rocksdb_transaction_options_get_lock_timeout(self.inner) }
336    }
337
338    /// Returns the current `write_batch_flush_threshold` setting.
339    ///
340    /// See [`Self::set_write_batch_flush_threshold`] for what this controls.
341    pub fn get_write_batch_flush_threshold(&self) -> i64 {
342        unsafe { ffi::rocksdb_transaction_options_get_write_batch_flush_threshold(self.inner) }
343    }
344
345    /// Timeout in microseconds before perform dead lock detection. If 0, deadlock detection
346    /// will be performed immediately.
347    ///
348    /// To optimize performance, this parameter could be tuned.
349    ///
350    /// When deadlock happens very frequently, deadlock timeout should be set to 0, so
351    /// deadlock will be detected immediately.
352    ///
353    /// When deadlock happen very rarely, this timeout could be turned to be slightly longer
354    /// than the typical transaction execution time, so that transaction will be waked up to
355    /// take the lock before this timeout, which will allow the transaction to save the CPU
356    /// time on deadlock detection.
357    ///
358    /// Deadlock timeout is always smaller than lock_timeout.
359    pub fn set_deadlock_timeout_us(&mut self, val: i64) {
360        unsafe {
361            ffi::rocksdb_transaction_options_set_deadlock_timeout_us(self.inner, val);
362        }
363    }
364
365    /// See TransactionDBOptions::default_write_batch_flush_threshold for description. If a
366    /// negative value is specified, then the default value from TransactionDBOptions is used.
367    pub fn set_write_batch_flush_threshold(&mut self, val: i64) {
368        unsafe {
369            ffi::rocksdb_transaction_options_set_write_batch_flush_threshold(self.inner, val);
370        }
371    }
372}
373
374impl Drop for TransactionOptions {
375    fn drop(&mut self) {
376        unsafe {
377            ffi::rocksdb_transaction_options_destroy(self.inner);
378        }
379    }
380}
381
382/// When a [`TransactionDB`] writes transaction data into the DB.
383///
384/// [`TransactionDB`] reads this once while opening, to choose which implementation to
385/// build, and keeps its own copy. Changing it on a [`TransactionDBOptions`] afterwards
386/// has no effect on a DB that is already open.
387///
388/// [`TransactionDB`]: crate::TransactionDB
389#[derive(Debug, Copy, Clone, PartialEq, Eq)]
390#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
391pub enum TxnDBWritePolicy {
392    /// Write data at commit time.
393    ///
394    /// Only committed data ever reaches the DB, so readers need no extra machinery to
395    /// tell committed from uncommitted. This is the default.
396    WriteCommitted = ffi::rocksdb_txndb_write_policy_write_committed as isize,
397
398    /// Write data after the prepare phase of two-phase commit.
399    ///
400    /// Data lands before the commit, so the DB has to track which of it is committed and
401    /// readers have to consult that. Experimental: RocksDB describes the non-committed
402    /// policies as less mature, less validated and less compatible with other features
403    /// than `WriteCommitted`.
404    WritePrepared = ffi::rocksdb_txndb_write_policy_write_prepared as isize,
405
406    /// Write data before the prepare phase of two-phase commit.
407    ///
408    /// A running transaction can flush its pending writes into the DB once its batch
409    /// crosses
410    /// [`TransactionDBOptions::set_default_write_batch_flush_threshold`], which keeps
411    /// memory bounded for very large transactions. Experimental, and the least mature of
412    /// the three.
413    WriteUnprepared = ffi::rocksdb_txndb_write_policy_write_unprepared as isize,
414}
415
416impl TxnDBWritePolicy {
417    /// Decodes a raw `rocksdb::TxnDBWritePolicy`.
418    ///
419    /// This covers every policy RocksDB defines today, so `None` only means a future
420    /// release added one.
421    pub(crate) fn try_from_raw(raw: c_int) -> Option<Self> {
422        match raw {
423            n if n == TxnDBWritePolicy::WriteCommitted as c_int => {
424                Some(TxnDBWritePolicy::WriteCommitted)
425            }
426            n if n == TxnDBWritePolicy::WritePrepared as c_int => {
427                Some(TxnDBWritePolicy::WritePrepared)
428            }
429            n if n == TxnDBWritePolicy::WriteUnprepared as c_int => {
430                Some(TxnDBWritePolicy::WriteUnprepared)
431            }
432            _ => None,
433        }
434    }
435}
436
437pub struct TransactionDBOptions {
438    pub(crate) inner: *mut ffi::rocksdb_transactiondb_options_t,
439}
440
441unsafe impl Send for TransactionDBOptions {}
442unsafe impl Sync for TransactionDBOptions {}
443
444impl Default for TransactionDBOptions {
445    fn default() -> Self {
446        let txn_db_opts = unsafe { ffi::rocksdb_transactiondb_options_create() };
447        assert!(
448            !txn_db_opts.is_null(),
449            "Could not create RocksDB transaction_db options"
450        );
451        Self { inner: txn_db_opts }
452    }
453}
454
455impl TransactionDBOptions {
456    pub fn new() -> TransactionDBOptions {
457        TransactionDBOptions::default()
458    }
459
460    /// Specifies the wait timeout in milliseconds when writing a key
461    /// outside a transaction (i.e. by calling `TransactionDB::put` directly).
462    ///
463    /// If 0, no waiting is done if a lock cannot instantly be acquired.
464    /// If negative, there is no timeout and will block indefinitely when acquiring
465    /// a lock.
466    ///
467    /// Not using a timeout can lead to deadlocks.  Currently, there
468    /// is no deadlock-detection to recover from a deadlock.  While DB writes
469    /// cannot deadlock with other DB writes, they can deadlock with a transaction.
470    /// A negative timeout should only be used if all transactions have a small
471    /// expiration set.
472    ///
473    /// Default: 1000(1s).
474    pub fn set_default_lock_timeout(&mut self, default_lock_timeout: i64) {
475        unsafe {
476            ffi::rocksdb_transactiondb_options_set_default_lock_timeout(
477                self.inner,
478                default_lock_timeout,
479            );
480        }
481    }
482
483    /// Specifies the default wait timeout in milliseconds when a transaction
484    /// attempts to lock a key if not specified in `TransactionOptions`.
485    ///
486    /// If 0, no waiting is done if a lock cannot instantly be acquired.
487    /// If negative, there is no timeout.  Not using a timeout is not recommended
488    /// as it can lead to deadlocks.  Currently, there is no deadlock-detection to
489    /// recover from a deadlock.
490    ///
491    /// Default: 1000(1s).
492    pub fn set_txn_lock_timeout(&mut self, txn_lock_timeout: i64) {
493        unsafe {
494            ffi::rocksdb_transactiondb_options_set_transaction_lock_timeout(
495                self.inner,
496                txn_lock_timeout,
497            );
498        }
499    }
500
501    /// Specifies the maximum number of keys that can be locked at the same time
502    /// per column family.
503    ///
504    /// If the number of locked keys is greater than `max_num_locks`, transaction
505    /// `writes` (or `get_for_update`) will return an error.
506    /// If this value is not positive, no limit will be enforced.
507    ///
508    /// Default: -1.
509    pub fn set_max_num_locks(&mut self, max_num_locks: i64) {
510        unsafe {
511            ffi::rocksdb_transactiondb_options_set_max_num_locks(self.inner, max_num_locks);
512        }
513    }
514
515    /// Specifies lock table stripes count.
516    ///
517    /// Increasing this value will increase the concurrency by dividing the lock
518    /// table (per column family) into more sub-tables, each with their own
519    /// separate mutex.
520    ///
521    /// Default: 16.
522    pub fn set_num_stripes(&mut self, num_stripes: usize) {
523        unsafe {
524            ffi::rocksdb_transactiondb_options_set_num_stripes(self.inner, num_stripes);
525        }
526    }
527
528    /// A flag to control for the whole DB whether user-defined timestamp based validation are
529    /// enabled when applicable. Only WriteCommittedTxn support user-defined timestamps so
530    /// this option only applies in this case.
531    pub fn set_enable_udt_validation(&mut self, val: bool) {
532        unsafe {
533            ffi::rocksdb_transactiondb_options_set_enable_udt_validation(self.inner, u8::from(val));
534        }
535    }
536
537    /// Returns the value of the `enable_udt_validation` option.
538    pub fn get_enable_udt_validation(&self) -> bool {
539        unsafe { ffi::rocksdb_transactiondb_options_get_enable_udt_validation(self.inner) != 0 }
540    }
541
542    /// Stores the number of latest deadlocks to track
543    pub fn set_max_num_deadlocks(&mut self, val: u32) {
544        unsafe {
545            ffi::rocksdb_transactiondb_options_set_max_num_deadlocks(self.inner, val);
546        }
547    }
548
549    /// Returns the value of the `max_num_deadlocks` option.
550    pub fn get_max_num_deadlocks(&self) -> u32 {
551        unsafe { ffi::rocksdb_transactiondb_options_get_max_num_deadlocks(self.inner) }
552    }
553
554    /// Increasing this value will increase the concurrency by dividing the lock table (per
555    /// column family) into more sub-tables, each with their own separate mutex.
556    pub fn get_num_stripes(&self) -> usize {
557        unsafe { ffi::rocksdb_transactiondb_options_get_num_stripes(self.inner) }
558    }
559
560    /// TODO(myabandeh): remove this option Note: this is a temporary option as a hot fix in
561    /// rollback of writeprepared txns in myrocks. MyRocks uses merge operands for autoinc
562    /// column id without however obtaining locks. This breaks the assumption behind the
563    /// rollback logic in myrocks. This hack of simply not rolling back merge operands works
564    /// for the special way that myrocks uses this operands.
565    pub fn set_rollback_merge_operands(&mut self, val: bool) {
566        unsafe {
567            ffi::rocksdb_transactiondb_options_set_rollback_merge_operands(
568                self.inner,
569                u8::from(val),
570            );
571        }
572    }
573
574    /// Returns the value of the `rollback_merge_operands` option.
575    pub fn get_rollback_merge_operands(&self) -> bool {
576        unsafe { ffi::rocksdb_transactiondb_options_get_rollback_merge_operands(self.inner) != 0 }
577    }
578
579    /// If true, the TransactionDB implementation might skip concurrency control unless it is
580    /// overridden by TransactionOptions or TransactionDBWriteOptimizations. This can be used
581    /// in conjunction with DBOptions::unordered_write when the TransactionDB is used solely
582    /// for write ordering rather than concurrency control.
583    pub fn set_skip_concurrency_control(&mut self, val: bool) {
584        unsafe {
585            ffi::rocksdb_transactiondb_options_set_skip_concurrency_control(
586                self.inner,
587                u8::from(val),
588            );
589        }
590    }
591
592    /// Returns the value of the `skip_concurrency_control` option.
593    pub fn get_skip_concurrency_control(&self) -> bool {
594        unsafe { ffi::rocksdb_transactiondb_options_get_skip_concurrency_control(self.inner) != 0 }
595    }
596
597    /// Deprecated, this option has no effect and may be removed in the future. Use
598    /// TransactionOptions::large_txn_commit_optimize_threshold instead.
599    ///
600    /// This option is only valid for write committed. If the number of updates in a
601    /// transaction is at least this threshold, then the transaction commit will skip
602    /// insertions into memtable as an optimization to reduce commit latency. See comment for
603    /// TransactionOptions::commit_bypass_memtable for more detail. Setting
604    /// TransactionOptions::commit_bypass_memtable to true takes precedence over this option.
605    pub fn set_txn_commit_bypass_memtable_threshold(&mut self, val: u32) {
606        unsafe {
607            ffi::rocksdb_transactiondb_options_set_txn_commit_bypass_memtable_threshold(
608                self.inner, val,
609            );
610        }
611    }
612
613    /// Returns the value of the `txn_commit_bypass_memtable_threshold` option.
614    pub fn get_txn_commit_bypass_memtable_threshold(&self) -> u32 {
615        unsafe {
616            ffi::rocksdb_transactiondb_options_get_txn_commit_bypass_memtable_threshold(self.inner)
617        }
618    }
619
620    /// EXPERIMENTAL
621    ///
622    /// Flag to enable/disable the per key point lock manager.
623    pub fn set_use_per_key_point_lock_mgr(&mut self, val: bool) {
624        unsafe {
625            ffi::rocksdb_transactiondb_options_set_use_per_key_point_lock_mgr(
626                self.inner,
627                u8::from(val),
628            );
629        }
630    }
631
632    /// Returns the value of the `use_per_key_point_lock_mgr` option.
633    pub fn get_use_per_key_point_lock_mgr(&self) -> bool {
634        unsafe {
635            ffi::rocksdb_transactiondb_options_get_use_per_key_point_lock_mgr(self.inner) != 0
636        }
637    }
638
639    /// Returns the current `default_lock_timeout` setting.
640    ///
641    /// See [`Self::set_default_lock_timeout`] for what this controls.
642    pub fn get_default_lock_timeout(&self) -> i64 {
643        unsafe { ffi::rocksdb_transactiondb_options_get_default_lock_timeout(self.inner) }
644    }
645
646    /// Returns the current `default_write_batch_flush_threshold` setting.
647    ///
648    /// See [`Self::set_default_write_batch_flush_threshold`] for what this controls.
649    pub fn get_default_write_batch_flush_threshold(&self) -> i64 {
650        unsafe {
651            ffi::rocksdb_transactiondb_options_get_default_write_batch_flush_threshold(self.inner)
652        }
653    }
654
655    /// Returns the current `max_num_locks` setting.
656    ///
657    /// See [`Self::set_max_num_locks`] for what this controls.
658    pub fn get_max_num_locks(&self) -> i64 {
659        unsafe { ffi::rocksdb_transactiondb_options_get_max_num_locks(self.inner) }
660    }
661
662    /// If positive, specifies the default wait timeout in milliseconds when a transaction
663    /// attempts to lock a key if not specified by TransactionOptions::lock_timeout.
664    ///
665    /// If 0, no waiting is done if a lock cannot instantly be acquired. If negative, there is
666    /// no timeout.  Not using a timeout is not recommended as it can lead to deadlocks.
667    /// Currently, there is no deadlock-detection to recover from a deadlock.
668    pub fn get_transaction_lock_timeout(&self) -> i64 {
669        unsafe { ffi::rocksdb_transactiondb_options_get_transaction_lock_timeout(self.inner) }
670    }
671
672    /// This option is only valid for write unprepared. If a write batch exceeds this
673    /// threshold, then the transaction will implicitly flush the currently pending writes
674    /// into the database. A value of 0 or less means no limit.
675    pub fn set_default_write_batch_flush_threshold(&mut self, val: i64) {
676        unsafe {
677            ffi::rocksdb_transactiondb_options_set_default_write_batch_flush_threshold(
678                self.inner, val,
679            );
680        }
681    }
682
683    /// Sets when transaction data becomes visible in the DB.
684    ///
685    /// Only read while opening a [`TransactionDB`], so this has to be set before the
686    /// open call. See [`TxnDBWritePolicy`] for what each policy costs.
687    ///
688    /// Default: [`TxnDBWritePolicy::WriteCommitted`].
689    ///
690    /// [`TransactionDB`]: crate::TransactionDB
691    pub fn set_write_policy(&mut self, policy: TxnDBWritePolicy) {
692        unsafe {
693            ffi::rocksdb_transactiondb_options_set_write_policy(self.inner, policy as c_int);
694        }
695    }
696
697    /// The write policy set by [`Self::set_write_policy`].
698    ///
699    /// [`TxnDBWritePolicy`] covers every policy RocksDB defines today, so `None` only
700    /// shows up if a future release adds one.
701    pub fn get_write_policy(&self) -> Option<TxnDBWritePolicy> {
702        let raw = unsafe { ffi::rocksdb_transactiondb_options_get_write_policy(self.inner) };
703        TxnDBWritePolicy::try_from_raw(raw)
704    }
705}
706
707impl Drop for TransactionDBOptions {
708    fn drop(&mut self) {
709        unsafe {
710            ffi::rocksdb_transactiondb_options_destroy(self.inner);
711        }
712    }
713}
714
715pub struct OptimisticTransactionOptions {
716    pub(crate) inner: *mut ffi::rocksdb_optimistictransaction_options_t,
717}
718
719unsafe impl Send for OptimisticTransactionOptions {}
720unsafe impl Sync for OptimisticTransactionOptions {}
721
722impl Default for OptimisticTransactionOptions {
723    fn default() -> Self {
724        let txn_opts = unsafe { ffi::rocksdb_optimistictransaction_options_create() };
725        assert!(
726            !txn_opts.is_null(),
727            "Could not create RocksDB optimistic transaction options"
728        );
729        Self { inner: txn_opts }
730    }
731}
732
733impl OptimisticTransactionOptions {
734    pub fn new() -> OptimisticTransactionOptions {
735        OptimisticTransactionOptions::default()
736    }
737
738    /// Specifies use snapshot or not.
739    ///
740    /// Default: false.
741    ///
742    /// If a transaction has a snapshot set, the transaction will ensure that
743    /// any keys successfully written(or fetched via `get_for_update`) have not
744    /// been modified outside the transaction since the time the snapshot was
745    /// set.
746    /// If a snapshot has not been set, the transaction guarantees that keys have
747    /// not been modified since the time each key was first written (or fetched via
748    /// `get_for_update`).
749    ///
750    /// Using snapshot will provide stricter isolation guarantees at the
751    /// expense of potentially more transaction failures due to conflicts with
752    /// other writes.
753    ///
754    /// Calling `set_snapshot` will not affect the version of Data returned by `get`
755    /// methods.
756    pub fn set_snapshot(&mut self, snapshot: bool) {
757        unsafe {
758            ffi::rocksdb_optimistictransaction_options_set_set_snapshot(
759                self.inner,
760                u8::from(snapshot),
761            );
762        }
763    }
764
765    /// Setting set_snapshot=true is the same as calling Transaction::SetSnapshot().
766    pub fn get_set_snapshot(&self) -> bool {
767        unsafe { ffi::rocksdb_optimistictransaction_options_get_set_snapshot(self.inner) != 0 }
768    }
769}
770
771impl Drop for OptimisticTransactionOptions {
772    fn drop(&mut self) {
773        unsafe {
774            ffi::rocksdb_optimistictransaction_options_destroy(self.inner);
775        }
776    }
777}
778
779/// How an [`OptimisticTransactionDB`] checks a transaction for write conflicts at commit.
780///
781/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
782// The C API has no constants for these, unlike `TxnDBWritePolicy`. The discriminants are
783// spelled out in `rocksdb::OccValidationPolicy` in
784// include/rocksdb/utilities/optimistic_transaction_db.h and must stay in step with it.
785#[derive(Debug, Copy, Clone, PartialEq, Eq)]
786#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
787pub enum OccValidationPolicy {
788    /// Validate serially at commit, after the transaction has entered the write group.
789    ///
790    /// Validation is single threaded because the write group is. Simple, but it can
791    /// suffer from high mutex contention. See
792    /// <https://github.com/facebook/rocksdb/issues/4402>.
793    ValidateSerial = 0,
794
795    /// Validate in parallel before the transaction enters the write group.
796    ///
797    /// Each transaction takes locks for its write set in a well defined order, off the
798    /// write group, which cuts the mutex contention. This is the default. The locks come
799    /// from the pool sized by [`OptimisticTransactionDBOptions::set_occ_lock_buckets`]
800    /// or supplied by [`OptimisticTransactionDBOptions::set_shared_lock_buckets`].
801    ValidateParallel = 1,
802}
803
804impl OccValidationPolicy {
805    /// Decodes a raw `rocksdb::OccValidationPolicy`.
806    ///
807    /// This covers every policy RocksDB defines today, so `None` only means a future
808    /// release added one.
809    pub(crate) fn try_from_raw(raw: c_int) -> Option<Self> {
810        match raw {
811            n if n == OccValidationPolicy::ValidateSerial as c_int => {
812                Some(OccValidationPolicy::ValidateSerial)
813            }
814            n if n == OccValidationPolicy::ValidateParallel as c_int => {
815                Some(OccValidationPolicy::ValidateParallel)
816            }
817            _ => None,
818        }
819    }
820}
821
822struct OccLockBucketsWrapper {
823    inner: NonNull<ffi::rocksdb_optimistictransactiondb_lock_buckets_t>,
824}
825
826// The C handle is a `std::shared_ptr<OccLockBuckets>` and nothing else, so moving it
827// between threads moves a refcounted pointer. Every call this crate makes on it either
828// reads the pool's memory usage through a `const` method or copies the `shared_ptr` into
829// an options struct, and shared_ptr refcounting is atomic. The `Arc` in `OccLockBuckets`
830// is what guarantees the `_destroy` call is unshared.
831unsafe impl Send for OccLockBucketsWrapper {}
832unsafe impl Sync for OccLockBucketsWrapper {}
833
834impl Drop for OccLockBucketsWrapper {
835    fn drop(&mut self) {
836        unsafe {
837            ffi::rocksdb_optimistictransactiondb_lock_buckets_destroy(self.inner.as_ptr());
838        }
839    }
840}
841
842/// A pool of mutex locks used to validate optimistic transactions.
843///
844/// Only consulted when the validation policy is
845/// [`OccValidationPolicy::ValidateParallel`]. Hand it to
846/// [`OptimisticTransactionDBOptions::set_shared_lock_buckets`] to make several databases
847/// validate against one pool instead of each allocating its own.
848///
849/// Cloning is cheap and produces another handle to the same pool. The pool itself lives
850/// as long as any handle or any options value or database that was given one, so it is
851/// fine to drop every Rust handle right after the `set_shared_lock_buckets` call.
852#[derive(Clone)]
853pub struct OccLockBuckets(Arc<OccLockBucketsWrapper>);
854
855impl OccLockBuckets {
856    /// Allocates a pool of `bucket_count` locks.
857    ///
858    /// More buckets mean less contention between transactions validating at the same
859    /// time, and more memory. `cache_aligned` pads each lock out to a cache line, which
860    /// avoids false sharing between neighbouring buckets at the cost of more memory
861    /// again. RocksDB has historically used `false` here.
862    ///
863    /// # Panics
864    ///
865    /// Panics if RocksDB could not allocate the pool.
866    pub fn new(bucket_count: usize, cache_aligned: bool) -> Self {
867        let inner = NonNull::new(unsafe {
868            ffi::rocksdb_optimistictransactiondb_lock_buckets_create(
869                bucket_count,
870                u8::from(cache_aligned),
871            )
872        })
873        .expect("Could not create RocksDB OCC lock buckets");
874        Self(Arc::new(OccLockBucketsWrapper { inner }))
875    }
876
877    /// Estimates how much memory this pool occupies, in bytes.
878    pub fn approximate_memory_usage(&self) -> usize {
879        unsafe {
880            ffi::rocksdb_optimistictransactiondb_lock_buckets_approximate_memory_usage(
881                self.0.inner.as_ptr(),
882            )
883        }
884    }
885}
886
887/// Options for opening an [`OptimisticTransactionDB`].
888///
889/// These control conflict validation only. Everything else about the database still
890/// comes from [`Options`], which is passed alongside these to the `open` call.
891///
892/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
893/// [`Options`]: crate::Options
894pub struct OptimisticTransactionDBOptions {
895    pub(crate) inner: *mut ffi::rocksdb_optimistictransactiondb_options_t,
896}
897
898// The C handle wraps a plain `OptimisticTransactionDBOptions` struct: an enum, a
899// `uint32_t` and a `shared_ptr`. It owns no thread-local or thread-affine state, so it
900// can move between threads. Every mutating call here takes `&mut self`, so a shared `&`
901// only ever reaches the getters, which read those fields.
902unsafe impl Send for OptimisticTransactionDBOptions {}
903unsafe impl Sync for OptimisticTransactionDBOptions {}
904
905impl Default for OptimisticTransactionDBOptions {
906    fn default() -> Self {
907        let otxn_db_opts = unsafe { ffi::rocksdb_optimistictransactiondb_options_create() };
908        assert!(
909            !otxn_db_opts.is_null(),
910            "Could not create RocksDB optimistic transaction_db options"
911        );
912        Self {
913            inner: otxn_db_opts,
914        }
915    }
916}
917
918impl OptimisticTransactionDBOptions {
919    pub fn new() -> OptimisticTransactionDBOptions {
920        OptimisticTransactionDBOptions::default()
921    }
922
923    /// Specifies how transactions are checked for write conflicts at commit.
924    ///
925    /// Default: [`OccValidationPolicy::ValidateParallel`].
926    pub fn set_validate_policy(&mut self, policy: OccValidationPolicy) {
927        unsafe {
928            ffi::rocksdb_optimistictransactiondb_options_set_validate_policy(
929                self.inner,
930                policy as c_int,
931            );
932        }
933    }
934
935    /// The validation policy set by [`Self::set_validate_policy`].
936    ///
937    /// [`OccValidationPolicy`] covers every policy RocksDB defines today, so `None` only
938    /// shows up if a future release adds one.
939    pub fn get_validate_policy(&self) -> Option<OccValidationPolicy> {
940        let raw =
941            unsafe { ffi::rocksdb_optimistictransactiondb_options_get_validate_policy(self.inner) };
942        OccValidationPolicy::try_from_raw(raw)
943    }
944
945    /// Specifies how many striped mutex locks to allocate for validating transactions.
946    ///
947    /// Only used when the validation policy is
948    /// [`OccValidationPolicy::ValidateParallel`] and no pool was supplied through
949    /// [`Self::set_shared_lock_buckets`]. A larger count potentially reduces contention
950    /// but uses more memory.
951    ///
952    /// Default: 1048576 (1 << 20).
953    pub fn set_occ_lock_buckets(&mut self, num_buckets: u32) {
954        unsafe {
955            ffi::rocksdb_optimistictransactiondb_options_set_occ_lock_buckets(
956                self.inner,
957                num_buckets,
958            );
959        }
960    }
961
962    /// Returns the value of the `occ_lock_buckets` option.
963    pub fn get_occ_lock_buckets(&self) -> u32 {
964        unsafe { ffi::rocksdb_optimistictransactiondb_options_get_occ_lock_buckets(self.inner) }
965    }
966
967    /// Validates against `buckets` instead of a pool private to this database.
968    ///
969    /// Give the same [`OccLockBuckets`] to several databases and they share one set of
970    /// locks, which bounds the total memory spent on validation. Ignored unless the
971    /// validation policy is [`OccValidationPolicy::ValidateParallel`], and it overrides
972    /// [`Self::set_occ_lock_buckets`] when set.
973    ///
974    /// These options take their own reference to the pool, so `buckets` may be dropped
975    /// as soon as this returns.
976    pub fn set_shared_lock_buckets(&mut self, buckets: &OccLockBuckets) {
977        unsafe {
978            ffi::rocksdb_optimistictransactiondb_options_set_shared_lock_buckets(
979                self.inner,
980                buckets.0.inner.as_ptr(),
981            );
982        }
983    }
984}
985
986impl Drop for OptimisticTransactionDBOptions {
987    fn drop(&mut self) {
988        unsafe {
989            ffi::rocksdb_optimistictransactiondb_options_destroy(self.inner);
990        }
991    }
992}