Support RocksDB transaction. (#565)
parent
934855fe54
commit
2257be1563
@ -0,0 +1,24 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
mod optimistic_transaction_db; |
||||
mod options; |
||||
mod transaction; |
||||
mod transaction_db; |
||||
|
||||
pub use optimistic_transaction_db::OptimisticTransactionDB; |
||||
pub use options::{OptimisticTransactionOptions, TransactionDBOptions, TransactionOptions}; |
||||
pub use transaction::Transaction; |
||||
pub use transaction_db::TransactionDB; |
@ -0,0 +1,294 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
use std::{collections::BTreeMap, ffi::CString, fs, iter, marker::PhantomData, path::Path, ptr}; |
||||
|
||||
use libc::{c_char, c_int}; |
||||
|
||||
use crate::{ |
||||
db::DBCommon, db::DBInner, ffi, ffi_util::to_cpath, write_batch::WriteBatchWithTransaction, |
||||
ColumnFamilyDescriptor, Error, OptimisticTransactionOptions, Options, ThreadMode, Transaction, |
||||
WriteOptions, DEFAULT_COLUMN_FAMILY_NAME, |
||||
}; |
||||
|
||||
/// A type alias to RocksDB Optimistic Transaction DB.
|
||||
///
|
||||
/// Please read the official
|
||||
/// [guide](https://github.com/facebook/rocksdb/wiki/Transactions#optimistictransactiondb)
|
||||
/// to learn more about RocksDB OptimisticTransactionDB.
|
||||
///
|
||||
/// The default thread mode for [`OptimisticTransactionDB`] is [`SingleThreaded`]
|
||||
/// if feature `multi-threaded-cf` is not enabled.
|
||||
///
|
||||
/// See [`DBCommon`] for full list of methods.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rocksdb::{DB, Options, OptimisticTransactionDB, SingleThreaded};
|
||||
/// let path = "_path_for_optimistic_transaction_db";
|
||||
/// {
|
||||
/// let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(path).unwrap();
|
||||
/// db.put(b"my key", b"my value").unwrap();
|
||||
///
|
||||
/// // create transaction
|
||||
/// let txn = db.transaction();
|
||||
/// txn.put(b"key2", b"value2");
|
||||
/// txn.put(b"key3", b"value3");
|
||||
/// txn.commit().unwrap();
|
||||
/// }
|
||||
/// let _ = DB::destroy(&Options::default(), path);
|
||||
/// ```
|
||||
///
|
||||
/// [`SingleThreaded`]: crate::SingleThreaded
|
||||
#[cfg(not(feature = "multi-threaded-cf"))] |
||||
pub type OptimisticTransactionDB<T = crate::SingleThreaded> = |
||||
DBCommon<T, OptimisticTransactionDBInner>; |
||||
#[cfg(feature = "multi-threaded-cf")] |
||||
pub type OptimisticTransactionDB<T = crate::MultiThreaded> = |
||||
DBCommon<T, OptimisticTransactionDBInner>; |
||||
|
||||
pub struct OptimisticTransactionDBInner { |
||||
base: *mut ffi::rocksdb_t, |
||||
db: *mut ffi::rocksdb_optimistictransactiondb_t, |
||||
} |
||||
|
||||
impl DBInner for OptimisticTransactionDBInner { |
||||
fn inner(&self) -> *mut ffi::rocksdb_t { |
||||
self.base |
||||
} |
||||
} |
||||
|
||||
impl Drop for OptimisticTransactionDBInner { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
ffi::rocksdb_optimistictransactiondb_close_base_db(self.base); |
||||
ffi::rocksdb_optimistictransactiondb_close(self.db); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Methods of `OptimisticTransactionDB`.
|
||||
impl<T: ThreadMode> OptimisticTransactionDB<T> { |
||||
/// Opens a database with default options.
|
||||
pub fn open_default<P: AsRef<Path>>(path: P) -> Result<Self, Error> { |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
Self::open(&opts, path) |
||||
} |
||||
|
||||
/// Opens the database with the specified options.
|
||||
pub fn open<P: AsRef<Path>>(opts: &Options, path: P) -> Result<Self, Error> { |
||||
Self::open_cf(opts, path, None::<&str>) |
||||
} |
||||
|
||||
/// Opens a database with the given database options and column family names.
|
||||
///
|
||||
/// Column families opened using this function will be created with default `Options`.
|
||||
pub fn open_cf<P, I, N>(opts: &Options, path: P, cfs: I) -> Result<Self, Error> |
||||
where |
||||
P: AsRef<Path>, |
||||
I: IntoIterator<Item = N>, |
||||
N: AsRef<str>, |
||||
{ |
||||
let cfs = cfs |
||||
.into_iter() |
||||
.map(|name| ColumnFamilyDescriptor::new(name.as_ref(), Options::default())); |
||||
|
||||
Self::open_cf_descriptors_internal(opts, path, cfs) |
||||
} |
||||
|
||||
/// Opens a database with the given database options and column family descriptors.
|
||||
pub fn open_cf_descriptors<P, I>(opts: &Options, path: P, cfs: I) -> Result<Self, Error> |
||||
where |
||||
P: AsRef<Path>, |
||||
I: IntoIterator<Item = ColumnFamilyDescriptor>, |
||||
{ |
||||
Self::open_cf_descriptors_internal(opts, path, cfs) |
||||
} |
||||
|
||||
/// Internal implementation for opening RocksDB.
|
||||
fn open_cf_descriptors_internal<P, I>(opts: &Options, path: P, cfs: I) -> Result<Self, Error> |
||||
where |
||||
P: AsRef<Path>, |
||||
I: IntoIterator<Item = ColumnFamilyDescriptor>, |
||||
{ |
||||
let cfs: Vec<_> = cfs.into_iter().collect(); |
||||
let outlive = iter::once(opts.outlive.clone()) |
||||
.chain(cfs.iter().map(|cf| cf.options.outlive.clone())) |
||||
.collect(); |
||||
|
||||
let cpath = to_cpath(&path)?; |
||||
|
||||
if let Err(e) = fs::create_dir_all(&path) { |
||||
return Err(Error::new(format!( |
||||
"Failed to create RocksDB directory: `{:?}`.", |
||||
e |
||||
))); |
||||
} |
||||
|
||||
let db: *mut ffi::rocksdb_optimistictransactiondb_t; |
||||
let mut cf_map = BTreeMap::new(); |
||||
|
||||
if cfs.is_empty() { |
||||
db = Self::open_raw(opts, &cpath)?; |
||||
} else { |
||||
let mut cfs_v = cfs; |
||||
// Always open the default column family.
|
||||
if !cfs_v.iter().any(|cf| cf.name == DEFAULT_COLUMN_FAMILY_NAME) { |
||||
cfs_v.push(ColumnFamilyDescriptor { |
||||
name: String::from(DEFAULT_COLUMN_FAMILY_NAME), |
||||
options: Options::default(), |
||||
}); |
||||
} |
||||
// We need to store our CStrings in an intermediate vector
|
||||
// so that their pointers remain valid.
|
||||
let c_cfs: Vec<CString> = cfs_v |
||||
.iter() |
||||
.map(|cf| CString::new(cf.name.as_bytes()).unwrap()) |
||||
.collect(); |
||||
|
||||
let cfnames: Vec<_> = c_cfs.iter().map(|cf| cf.as_ptr()).collect(); |
||||
|
||||
// These handles will be populated by DB.
|
||||
let mut cfhandles: Vec<_> = cfs_v.iter().map(|_| ptr::null_mut()).collect(); |
||||
|
||||
let cfopts: Vec<_> = cfs_v |
||||
.iter() |
||||
.map(|cf| cf.options.inner as *const _) |
||||
.collect(); |
||||
|
||||
db = Self::open_cf_raw(opts, &cpath, &cfs_v, &cfnames, &cfopts, &mut cfhandles)?; |
||||
|
||||
for handle in &cfhandles { |
||||
if handle.is_null() { |
||||
return Err(Error::new( |
||||
"Received null column family handle from DB.".to_owned(), |
||||
)); |
||||
} |
||||
} |
||||
|
||||
for (cf_desc, inner) in cfs_v.iter().zip(cfhandles) { |
||||
cf_map.insert(cf_desc.name.clone(), inner); |
||||
} |
||||
} |
||||
|
||||
if db.is_null() { |
||||
return Err(Error::new("Could not initialize database.".to_owned())); |
||||
} |
||||
|
||||
let base = unsafe { ffi::rocksdb_optimistictransactiondb_get_base_db(db) }; |
||||
if base.is_null() { |
||||
unsafe { |
||||
ffi::rocksdb_optimistictransactiondb_close(db); |
||||
} |
||||
return Err(Error::new("Could not initialize database.".to_owned())); |
||||
} |
||||
let inner = OptimisticTransactionDBInner { base, db }; |
||||
|
||||
Ok(Self::new( |
||||
inner, |
||||
T::new_cf_map_internal(cf_map), |
||||
path.as_ref().to_path_buf(), |
||||
outlive, |
||||
)) |
||||
} |
||||
|
||||
fn open_raw( |
||||
opts: &Options, |
||||
cpath: &CString, |
||||
) -> Result<*mut ffi::rocksdb_optimistictransactiondb_t, Error> { |
||||
unsafe { |
||||
let db = ffi_try!(ffi::rocksdb_optimistictransactiondb_open( |
||||
opts.inner, |
||||
cpath.as_ptr() |
||||
)); |
||||
Ok(db) |
||||
} |
||||
} |
||||
|
||||
fn open_cf_raw( |
||||
opts: &Options, |
||||
cpath: &CString, |
||||
cfs_v: &[ColumnFamilyDescriptor], |
||||
cfnames: &[*const c_char], |
||||
cfopts: &[*const ffi::rocksdb_options_t], |
||||
cfhandles: &mut [*mut ffi::rocksdb_column_family_handle_t], |
||||
) -> Result<*mut ffi::rocksdb_optimistictransactiondb_t, Error> { |
||||
unsafe { |
||||
let db = ffi_try!(ffi::rocksdb_optimistictransactiondb_open_column_families( |
||||
opts.inner, |
||||
cpath.as_ptr(), |
||||
cfs_v.len() as c_int, |
||||
cfnames.as_ptr(), |
||||
cfopts.as_ptr(), |
||||
cfhandles.as_mut_ptr(), |
||||
)); |
||||
Ok(db) |
||||
} |
||||
} |
||||
|
||||
/// Creates a transaction with default options.
|
||||
pub fn transaction(&self) -> Transaction<Self> { |
||||
self.transaction_opt( |
||||
&WriteOptions::default(), |
||||
&OptimisticTransactionOptions::default(), |
||||
) |
||||
} |
||||
|
||||
/// Creates a transaction with default options.
|
||||
pub fn transaction_opt( |
||||
&self, |
||||
writeopts: &WriteOptions, |
||||
otxn_opts: &OptimisticTransactionOptions, |
||||
) -> Transaction<Self> { |
||||
Transaction { |
||||
inner: unsafe { |
||||
ffi::rocksdb_optimistictransaction_begin( |
||||
self.inner.db, |
||||
writeopts.inner, |
||||
otxn_opts.inner, |
||||
std::ptr::null_mut(), |
||||
) |
||||
}, |
||||
_marker: PhantomData::default(), |
||||
} |
||||
} |
||||
|
||||
pub fn write_opt( |
||||
&self, |
||||
batch: WriteBatchWithTransaction<true>, |
||||
writeopts: &WriteOptions, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_optimistictransactiondb_write( |
||||
self.inner.db, |
||||
writeopts.inner, |
||||
batch.inner |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn write(&self, batch: WriteBatchWithTransaction<true>) -> Result<(), Error> { |
||||
self.write_opt(batch, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn write_without_wal(&self, batch: WriteBatchWithTransaction<true>) -> Result<(), Error> { |
||||
let mut wo = WriteOptions::new(); |
||||
wo.disable_wal(true); |
||||
self.write_opt(batch, &wo) |
||||
} |
||||
} |
@ -0,0 +1,297 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
use crate::ffi; |
||||
use core::panic; |
||||
|
||||
pub struct TransactionOptions { |
||||
pub(crate) inner: *mut ffi::rocksdb_transaction_options_t, |
||||
} |
||||
|
||||
unsafe impl Send for TransactionOptions {} |
||||
unsafe impl Sync for TransactionOptions {} |
||||
|
||||
impl Default for TransactionOptions { |
||||
fn default() -> Self { |
||||
let txn_opts = unsafe { ffi::rocksdb_transaction_options_create() }; |
||||
assert!( |
||||
!txn_opts.is_null(), |
||||
"Could not create RocksDB transaction options" |
||||
); |
||||
Self { inner: txn_opts } |
||||
} |
||||
} |
||||
|
||||
impl TransactionOptions { |
||||
pub fn new() -> TransactionOptions { |
||||
TransactionOptions::default() |
||||
} |
||||
|
||||
pub fn set_skip_prepare(&mut self, skip_prepare: bool) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_set_snapshot(self.inner, u8::from(skip_prepare)); |
||||
} |
||||
} |
||||
|
||||
/// Specifies use snapshot or not.
|
||||
///
|
||||
/// Default: false.
|
||||
///
|
||||
/// If a transaction has a snapshot set, the transaction will ensure that
|
||||
/// any keys successfully written(or fetched via `get_for_update`) 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
|
||||
/// `get_for_update`).
|
||||
///
|
||||
/// Using snapshot will provide stricter isolation guarantees at the
|
||||
/// expense of potentially more transaction failures due to conflicts with
|
||||
/// other writes.
|
||||
///
|
||||
/// Calling `set_snapshot` will not affect the version of Data returned by `get`
|
||||
/// methods.
|
||||
pub fn set_snapshot(&mut self, snapshot: bool) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_set_snapshot(self.inner, u8::from(snapshot)); |
||||
} |
||||
} |
||||
|
||||
/// Specifies whether detect deadlock or not.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Default: false.
|
||||
pub fn set_deadlock_detect(&mut self, deadlock_detect: bool) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_deadlock_detect( |
||||
self.inner, |
||||
u8::from(deadlock_detect), |
||||
); |
||||
} |
||||
} |
||||
|
||||
/// 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, transaction lock timeout in `TransactionDBOptions` will be used.
|
||||
///
|
||||
/// Default: -1.
|
||||
pub fn set_lock_timeout(&mut self, lock_timeout: i64) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_lock_timeout(self.inner, lock_timeout); |
||||
} |
||||
} |
||||
|
||||
/// Specifies 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 by other writers.
|
||||
///
|
||||
/// Default: -1.
|
||||
pub fn set_expiration(&mut self, expiration: i64) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_expiration(self.inner, expiration); |
||||
} |
||||
} |
||||
|
||||
/// Specifies the number of traversals to make during deadlock detection.
|
||||
///
|
||||
/// Default: 50.
|
||||
pub fn set_deadlock_detect_depth(&mut self, depth: i64) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_deadlock_detect_depth(self.inner, depth); |
||||
} |
||||
} |
||||
|
||||
/// Specifies the maximum number of bytes used for the write batch. 0 means no limit.
|
||||
///
|
||||
/// Default: 0.
|
||||
pub fn set_max_write_batch_size(&mut self, size: usize) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_set_max_write_batch_size(self.inner, size); |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Drop for TransactionOptions { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_options_destroy(self.inner); |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct TransactionDBOptions { |
||||
pub(crate) inner: *mut ffi::rocksdb_transactiondb_options_t, |
||||
} |
||||
|
||||
unsafe impl Send for TransactionDBOptions {} |
||||
unsafe impl Sync for TransactionDBOptions {} |
||||
|
||||
impl Default for TransactionDBOptions { |
||||
fn default() -> Self { |
||||
let txn_db_opts = unsafe { ffi::rocksdb_transactiondb_options_create() }; |
||||
assert!( |
||||
!txn_db_opts.is_null(), |
||||
"Could not create RocksDB transactiondb options" |
||||
); |
||||
Self { inner: txn_db_opts } |
||||
} |
||||
} |
||||
|
||||
impl TransactionDBOptions { |
||||
pub fn new() -> TransactionDBOptions { |
||||
TransactionDBOptions::default() |
||||
} |
||||
|
||||
/// Specifies the wait timeout in milliseconds when writing a key
|
||||
/// outside of a transaction (ie. by calling `TransactionDB::put` 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(1s).
|
||||
pub fn set_default_lock_timeout(&mut self, default_lock_timeout: i64) { |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_options_set_default_lock_timeout( |
||||
self.inner, |
||||
default_lock_timeout, |
||||
); |
||||
} |
||||
} |
||||
|
||||
/// Specifies the default wait timeout in milliseconds when a stransaction
|
||||
/// attempts to lock a key if not secified in `TransactionOptions`.
|
||||
///
|
||||
/// 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(1s).
|
||||
pub fn set_txn_lock_timeout(&mut self, txn_lock_timeout: i64) { |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_options_set_transaction_lock_timeout( |
||||
self.inner, |
||||
txn_lock_timeout, |
||||
); |
||||
} |
||||
} |
||||
|
||||
/// 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 `get_for_update`) will return an error.
|
||||
/// If this value is not positive, no limit will be enforced.
|
||||
///
|
||||
/// Default: -1.
|
||||
pub fn set_max_num_locks(&mut self, max_num_locks: i64) { |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_options_set_max_num_locks(self.inner, max_num_locks); |
||||
} |
||||
} |
||||
|
||||
/// Specifies lock table stripes count.
|
||||
///
|
||||
/// 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.
|
||||
pub fn set_num_stripes(&mut self, num_stripes: usize) { |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_options_set_num_stripes(self.inner, num_stripes); |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Drop for TransactionDBOptions { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_options_destroy(self.inner); |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct OptimisticTransactionOptions { |
||||
pub(crate) inner: *mut ffi::rocksdb_optimistictransaction_options_t, |
||||
} |
||||
|
||||
unsafe impl Send for OptimisticTransactionOptions {} |
||||
unsafe impl Sync for OptimisticTransactionOptions {} |
||||
|
||||
impl Default for OptimisticTransactionOptions { |
||||
fn default() -> Self { |
||||
let txn_opts = unsafe { ffi::rocksdb_optimistictransaction_options_create() }; |
||||
assert!( |
||||
!txn_opts.is_null(), |
||||
"Could not create RocksDB optimistic transaction options" |
||||
); |
||||
Self { inner: txn_opts } |
||||
} |
||||
} |
||||
|
||||
impl OptimisticTransactionOptions { |
||||
pub fn new() -> OptimisticTransactionOptions { |
||||
OptimisticTransactionOptions::default() |
||||
} |
||||
|
||||
/// Specifies use snapshot or not.
|
||||
///
|
||||
/// Default: false.
|
||||
///
|
||||
/// If a transaction has a snapshot set, the transaction will ensure that
|
||||
/// any keys successfully written(or fetched via `get_for_update`) 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
|
||||
/// `get_for_update`).
|
||||
///
|
||||
/// Using snapshot will provide stricter isolation guarantees at the
|
||||
/// expense of potentially more transaction failures due to conflicts with
|
||||
/// other writes.
|
||||
///
|
||||
/// Calling `set_snapshot` will not affect the version of Data returned by `get`
|
||||
/// methods.
|
||||
pub fn set_snapshot(&mut self, snapshot: bool) { |
||||
unsafe { |
||||
ffi::rocksdb_optimistictransaction_options_set_set_snapshot( |
||||
self.inner, |
||||
u8::from(snapshot), |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Drop for OptimisticTransactionOptions { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
ffi::rocksdb_optimistictransaction_options_destroy(self.inner); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,881 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
use std::{marker::PhantomData, ptr}; |
||||
|
||||
use crate::{ |
||||
db::{convert_values, DBAccess}, |
||||
ffi, AsColumnFamilyRef, DBIteratorWithThreadMode, DBPinnableSlice, DBRawIteratorWithThreadMode, |
||||
Direction, Error, IteratorMode, ReadOptions, SnapshotWithThreadMode, WriteBatchWithTransaction, |
||||
}; |
||||
use libc::{c_char, c_void, size_t}; |
||||
|
||||
/// RocksDB Transaction.
|
||||
///
|
||||
/// To use transactions, you must first create a [`TransactionDB`] or [`OptimisticTransactionDB`].
|
||||
///
|
||||
/// [`TransactionDB`]: crate::TransactionDB
|
||||
/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
|
||||
pub struct Transaction<'db, DB> { |
||||
pub(crate) inner: *mut ffi::rocksdb_transaction_t, |
||||
pub(crate) _marker: PhantomData<&'db DB>, |
||||
} |
||||
|
||||
unsafe impl<'db, DB> Send for Transaction<'db, DB> {} |
||||
|
||||
impl<'db, DB> DBAccess for Transaction<'db, DB> { |
||||
unsafe fn create_snapshot(&self) -> *const ffi::rocksdb_snapshot_t { |
||||
ffi::rocksdb_transaction_get_snapshot(self.inner) |
||||
} |
||||
|
||||
unsafe fn release_snapshot(&self, snapshot: *const ffi::rocksdb_snapshot_t) { |
||||
ffi::rocksdb_free(snapshot as *mut c_void); |
||||
} |
||||
|
||||
unsafe fn create_iterator(&self, readopts: &ReadOptions) -> *mut ffi::rocksdb_iterator_t { |
||||
ffi::rocksdb_transaction_create_iterator(self.inner, readopts.inner) |
||||
} |
||||
|
||||
unsafe fn create_iterator_cf( |
||||
&self, |
||||
cf_handle: *mut ffi::rocksdb_column_family_handle_t, |
||||
readopts: &ReadOptions, |
||||
) -> *mut ffi::rocksdb_iterator_t { |
||||
ffi::rocksdb_transaction_create_iterator_cf(self.inner, readopts.inner, cf_handle) |
||||
} |
||||
|
||||
fn get_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_opt(key, readopts) |
||||
} |
||||
|
||||
fn get_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_cf_opt(cf, key, readopts) |
||||
} |
||||
|
||||
fn get_pinned_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_opt(key, readopts) |
||||
} |
||||
|
||||
fn get_pinned_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_cf_opt(cf, key, readopts) |
||||
} |
||||
|
||||
fn multi_get_opt<K, I>( |
||||
&self, |
||||
keys: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = K>, |
||||
{ |
||||
self.multi_get_opt(keys, readopts) |
||||
} |
||||
|
||||
fn multi_get_cf_opt<'b, K, I, W>( |
||||
&self, |
||||
keys_cf: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = (&'b W, K)>, |
||||
W: AsColumnFamilyRef + 'b, |
||||
{ |
||||
self.multi_get_cf_opt(keys_cf, readopts) |
||||
} |
||||
} |
||||
|
||||
impl<'db, DB> Transaction<'db, DB> { |
||||
/// Write all batched keys to the DB atomically.
|
||||
///
|
||||
/// May return any error that could be returned by `DB::write`.
|
||||
///
|
||||
/// If this transaction was created by a [`TransactionDB`], an error of
|
||||
/// the [`Expired`] kind may be returned if this transaction has
|
||||
/// lived longer than expiration time in [`TransactionOptions`].
|
||||
///
|
||||
/// If this transaction was created by an [`OptimisticTransactionDB`], an error of
|
||||
/// the [`Busy`] kind may be returned if the transaction
|
||||
/// could not guarantee that there are no write conflicts.
|
||||
/// An error of the [`TryAgain`] kind may be returned if the memtable
|
||||
/// history size is not large enough (see [`Options::set_max_write_buffer_size_to_maintain`]).
|
||||
///
|
||||
/// [`Expired`]: crate::ErrorKind::Expired
|
||||
/// [`TransactionOptions`]: crate::TransactionOptions
|
||||
/// [`TransactionDB`]: crate::TransactionDB
|
||||
/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
|
||||
/// [`Busy`]: crate::ErrorKind::Busy
|
||||
/// [`TryAgain`]: crate::ErrorKind::TryAgain
|
||||
/// [`Options::set_max_write_buffer_size_to_maintain`]: crate::Options::set_max_write_buffer_size_to_maintain
|
||||
pub fn commit(self) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_commit(self.inner)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn set_name(&self, name: &[u8]) -> Result<(), Error> { |
||||
let ptr = name.as_ptr(); |
||||
let len = name.len(); |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_set_name( |
||||
self.inner, ptr as _, len as _ |
||||
)); |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn get_name(&self) -> Option<Vec<u8>> { |
||||
unsafe { |
||||
let mut name_len = 0; |
||||
let name = ffi::rocksdb_transaction_get_name(self.inner, &mut name_len); |
||||
if name.is_null() { |
||||
None |
||||
} else { |
||||
let mut vec = vec![0; name_len]; |
||||
std::ptr::copy_nonoverlapping(name as *mut u8, vec.as_mut_ptr(), name_len as usize); |
||||
ffi::rocksdb_free(name as *mut c_void); |
||||
Some(vec) |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn prepare(&self) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_prepare(self.inner)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Returns snapshot associated with transaction if snapshot was enabled in [`TransactionOptions`].
|
||||
/// Otherwise, returns a snapshot with `nullptr` inside which doesn't effect read operations.
|
||||
///
|
||||
/// [`TransactionOptions`]: crate::TransactionOptions
|
||||
pub fn snapshot(&self) -> SnapshotWithThreadMode<Self> { |
||||
SnapshotWithThreadMode::new(self) |
||||
} |
||||
|
||||
/// Discard all batched writes in this transaction.
|
||||
pub fn rollback(&self) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_rollback(self.inner)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Record the state of the transaction for future calls to [`rollback_to_savepoint`].
|
||||
/// May be called multiple times to set multiple save points.
|
||||
///
|
||||
/// [`rollback_to_savepoint`]: Self::rollback_to_savepoint
|
||||
pub fn set_savepoint(&self) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_set_savepoint(self.inner); |
||||
} |
||||
} |
||||
|
||||
/// Undo all operations in this transaction since the most recent call to [`set_savepoint`]
|
||||
/// and removes the most recent [`set_savepoint`].
|
||||
///
|
||||
/// Returns error if there is no previous call to [`set_savepoint`].
|
||||
///
|
||||
/// [`set_savepoint`]: Self::set_savepoint
|
||||
pub fn rollback_to_savepoint(&self) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_rollback_to_savepoint(self.inner)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Get the bytes associated with a key value.
|
||||
///
|
||||
/// See [`get_cf_opt`] for details.
|
||||
///
|
||||
/// [`get_cf_opt`]: Self::get_cf_opt
|
||||
pub fn get<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_opt(key, &ReadOptions::default()) |
||||
} |
||||
|
||||
pub fn get_pinned<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_opt(key, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Get the bytes associated with a key value and the given column family.
|
||||
///
|
||||
/// See [`get_cf_opt`] for details.
|
||||
///
|
||||
/// [`get_cf_opt`]: Self::get_cf_opt
|
||||
pub fn get_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_cf_opt(cf, key, &ReadOptions::default()) |
||||
} |
||||
|
||||
pub fn get_pinned_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_cf_opt(cf, key, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Get the 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).
|
||||
///
|
||||
/// See [`get_for_update_cf_opt`] for details.
|
||||
///
|
||||
/// [`get_for_update_cf_opt`]: Self::get_for_update_cf_opt
|
||||
pub fn get_for_update<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
exclusive: bool, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_for_update_opt(key, exclusive, &ReadOptions::default()) |
||||
} |
||||
|
||||
pub fn get_pinned_for_update<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
exclusive: bool, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_for_update_opt(key, exclusive, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Get the key in the given column family 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).
|
||||
///
|
||||
/// See [`get_for_update_cf_opt`] for details.
|
||||
///
|
||||
/// [`get_for_update_cf_opt`]: Self::get_for_update_cf_opt
|
||||
pub fn get_for_update_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
exclusive: bool, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_for_update_cf_opt(cf, key, exclusive, &ReadOptions::default()) |
||||
} |
||||
|
||||
pub fn get_pinned_for_update_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
exclusive: bool, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_for_update_cf_opt(cf, key, exclusive, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value with read options.
|
||||
///
|
||||
/// See [`get_cf_opt`] for details.
|
||||
///
|
||||
/// [`get_cf_opt`]: Self::get_cf_opt
|
||||
pub fn get_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_opt(key, readopts) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
pub fn get_pinned_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
unsafe { |
||||
let val = ffi_try!(ffi::rocksdb_transaction_get_pinned( |
||||
self.inner, |
||||
readopts.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len(), |
||||
)); |
||||
if val.is_null() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(DBPinnableSlice::from_c(val))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Get the bytes associated with a key value and the given column family with read options.
|
||||
///
|
||||
/// This function will also read pending changes in this transaction.
|
||||
/// Currently, this function will return an error of the [`MergeInProgress`] kind
|
||||
/// if the most recent write to the queried key in this batch is a Merge.
|
||||
///
|
||||
/// [`MergeInProgress`]: crate::ErrorKind::MergeInProgress
|
||||
pub fn get_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_cf_opt(cf, key, readopts) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
pub fn get_pinned_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
unsafe { |
||||
let val = ffi_try!(ffi::rocksdb_transaction_get_pinned_cf( |
||||
self.inner, |
||||
readopts.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len(), |
||||
)); |
||||
if val.is_null() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(DBPinnableSlice::from_c(val))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Get the key with read options 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).
|
||||
///
|
||||
/// See [`get_for_update_cf_opt`] for details.
|
||||
///
|
||||
/// [`get_for_update_cf_opt`]: Self::get_for_update_cf_opt
|
||||
pub fn get_for_update_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
exclusive: bool, |
||||
opts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_for_update_opt(key, exclusive, opts) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
pub fn get_pinned_for_update_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
exclusive: bool, |
||||
opts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
unsafe { |
||||
let val = ffi_try!(ffi::rocksdb_transaction_get_pinned_for_update( |
||||
self.inner, |
||||
opts.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
u8::from(exclusive), |
||||
)); |
||||
if val.is_null() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(DBPinnableSlice::from_c(val))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Get the key in the given column family with read options
|
||||
/// 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).
|
||||
///
|
||||
/// Currently, this function will return an error of the [`MergeInProgress`]
|
||||
/// if the most recent write to the queried key in this batch is a Merge.
|
||||
///
|
||||
/// If this transaction was created by a [`TransactionDB`], it can return error of kind:
|
||||
/// * [`Busy`] if there is a write conflict.
|
||||
/// * [`TimedOut`] if a lock could not be acquired.
|
||||
/// * [`TryAgain`] if the memtable history size is not large enough.
|
||||
/// * [`MergeInProgress`] if merge operations cannot be resolved.
|
||||
/// * or other errors if this key could not be read.
|
||||
///
|
||||
/// If this transaction was created by an `[OptimisticTransactionDB]`, `get_for_update_opt`
|
||||
/// can cause [`commit`] to fail. Otherwise, it could return any error that could
|
||||
/// be returned by `[DB::get]`.
|
||||
///
|
||||
/// [`Busy`]: crate::ErrorKind::Busy
|
||||
/// [`TimedOut`]: crate::ErrorKind::TimedOut
|
||||
/// [`TryAgain`]: crate::ErrorKind::TryAgain
|
||||
/// [`MergeInProgress`]: crate::ErrorKind::MergeInProgress
|
||||
/// [`TransactionDB`]: crate::TransactionDB
|
||||
/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
|
||||
/// [`commit`]: Self::commit
|
||||
/// [`DB::get`]: crate::DB::get
|
||||
pub fn get_for_update_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
exclusive: bool, |
||||
opts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_for_update_cf_opt(cf, key, exclusive, opts) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
pub fn get_pinned_for_update_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
exclusive: bool, |
||||
opts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
unsafe { |
||||
let val = ffi_try!(ffi::rocksdb_transaction_get_pinned_for_update_cf( |
||||
self.inner, |
||||
opts.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
u8::from(exclusive), |
||||
)); |
||||
if val.is_null() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(DBPinnableSlice::from_c(val))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Return the values associated with the given keys.
|
||||
pub fn multi_get<K, I>(&self, keys: I) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = K>, |
||||
{ |
||||
self.multi_get_opt(keys, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Return the values associated with the given keys using read options.
|
||||
pub fn multi_get_opt<K, I>( |
||||
&self, |
||||
keys: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = K>, |
||||
{ |
||||
let (keys, keys_sizes): (Vec<Box<[u8]>>, Vec<_>) = keys |
||||
.into_iter() |
||||
.map(|k| (Box::from(k.as_ref()), k.as_ref().len())) |
||||
.unzip(); |
||||
let ptr_keys: Vec<_> = keys.iter().map(|k| k.as_ptr() as *const c_char).collect(); |
||||
|
||||
let mut values = vec![ptr::null_mut(); keys.len()]; |
||||
let mut values_sizes = vec![0_usize; keys.len()]; |
||||
let mut errors = vec![ptr::null_mut(); keys.len()]; |
||||
unsafe { |
||||
ffi::rocksdb_transaction_multi_get( |
||||
self.inner, |
||||
readopts.inner, |
||||
ptr_keys.len(), |
||||
ptr_keys.as_ptr(), |
||||
keys_sizes.as_ptr(), |
||||
values.as_mut_ptr(), |
||||
values_sizes.as_mut_ptr(), |
||||
errors.as_mut_ptr(), |
||||
); |
||||
} |
||||
|
||||
convert_values(values, values_sizes, errors) |
||||
} |
||||
|
||||
/// Return the values associated with the given keys and column families.
|
||||
pub fn multi_get_cf<'a, 'b: 'a, K, I, W>( |
||||
&'a self, |
||||
keys: I, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = (&'b W, K)>, |
||||
W: 'b + AsColumnFamilyRef, |
||||
{ |
||||
self.multi_get_cf_opt(keys, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Return the values associated with the given keys and column families using read options.
|
||||
pub fn multi_get_cf_opt<'a, 'b: 'a, K, I, W>( |
||||
&'a self, |
||||
keys: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = (&'b W, K)>, |
||||
W: 'b + AsColumnFamilyRef, |
||||
{ |
||||
let (cfs_and_keys, keys_sizes): (Vec<(_, Box<[u8]>)>, Vec<_>) = keys |
||||
.into_iter() |
||||
.map(|(cf, key)| ((cf, Box::from(key.as_ref())), key.as_ref().len())) |
||||
.unzip(); |
||||
let ptr_keys: Vec<_> = cfs_and_keys |
||||
.iter() |
||||
.map(|(_, k)| k.as_ptr() as *const c_char) |
||||
.collect(); |
||||
let ptr_cfs: Vec<_> = cfs_and_keys |
||||
.iter() |
||||
.map(|(c, _)| c.inner() as *const _) |
||||
.collect(); |
||||
|
||||
let mut values = vec![ptr::null_mut(); ptr_keys.len()]; |
||||
let mut values_sizes = vec![0_usize; ptr_keys.len()]; |
||||
let mut errors = vec![ptr::null_mut(); ptr_keys.len()]; |
||||
unsafe { |
||||
ffi::rocksdb_transaction_multi_get_cf( |
||||
self.inner, |
||||
readopts.inner, |
||||
ptr_cfs.as_ptr(), |
||||
ptr_keys.len(), |
||||
ptr_keys.as_ptr(), |
||||
keys_sizes.as_ptr(), |
||||
values.as_mut_ptr(), |
||||
values_sizes.as_mut_ptr(), |
||||
errors.as_mut_ptr(), |
||||
); |
||||
} |
||||
|
||||
convert_values(values, values_sizes, errors) |
||||
} |
||||
|
||||
/// Put the key value in default column family and do conflict checking on the key.
|
||||
///
|
||||
/// See [`put_cf`] for details.
|
||||
///
|
||||
/// [`put_cf`]: Self::put_cf
|
||||
pub fn put<K: AsRef<[u8]>, V: AsRef<[u8]>>(&self, key: K, value: V) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_put( |
||||
self.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t, |
||||
)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Put the key value in the given column famuly and do conflict checking on the key.
|
||||
///
|
||||
/// If this transaction was created by a [`TransactionDB`], it can return error of kind:
|
||||
/// * [`Busy`] if there is a write conflict.
|
||||
/// * [`TimedOut`] if a lock could not be acquired.
|
||||
/// * [`TryAgain`] if the memtable history size is not large enough.
|
||||
/// * [`MergeInProgress`] if merge operations cannot be resolved.
|
||||
/// * or other errors on unexpected failures.
|
||||
///
|
||||
/// [`Busy`]: crate::ErrorKind::Busy
|
||||
/// [`TimedOut`]: crate::ErrorKind::TimedOut
|
||||
/// [`TryAgain`]: crate::ErrorKind::TryAgain
|
||||
/// [`MergeInProgress`]: crate::ErrorKind::MergeInProgress
|
||||
/// [`TransactionDB`]: crate::TransactionDB
|
||||
/// [`OptimisticTransactionDB`]: crate::OptimisticTransactionDB
|
||||
pub fn put_cf<K: AsRef<[u8]>, V: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
value: V, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_put_cf( |
||||
self.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t, |
||||
)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Merge value with existing value of key, and also do conflict checking on the key.
|
||||
///
|
||||
/// See [`merge_cf`] for details.
|
||||
///
|
||||
/// [`merge_cf`]: Self::merge_cf
|
||||
pub fn merge<K: AsRef<[u8]>, V: AsRef<[u8]>>(&self, key: K, value: V) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_merge( |
||||
self.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t |
||||
)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Merge `value` with existing value of `key` in the given column family,
|
||||
/// and also do conflict checking on the key.
|
||||
///
|
||||
/// If this transaction was created by a [`TransactionDB`], it can return error of kind:
|
||||
/// * [`Busy`] if there is a write conflict.
|
||||
/// * [`TimedOut`] if a lock could not be acquired.
|
||||
/// * [`TryAgain`] if the memtable history size is not large enough.
|
||||
/// * [`MergeInProgress`] if merge operations cannot be resolved.
|
||||
/// * or other errors on unexpected failures.
|
||||
///
|
||||
/// [`Busy`]: crate::ErrorKind::Busy
|
||||
/// [`TimedOut`]: crate::ErrorKind::TimedOut
|
||||
/// [`TryAgain`]: crate::ErrorKind::TryAgain
|
||||
/// [`MergeInProgress`]: crate::ErrorKind::MergeInProgress
|
||||
/// [`TransactionDB`]: crate::TransactionDB
|
||||
pub fn merge_cf<K: AsRef<[u8]>, V: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
value: V, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_merge_cf( |
||||
self.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t |
||||
)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
/// Delete the key value if it exists and do conflict checking on the key.
|
||||
///
|
||||
/// See [`delete_cf`] for details.
|
||||
///
|
||||
/// [`delete_cf`]: Self::delete_cf
|
||||
pub fn delete<K: AsRef<[u8]>>(&self, key: K) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_delete( |
||||
self.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Delete the key value in the given column family and do conflict checking.
|
||||
///
|
||||
/// If this transaction was created by a [`TransactionDB`], it can return error of kind:
|
||||
/// * [`Busy`] if there is a write conflict.
|
||||
/// * [`TimedOut`] if a lock could not be acquired.
|
||||
/// * [`TryAgain`] if the memtable history size is not large enough.
|
||||
/// * [`MergeInProgress`] if merge operations cannot be resolved.
|
||||
/// * or other errors on unexpected failures.
|
||||
///
|
||||
/// [`Busy`]: crate::ErrorKind::Busy
|
||||
/// [`TimedOut`]: crate::ErrorKind::TimedOut
|
||||
/// [`TryAgain`]: crate::ErrorKind::TryAgain
|
||||
/// [`MergeInProgress`]: crate::ErrorKind::MergeInProgress
|
||||
/// [`TransactionDB`]: crate::TransactionDB
|
||||
pub fn delete_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_delete_cf( |
||||
self.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn iterator<'a: 'b, 'b>( |
||||
&'a self, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let readopts = ReadOptions::default(); |
||||
self.iterator_opt(mode, readopts) |
||||
} |
||||
|
||||
pub fn iterator_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
mode: IteratorMode, |
||||
readopts: ReadOptions, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
DBIteratorWithThreadMode::new(self, readopts, mode) |
||||
} |
||||
|
||||
/// Opens an iterator using the provided ReadOptions.
|
||||
/// This is used when you want to iterate over a specific ColumnFamily with a modified ReadOptions.
|
||||
pub fn iterator_cf_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
readopts: ReadOptions, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), readopts, mode) |
||||
} |
||||
|
||||
/// Opens an iterator with `set_total_order_seek` enabled.
|
||||
/// This must be used to iterate across prefixes when `set_memtable_factory` has been called
|
||||
/// with a Hash-based implementation.
|
||||
pub fn full_iterator<'a: 'b, 'b>( |
||||
&'a self, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_total_order_seek(true); |
||||
DBIteratorWithThreadMode::new(self, opts, mode) |
||||
} |
||||
|
||||
pub fn prefix_iterator<'a: 'b, 'b, P: AsRef<[u8]>>( |
||||
&'a self, |
||||
prefix: P, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_prefix_same_as_start(true); |
||||
DBIteratorWithThreadMode::new( |
||||
self, |
||||
opts, |
||||
IteratorMode::From(prefix.as_ref(), Direction::Forward), |
||||
) |
||||
} |
||||
|
||||
pub fn iterator_cf<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let opts = ReadOptions::default(); |
||||
DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts, mode) |
||||
} |
||||
|
||||
pub fn full_iterator_cf<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_total_order_seek(true); |
||||
DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts, mode) |
||||
} |
||||
|
||||
pub fn prefix_iterator_cf<'a, P: AsRef<[u8]>>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
prefix: P, |
||||
) -> DBIteratorWithThreadMode<'a, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_prefix_same_as_start(true); |
||||
DBIteratorWithThreadMode::<'a, Self>::new_cf( |
||||
self, |
||||
cf_handle.inner(), |
||||
opts, |
||||
IteratorMode::From(prefix.as_ref(), Direction::Forward), |
||||
) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the database, using the default read options
|
||||
pub fn raw_iterator<'a: 'b, 'b>(&'a self) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
let opts = ReadOptions::default(); |
||||
DBRawIteratorWithThreadMode::new(self, opts) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the given column family, using the default read options
|
||||
pub fn raw_iterator_cf<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
let opts = ReadOptions::default(); |
||||
DBRawIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the database, using the given read options
|
||||
pub fn raw_iterator_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
readopts: ReadOptions, |
||||
) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
DBRawIteratorWithThreadMode::new(self, readopts) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the given column family, using the given read options
|
||||
pub fn raw_iterator_cf_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
readopts: ReadOptions, |
||||
) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
DBRawIteratorWithThreadMode::new_cf(self, cf_handle.inner(), readopts) |
||||
} |
||||
|
||||
pub fn get_writebatch(&self) -> WriteBatchWithTransaction<true> { |
||||
unsafe { |
||||
let wi = ffi::rocksdb_transaction_get_writebatch_wi(self.inner); |
||||
let mut len: usize = 0; |
||||
let ptr = ffi::rocksdb_writebatch_wi_data(wi, &mut len as _); |
||||
let data = std::slice::from_raw_parts(ptr, len).to_owned(); |
||||
let writebatch = ffi::rocksdb_writebatch_create_from(data.as_ptr(), data.len()); |
||||
WriteBatchWithTransaction { inner: writebatch } |
||||
} |
||||
} |
||||
|
||||
pub fn rebuild_from_writebatch( |
||||
&self, |
||||
writebatch: &WriteBatchWithTransaction<true>, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transaction_rebuild_from_writebatch( |
||||
self.inner, |
||||
writebatch.inner |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
impl<'db, DB> Drop for Transaction<'db, DB> { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
ffi::rocksdb_transaction_destroy(self.inner); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,983 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
use std::{ |
||||
collections::BTreeMap, |
||||
ffi::CString, |
||||
fs, iter, |
||||
marker::PhantomData, |
||||
path::{Path, PathBuf}, |
||||
ptr, |
||||
sync::{Arc, Mutex}, |
||||
}; |
||||
|
||||
use crate::{ |
||||
column_family::UnboundColumnFamily, |
||||
db::{convert_values, DBAccess}, |
||||
db_options::OptionsMustOutliveDB, |
||||
ffi, |
||||
ffi_util::to_cpath, |
||||
AsColumnFamilyRef, BoundColumnFamily, ColumnFamily, ColumnFamilyDescriptor, |
||||
DBIteratorWithThreadMode, DBPinnableSlice, DBRawIteratorWithThreadMode, Direction, Error, |
||||
IteratorMode, MultiThreaded, Options, ReadOptions, SingleThreaded, SnapshotWithThreadMode, |
||||
ThreadMode, Transaction, TransactionDBOptions, TransactionOptions, WriteBatchWithTransaction, |
||||
WriteOptions, DB, DEFAULT_COLUMN_FAMILY_NAME, |
||||
}; |
||||
use ffi::rocksdb_transaction_t; |
||||
use libc::{c_char, c_int, c_void, size_t}; |
||||
|
||||
#[cfg(not(feature = "multi-threaded-cf"))] |
||||
type DefaultThreadMode = crate::SingleThreaded; |
||||
#[cfg(feature = "multi-threaded-cf")] |
||||
type DefaultThreadMode = crate::MultiThreaded; |
||||
|
||||
/// RocksDB TransactionDB.
|
||||
///
|
||||
/// Please read the official [guide](https://github.com/facebook/rocksdb/wiki/Transactions)
|
||||
/// to learn more about RocksDB TransactionDB.
|
||||
///
|
||||
/// The default thread mode for [`TransactionDB`] is [`SingleThreaded`]
|
||||
/// if feature `multi-threaded-cf` is not enabled.
|
||||
///
|
||||
/// ```
|
||||
/// use rocksdb::{DB, Options, TransactionDB, SingleThreaded};
|
||||
/// let path = "_path_for_transaction_db";
|
||||
/// {
|
||||
/// let db: TransactionDB = TransactionDB::open_default(path).unwrap();
|
||||
/// db.put(b"my key", b"my value").unwrap();
|
||||
///
|
||||
/// // create transaction
|
||||
/// let txn = db.transaction();
|
||||
/// txn.put(b"key2", b"value2");
|
||||
/// txn.put(b"key3", b"value3");
|
||||
/// txn.commit().unwrap();
|
||||
/// }
|
||||
/// let _ = DB::destroy(&Options::default(), path);
|
||||
/// ```
|
||||
///
|
||||
/// [`SingleThreaded`]: crate::SingleThreaded
|
||||
pub struct TransactionDB<T: ThreadMode = DefaultThreadMode> { |
||||
pub(crate) inner: *mut ffi::rocksdb_transactiondb_t, |
||||
cfs: T, |
||||
path: PathBuf, |
||||
// prepared 2pc transactions.
|
||||
prepared: Mutex<Vec<*mut rocksdb_transaction_t>>, |
||||
_outlive: Vec<OptionsMustOutliveDB>, |
||||
} |
||||
|
||||
unsafe impl<T: ThreadMode> Send for TransactionDB<T> {} |
||||
unsafe impl<T: ThreadMode> Sync for TransactionDB<T> {} |
||||
|
||||
impl<T: ThreadMode> DBAccess for TransactionDB<T> { |
||||
unsafe fn create_snapshot(&self) -> *const ffi::rocksdb_snapshot_t { |
||||
ffi::rocksdb_transactiondb_create_snapshot(self.inner) |
||||
} |
||||
|
||||
unsafe fn release_snapshot(&self, snapshot: *const ffi::rocksdb_snapshot_t) { |
||||
ffi::rocksdb_transactiondb_release_snapshot(self.inner, snapshot); |
||||
} |
||||
|
||||
unsafe fn create_iterator(&self, readopts: &ReadOptions) -> *mut ffi::rocksdb_iterator_t { |
||||
ffi::rocksdb_transactiondb_create_iterator(self.inner, readopts.inner) |
||||
} |
||||
|
||||
unsafe fn create_iterator_cf( |
||||
&self, |
||||
cf_handle: *mut ffi::rocksdb_column_family_handle_t, |
||||
readopts: &ReadOptions, |
||||
) -> *mut ffi::rocksdb_iterator_t { |
||||
ffi::rocksdb_transactiondb_create_iterator_cf(self.inner, readopts.inner, cf_handle) |
||||
} |
||||
|
||||
fn get_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_opt(key, readopts) |
||||
} |
||||
|
||||
fn get_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_cf_opt(cf, key, readopts) |
||||
} |
||||
|
||||
fn get_pinned_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_opt(key, readopts) |
||||
} |
||||
|
||||
fn get_pinned_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_cf_opt(cf, key, readopts) |
||||
} |
||||
|
||||
fn multi_get_opt<K, I>( |
||||
&self, |
||||
keys: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = K>, |
||||
{ |
||||
self.multi_get_opt(keys, readopts) |
||||
} |
||||
|
||||
fn multi_get_cf_opt<'b, K, I, W>( |
||||
&self, |
||||
keys_cf: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = (&'b W, K)>, |
||||
W: AsColumnFamilyRef + 'b, |
||||
{ |
||||
self.multi_get_cf_opt(keys_cf, readopts) |
||||
} |
||||
} |
||||
|
||||
impl<T: ThreadMode> TransactionDB<T> { |
||||
/// Opens a database with default options.
|
||||
pub fn open_default<P: AsRef<Path>>(path: P) -> Result<Self, Error> { |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
let txn_db_opts = TransactionDBOptions::default(); |
||||
Self::open(&opts, &txn_db_opts, path) |
||||
} |
||||
|
||||
/// Opens the database with the specified options.
|
||||
pub fn open<P: AsRef<Path>>( |
||||
opts: &Options, |
||||
txn_db_opts: &TransactionDBOptions, |
||||
path: P, |
||||
) -> Result<Self, Error> { |
||||
Self::open_cf(opts, txn_db_opts, path, None::<&str>) |
||||
} |
||||
|
||||
/// Opens a database with the given database options and column family names.
|
||||
///
|
||||
/// Column families opened using this function will be created with default `Options`.
|
||||
pub fn open_cf<P, I, N>( |
||||
opts: &Options, |
||||
txn_db_opts: &TransactionDBOptions, |
||||
path: P, |
||||
cfs: I, |
||||
) -> Result<Self, Error> |
||||
where |
||||
P: AsRef<Path>, |
||||
I: IntoIterator<Item = N>, |
||||
N: AsRef<str>, |
||||
{ |
||||
let cfs = cfs |
||||
.into_iter() |
||||
.map(|name| ColumnFamilyDescriptor::new(name.as_ref(), Options::default())); |
||||
|
||||
Self::open_cf_descriptors_internal(opts, txn_db_opts, path, cfs) |
||||
} |
||||
|
||||
/// Opens a database with the given database options and column family descriptors.
|
||||
pub fn open_cf_descriptors<P, I>( |
||||
opts: &Options, |
||||
txn_db_opts: &TransactionDBOptions, |
||||
path: P, |
||||
cfs: I, |
||||
) -> Result<Self, Error> |
||||
where |
||||
P: AsRef<Path>, |
||||
I: IntoIterator<Item = ColumnFamilyDescriptor>, |
||||
{ |
||||
Self::open_cf_descriptors_internal(opts, txn_db_opts, path, cfs) |
||||
} |
||||
|
||||
/// Internal implementation for opening RocksDB.
|
||||
fn open_cf_descriptors_internal<P, I>( |
||||
opts: &Options, |
||||
txn_db_opts: &TransactionDBOptions, |
||||
path: P, |
||||
cfs: I, |
||||
) -> Result<Self, Error> |
||||
where |
||||
P: AsRef<Path>, |
||||
I: IntoIterator<Item = ColumnFamilyDescriptor>, |
||||
{ |
||||
let cfs: Vec<_> = cfs.into_iter().collect(); |
||||
let outlive = iter::once(opts.outlive.clone()) |
||||
.chain(cfs.iter().map(|cf| cf.options.outlive.clone())) |
||||
.collect(); |
||||
|
||||
let cpath = to_cpath(&path)?; |
||||
|
||||
if let Err(e) = fs::create_dir_all(&path) { |
||||
return Err(Error::new(format!( |
||||
"Failed to create RocksDB directory: `{:?}`.", |
||||
e |
||||
))); |
||||
} |
||||
|
||||
let db: *mut ffi::rocksdb_transactiondb_t; |
||||
let mut cf_map = BTreeMap::new(); |
||||
|
||||
if cfs.is_empty() { |
||||
db = Self::open_raw(opts, txn_db_opts, &cpath)?; |
||||
} else { |
||||
let mut cfs_v = cfs; |
||||
// Always open the default column family.
|
||||
if !cfs_v.iter().any(|cf| cf.name == DEFAULT_COLUMN_FAMILY_NAME) { |
||||
cfs_v.push(ColumnFamilyDescriptor { |
||||
name: String::from(DEFAULT_COLUMN_FAMILY_NAME), |
||||
options: Options::default(), |
||||
}); |
||||
} |
||||
// We need to store our CStrings in an intermediate vector
|
||||
// so that their pointers remain valid.
|
||||
let c_cfs: Vec<CString> = cfs_v |
||||
.iter() |
||||
.map(|cf| CString::new(cf.name.as_bytes()).unwrap()) |
||||
.collect(); |
||||
|
||||
let cfnames: Vec<_> = c_cfs.iter().map(|cf| cf.as_ptr()).collect(); |
||||
|
||||
// These handles will be populated by DB.
|
||||
let mut cfhandles: Vec<_> = cfs_v.iter().map(|_| ptr::null_mut()).collect(); |
||||
|
||||
let cfopts: Vec<_> = cfs_v |
||||
.iter() |
||||
.map(|cf| cf.options.inner as *const _) |
||||
.collect(); |
||||
|
||||
db = Self::open_cf_raw( |
||||
opts, |
||||
txn_db_opts, |
||||
&cpath, |
||||
&cfs_v, |
||||
&cfnames, |
||||
&cfopts, |
||||
&mut cfhandles, |
||||
)?; |
||||
|
||||
for handle in &cfhandles { |
||||
if handle.is_null() { |
||||
return Err(Error::new( |
||||
"Received null column family handle from DB.".to_owned(), |
||||
)); |
||||
} |
||||
} |
||||
|
||||
for (cf_desc, inner) in cfs_v.iter().zip(cfhandles) { |
||||
cf_map.insert(cf_desc.name.clone(), inner); |
||||
} |
||||
} |
||||
|
||||
if db.is_null() { |
||||
return Err(Error::new("Could not initialize database.".to_owned())); |
||||
} |
||||
|
||||
let prepared = unsafe { |
||||
let mut cnt = 0; |
||||
let ptr = ffi::rocksdb_transactiondb_get_prepared_transactions(db, &mut cnt); |
||||
let mut vec = vec![std::ptr::null_mut(); cnt]; |
||||
std::ptr::copy_nonoverlapping(ptr, vec.as_mut_ptr(), cnt); |
||||
if !ptr.is_null() { |
||||
ffi::rocksdb_free(ptr as *mut c_void); |
||||
} |
||||
vec |
||||
}; |
||||
|
||||
Ok(TransactionDB { |
||||
inner: db, |
||||
cfs: T::new_cf_map_internal(cf_map), |
||||
path: path.as_ref().to_path_buf(), |
||||
prepared: Mutex::new(prepared), |
||||
_outlive: outlive, |
||||
}) |
||||
} |
||||
|
||||
fn open_raw( |
||||
opts: &Options, |
||||
txn_db_opts: &TransactionDBOptions, |
||||
cpath: &CString, |
||||
) -> Result<*mut ffi::rocksdb_transactiondb_t, Error> { |
||||
unsafe { |
||||
let db = ffi_try!(ffi::rocksdb_transactiondb_open( |
||||
opts.inner, |
||||
txn_db_opts.inner, |
||||
cpath.as_ptr() |
||||
)); |
||||
Ok(db) |
||||
} |
||||
} |
||||
|
||||
fn open_cf_raw( |
||||
opts: &Options, |
||||
txn_db_opts: &TransactionDBOptions, |
||||
cpath: &CString, |
||||
cfs_v: &[ColumnFamilyDescriptor], |
||||
cfnames: &[*const c_char], |
||||
cfopts: &[*const ffi::rocksdb_options_t], |
||||
cfhandles: &mut [*mut ffi::rocksdb_column_family_handle_t], |
||||
) -> Result<*mut ffi::rocksdb_transactiondb_t, Error> { |
||||
unsafe { |
||||
let db = ffi_try!(ffi::rocksdb_transactiondb_open_column_families( |
||||
opts.inner, |
||||
txn_db_opts.inner, |
||||
cpath.as_ptr(), |
||||
cfs_v.len() as c_int, |
||||
cfnames.as_ptr(), |
||||
cfopts.as_ptr(), |
||||
cfhandles.as_mut_ptr(), |
||||
)); |
||||
Ok(db) |
||||
} |
||||
} |
||||
|
||||
fn create_inner_cf_handle( |
||||
&self, |
||||
name: &str, |
||||
opts: &Options, |
||||
) -> Result<*mut ffi::rocksdb_column_family_handle_t, Error> { |
||||
let cf_name = if let Ok(c) = CString::new(name.as_bytes()) { |
||||
c |
||||
} else { |
||||
return Err(Error::new( |
||||
"Failed to convert path to CString when creating cf".to_owned(), |
||||
)); |
||||
}; |
||||
Ok(unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_create_column_family( |
||||
self.inner, |
||||
opts.inner, |
||||
cf_name.as_ptr(), |
||||
)) |
||||
}) |
||||
} |
||||
|
||||
pub fn list_cf<P: AsRef<Path>>(opts: &Options, path: P) -> Result<Vec<String>, Error> { |
||||
DB::list_cf(opts, path) |
||||
} |
||||
|
||||
pub fn destroy<P: AsRef<Path>>(opts: &Options, path: P) -> Result<(), Error> { |
||||
DB::destroy(opts, path) |
||||
} |
||||
|
||||
pub fn repair<P: AsRef<Path>>(opts: &Options, path: P) -> Result<(), Error> { |
||||
DB::repair(opts, path) |
||||
} |
||||
|
||||
pub fn path(&self) -> &Path { |
||||
self.path.as_path() |
||||
} |
||||
|
||||
/// Creates a transaction with default options.
|
||||
pub fn transaction(&self) -> Transaction<Self> { |
||||
self.transaction_opt(&WriteOptions::default(), &TransactionOptions::default()) |
||||
} |
||||
|
||||
/// Creates a transaction with options.
|
||||
pub fn transaction_opt<'a>( |
||||
&'a self, |
||||
write_opts: &WriteOptions, |
||||
txn_opts: &TransactionOptions, |
||||
) -> Transaction<'a, Self> { |
||||
Transaction { |
||||
inner: unsafe { |
||||
ffi::rocksdb_transaction_begin( |
||||
self.inner, |
||||
write_opts.inner, |
||||
txn_opts.inner, |
||||
std::ptr::null_mut(), |
||||
) |
||||
}, |
||||
_marker: PhantomData::default(), |
||||
} |
||||
} |
||||
|
||||
/// Get all prepared transactions for recovery.
|
||||
///
|
||||
/// This function is expected to call once after open database.
|
||||
/// User should commit or rollback all transactions before start other transactions.
|
||||
pub fn prepared_transactions(&self) -> Vec<Transaction<Self>> { |
||||
self.prepared |
||||
.lock() |
||||
.unwrap() |
||||
.drain(0..) |
||||
.map(|inner| Transaction { |
||||
inner, |
||||
_marker: PhantomData::default(), |
||||
}) |
||||
.collect() |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value.
|
||||
pub fn get<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned(key).map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value and the given column family.
|
||||
pub fn get_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_cf(cf, key) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value with read options.
|
||||
pub fn get_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_opt(key, readopts) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value and the given column family with read options.
|
||||
pub fn get_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<Vec<u8>>, Error> { |
||||
self.get_pinned_cf_opt(cf, key, readopts) |
||||
.map(|x| x.map(|v| v.as_ref().to_vec())) |
||||
} |
||||
|
||||
pub fn get_pinned<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_opt(key, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value and the given column family.
|
||||
pub fn get_pinned_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
self.get_pinned_cf_opt(cf, key, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value with read options.
|
||||
pub fn get_pinned_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
let key = key.as_ref(); |
||||
unsafe { |
||||
let val = ffi_try!(ffi::rocksdb_transactiondb_get_pinned( |
||||
self.inner, |
||||
readopts.inner, |
||||
key.as_ptr() as *const c_char, |
||||
key.len() as size_t, |
||||
)); |
||||
if val.is_null() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(DBPinnableSlice::from_c(val))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Returns the bytes associated with a key value and the given column family with read options.
|
||||
pub fn get_pinned_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
readopts: &ReadOptions, |
||||
) -> Result<Option<DBPinnableSlice>, Error> { |
||||
unsafe { |
||||
let val = ffi_try!(ffi::rocksdb_transactiondb_get_pinned_cf( |
||||
self.inner, |
||||
readopts.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
)); |
||||
if val.is_null() { |
||||
Ok(None) |
||||
} else { |
||||
Ok(Some(DBPinnableSlice::from_c(val))) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Return the values associated with the given keys.
|
||||
pub fn multi_get<K, I>(&self, keys: I) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = K>, |
||||
{ |
||||
self.multi_get_opt(keys, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Return the values associated with the given keys using read options.
|
||||
pub fn multi_get_opt<K, I>( |
||||
&self, |
||||
keys: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = K>, |
||||
{ |
||||
let (keys, keys_sizes): (Vec<Box<[u8]>>, Vec<_>) = keys |
||||
.into_iter() |
||||
.map(|k| (Box::from(k.as_ref()), k.as_ref().len())) |
||||
.unzip(); |
||||
let ptr_keys: Vec<_> = keys.iter().map(|k| k.as_ptr() as *const c_char).collect(); |
||||
|
||||
let mut values = vec![ptr::null_mut(); keys.len()]; |
||||
let mut values_sizes = vec![0_usize; keys.len()]; |
||||
let mut errors = vec![ptr::null_mut(); keys.len()]; |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_multi_get( |
||||
self.inner, |
||||
readopts.inner, |
||||
ptr_keys.len(), |
||||
ptr_keys.as_ptr(), |
||||
keys_sizes.as_ptr(), |
||||
values.as_mut_ptr(), |
||||
values_sizes.as_mut_ptr(), |
||||
errors.as_mut_ptr(), |
||||
); |
||||
} |
||||
|
||||
convert_values(values, values_sizes, errors) |
||||
} |
||||
|
||||
/// Return the values associated with the given keys and column families.
|
||||
pub fn multi_get_cf<'a, 'b: 'a, K, I, W>( |
||||
&'a self, |
||||
keys: I, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = (&'b W, K)>, |
||||
W: 'b + AsColumnFamilyRef, |
||||
{ |
||||
self.multi_get_cf_opt(keys, &ReadOptions::default()) |
||||
} |
||||
|
||||
/// Return the values associated with the given keys and column families using read options.
|
||||
pub fn multi_get_cf_opt<'a, 'b: 'a, K, I, W>( |
||||
&'a self, |
||||
keys: I, |
||||
readopts: &ReadOptions, |
||||
) -> Vec<Result<Option<Vec<u8>>, Error>> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
I: IntoIterator<Item = (&'b W, K)>, |
||||
W: 'b + AsColumnFamilyRef, |
||||
{ |
||||
let (cfs_and_keys, keys_sizes): (Vec<(_, Box<[u8]>)>, Vec<_>) = keys |
||||
.into_iter() |
||||
.map(|(cf, key)| ((cf, Box::from(key.as_ref())), key.as_ref().len())) |
||||
.unzip(); |
||||
let ptr_keys: Vec<_> = cfs_and_keys |
||||
.iter() |
||||
.map(|(_, k)| k.as_ptr() as *const c_char) |
||||
.collect(); |
||||
let ptr_cfs: Vec<_> = cfs_and_keys |
||||
.iter() |
||||
.map(|(c, _)| c.inner() as *const _) |
||||
.collect(); |
||||
|
||||
let mut values = vec![ptr::null_mut(); ptr_keys.len()]; |
||||
let mut values_sizes = vec![0_usize; ptr_keys.len()]; |
||||
let mut errors = vec![ptr::null_mut(); ptr_keys.len()]; |
||||
unsafe { |
||||
ffi::rocksdb_transactiondb_multi_get_cf( |
||||
self.inner, |
||||
readopts.inner, |
||||
ptr_cfs.as_ptr(), |
||||
ptr_keys.len(), |
||||
ptr_keys.as_ptr(), |
||||
keys_sizes.as_ptr(), |
||||
values.as_mut_ptr(), |
||||
values_sizes.as_mut_ptr(), |
||||
errors.as_mut_ptr(), |
||||
); |
||||
} |
||||
|
||||
convert_values(values, values_sizes, errors) |
||||
} |
||||
|
||||
pub fn put<K, V>(&self, key: K, value: V) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
self.put_opt(key, value, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn put_cf<K, V>(&self, cf: &impl AsColumnFamilyRef, key: K, value: V) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
self.put_cf_opt(cf, key, value, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn put_opt<K, V>(&self, key: K, value: V, writeopts: &WriteOptions) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_put( |
||||
self.inner, |
||||
writeopts.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn put_cf_opt<K, V>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
value: V, |
||||
writeopts: &WriteOptions, |
||||
) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_put_cf( |
||||
self.inner, |
||||
writeopts.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn write(&self, batch: WriteBatchWithTransaction<true>) -> Result<(), Error> { |
||||
self.write_opt(batch, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn write_opt( |
||||
&self, |
||||
batch: WriteBatchWithTransaction<true>, |
||||
writeopts: &WriteOptions, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_write( |
||||
self.inner, |
||||
writeopts.inner, |
||||
batch.inner |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn merge<K, V>(&self, key: K, value: V) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
self.merge_opt(key, value, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn merge_cf<K, V>(&self, cf: &impl AsColumnFamilyRef, key: K, value: V) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
self.merge_cf_opt(cf, key, value, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn merge_opt<K, V>(&self, key: K, value: V, writeopts: &WriteOptions) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_merge( |
||||
self.inner, |
||||
writeopts.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t, |
||||
)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub fn merge_cf_opt<K, V>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
value: V, |
||||
writeopts: &WriteOptions, |
||||
) -> Result<(), Error> |
||||
where |
||||
K: AsRef<[u8]>, |
||||
V: AsRef<[u8]>, |
||||
{ |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_merge_cf( |
||||
self.inner, |
||||
writeopts.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
value.as_ref().as_ptr() as *const c_char, |
||||
value.as_ref().len() as size_t, |
||||
)); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub fn delete<K: AsRef<[u8]>>(&self, key: K) -> Result<(), Error> { |
||||
self.delete_opt(key, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn delete_cf<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
) -> Result<(), Error> { |
||||
self.delete_cf_opt(cf, key, &WriteOptions::default()) |
||||
} |
||||
|
||||
pub fn delete_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
key: K, |
||||
writeopts: &WriteOptions, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_delete( |
||||
self.inner, |
||||
writeopts.inner, |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn delete_cf_opt<K: AsRef<[u8]>>( |
||||
&self, |
||||
cf: &impl AsColumnFamilyRef, |
||||
key: K, |
||||
writeopts: &WriteOptions, |
||||
) -> Result<(), Error> { |
||||
unsafe { |
||||
ffi_try!(ffi::rocksdb_transactiondb_delete_cf( |
||||
self.inner, |
||||
writeopts.inner, |
||||
cf.inner(), |
||||
key.as_ref().as_ptr() as *const c_char, |
||||
key.as_ref().len() as size_t, |
||||
)); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn iterator<'a: 'b, 'b>( |
||||
&'a self, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let readopts = ReadOptions::default(); |
||||
self.iterator_opt(mode, readopts) |
||||
} |
||||
|
||||
pub fn iterator_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
mode: IteratorMode, |
||||
readopts: ReadOptions, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
DBIteratorWithThreadMode::new(self, readopts, mode) |
||||
} |
||||
|
||||
/// Opens an iterator using the provided ReadOptions.
|
||||
/// This is used when you want to iterate over a specific ColumnFamily with a modified ReadOptions
|
||||
pub fn iterator_cf_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
readopts: ReadOptions, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), readopts, mode) |
||||
} |
||||
|
||||
/// Opens an iterator with `set_total_order_seek` enabled.
|
||||
/// This must be used to iterate across prefixes when `set_memtable_factory` has been called
|
||||
/// with a Hash-based implementation.
|
||||
pub fn full_iterator<'a: 'b, 'b>( |
||||
&'a self, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_total_order_seek(true); |
||||
DBIteratorWithThreadMode::new(self, opts, mode) |
||||
} |
||||
|
||||
pub fn prefix_iterator<'a: 'b, 'b, P: AsRef<[u8]>>( |
||||
&'a self, |
||||
prefix: P, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_prefix_same_as_start(true); |
||||
DBIteratorWithThreadMode::new( |
||||
self, |
||||
opts, |
||||
IteratorMode::From(prefix.as_ref(), Direction::Forward), |
||||
) |
||||
} |
||||
|
||||
pub fn iterator_cf<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let opts = ReadOptions::default(); |
||||
DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts, mode) |
||||
} |
||||
|
||||
pub fn full_iterator_cf<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
mode: IteratorMode, |
||||
) -> DBIteratorWithThreadMode<'b, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_total_order_seek(true); |
||||
DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts, mode) |
||||
} |
||||
|
||||
pub fn prefix_iterator_cf<'a, P: AsRef<[u8]>>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
prefix: P, |
||||
) -> DBIteratorWithThreadMode<'a, Self> { |
||||
let mut opts = ReadOptions::default(); |
||||
opts.set_prefix_same_as_start(true); |
||||
DBIteratorWithThreadMode::<'a, Self>::new_cf( |
||||
self, |
||||
cf_handle.inner(), |
||||
opts, |
||||
IteratorMode::From(prefix.as_ref(), Direction::Forward), |
||||
) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the database, using the default read options
|
||||
pub fn raw_iterator<'a: 'b, 'b>(&'a self) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
let opts = ReadOptions::default(); |
||||
DBRawIteratorWithThreadMode::new(self, opts) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the given column family, using the default read options
|
||||
pub fn raw_iterator_cf<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
let opts = ReadOptions::default(); |
||||
DBRawIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the database, using the given read options
|
||||
pub fn raw_iterator_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
readopts: ReadOptions, |
||||
) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
DBRawIteratorWithThreadMode::new(self, readopts) |
||||
} |
||||
|
||||
/// Opens a raw iterator over the given column family, using the given read options
|
||||
pub fn raw_iterator_cf_opt<'a: 'b, 'b>( |
||||
&'a self, |
||||
cf_handle: &impl AsColumnFamilyRef, |
||||
readopts: ReadOptions, |
||||
) -> DBRawIteratorWithThreadMode<'b, Self> { |
||||
DBRawIteratorWithThreadMode::new_cf(self, cf_handle.inner(), readopts) |
||||
} |
||||
|
||||
pub fn snapshot(&self) -> SnapshotWithThreadMode<Self> { |
||||
SnapshotWithThreadMode::<Self>::new(self) |
||||
} |
||||
} |
||||
|
||||
impl TransactionDB<SingleThreaded> { |
||||
/// Creates column family with given name and options.
|
||||
pub fn create_cf<N: AsRef<str>>(&mut self, name: N, opts: &Options) -> Result<(), Error> { |
||||
let inner = self.create_inner_cf_handle(name.as_ref(), opts)?; |
||||
self.cfs |
||||
.cfs |
||||
.insert(name.as_ref().to_string(), ColumnFamily { inner }); |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Returns the underlying column family handle.
|
||||
pub fn cf_handle(&self, name: &str) -> Option<&ColumnFamily> { |
||||
self.cfs.cfs.get(name) |
||||
} |
||||
} |
||||
|
||||
impl TransactionDB<MultiThreaded> { |
||||
/// Creates column family with given name and options.
|
||||
pub fn create_cf<N: AsRef<str>>(&self, name: N, opts: &Options) -> Result<(), Error> { |
||||
let inner = self.create_inner_cf_handle(name.as_ref(), opts)?; |
||||
self.cfs.cfs.write().unwrap().insert( |
||||
name.as_ref().to_string(), |
||||
Arc::new(UnboundColumnFamily { inner }), |
||||
); |
||||
Ok(()) |
||||
} |
||||
|
||||
/// Returns the underlying column family handle.
|
||||
pub fn cf_handle(&self, name: &str) -> Option<Arc<BoundColumnFamily>> { |
||||
self.cfs |
||||
.cfs |
||||
.read() |
||||
.unwrap() |
||||
.get(name) |
||||
.cloned() |
||||
.map(UnboundColumnFamily::bound_column_family) |
||||
} |
||||
} |
||||
|
||||
impl<T: ThreadMode> Drop for TransactionDB<T> { |
||||
fn drop(&mut self) { |
||||
unsafe { |
||||
self.prepared_transactions().clear(); |
||||
self.cfs.drop_all_cfs_internal(); |
||||
ffi::rocksdb_transactiondb_close(self.inner); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
use rocksdb::{TransactionDB, SingleThreaded}; |
||||
|
||||
fn main() { |
||||
let db = TransactionDB::<SingleThreaded>::open_default("foo").unwrap(); |
||||
let _snapshot = { |
||||
let txn = db.transaction(); |
||||
txn.snapshot() |
||||
}; |
||||
} |
@ -0,0 +1,10 @@ |
||||
error[E0597]: `txn` does not live long enough |
||||
--> tests/fail/snapshot_outlive_transaction.rs:7:9 |
||||
| |
||||
5 | let _snapshot = { |
||||
| --------- borrow later stored here |
||||
6 | let txn = db.transaction(); |
||||
7 | txn.snapshot() |
||||
| ^^^^^^^^^^^^^^ borrowed value does not live long enough |
||||
8 | }; |
||||
| - `txn` dropped here while still borrowed |
@ -0,0 +1,8 @@ |
||||
use rocksdb::{TransactionDB, SingleThreaded}; |
||||
|
||||
fn main() { |
||||
let _snapshot = { |
||||
let db = TransactionDB::<SingleThreaded>::open_default("foo").unwrap(); |
||||
db.snapshot() |
||||
}; |
||||
} |
@ -0,0 +1,10 @@ |
||||
error[E0597]: `db` does not live long enough |
||||
--> tests/fail/snapshot_outlive_transaction_db.rs:6:9 |
||||
| |
||||
4 | let _snapshot = { |
||||
| --------- borrow later stored here |
||||
5 | let db = TransactionDB::<SingleThreaded>::open_default("foo").unwrap(); |
||||
6 | db.snapshot() |
||||
| ^^^^^^^^^^^^^ borrowed value does not live long enough |
||||
7 | }; |
||||
| - `db` dropped here while still borrowed |
@ -0,0 +1,8 @@ |
||||
use rocksdb::{TransactionDB, SingleThreaded}; |
||||
|
||||
fn main() { |
||||
let _txn = { |
||||
let db = TransactionDB::<SingleThreaded>::open_default("foo").unwrap(); |
||||
db.transaction() |
||||
}; |
||||
} |
@ -0,0 +1,10 @@ |
||||
error[E0597]: `db` does not live long enough |
||||
--> tests/fail/transaction_outlive_transaction_db.rs:6:9 |
||||
| |
||||
4 | let _txn = { |
||||
| ---- borrow later stored here |
||||
5 | let db = TransactionDB::<SingleThreaded>::open_default("foo").unwrap(); |
||||
6 | db.transaction() |
||||
| ^^^^^^^^^^^^^^^^ borrowed value does not live long enough |
||||
7 | }; |
||||
| - `db` dropped here while still borrowed |
@ -0,0 +1,577 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
mod util; |
||||
|
||||
use rocksdb::{ |
||||
CuckooTableOptions, DBAccess, Direction, Error, ErrorKind, IteratorMode, |
||||
OptimisticTransactionDB, OptimisticTransactionOptions, Options, ReadOptions, SingleThreaded, |
||||
SliceTransform, SnapshotWithThreadMode, WriteBatchWithTransaction, WriteOptions, DB, |
||||
}; |
||||
use util::DBPath; |
||||
|
||||
#[test] |
||||
fn open_default() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_open_default"); |
||||
{ |
||||
let db: OptimisticTransactionDB<SingleThreaded> = |
||||
OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
|
||||
assert!(db.put(b"k1", b"v1111").is_ok()); |
||||
|
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1"); |
||||
|
||||
assert_eq!(r.unwrap().unwrap(), b"v1111"); |
||||
assert!(db.delete(b"k1").is_ok()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn open_cf() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_open_cf"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let db: OptimisticTransactionDB<SingleThreaded> = |
||||
OptimisticTransactionDB::open_cf(&opts, &path, ["cf1", "cf2"]).unwrap(); |
||||
|
||||
let cf1 = db.cf_handle("cf1").unwrap(); |
||||
let cf2 = db.cf_handle("cf2").unwrap(); |
||||
|
||||
db.put(b"k0", b"v0").unwrap(); |
||||
db.put_cf(&cf1, b"k1", b"v1").unwrap(); |
||||
db.put_cf(&cf2, b"k2", b"v2").unwrap(); |
||||
|
||||
assert_eq!(db.get(b"k0").unwrap().unwrap(), b"v0"); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
assert!(db.get(b"k2").unwrap().is_none()); |
||||
|
||||
assert!(db.get_cf(&cf1, b"k0").unwrap().is_none()); |
||||
assert_eq!(db.get_cf(&cf1, b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(db.get_cf(&cf1, b"k2").unwrap().is_none()); |
||||
|
||||
assert!(db.get_cf(&cf2, b"k0").unwrap().is_none()); |
||||
assert!(db.get_cf(&cf2, b"k1").unwrap().is_none()); |
||||
assert_eq!(db.get_cf(&cf2, b"k2").unwrap().unwrap(), b"v2"); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn multi_get() { |
||||
let path = DBPath::new("_rust_rocksdb_multi_get"); |
||||
|
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
let initial_snap = db.snapshot(); |
||||
db.put(b"k1", b"v1").unwrap(); |
||||
let k1_snap = db.snapshot(); |
||||
db.put(b"k2", b"v2").unwrap(); |
||||
|
||||
let _ = db.multi_get(&[b"k0"; 40]); |
||||
|
||||
let assert_values = |values: Vec<_>| { |
||||
assert_eq!(3, values.len()); |
||||
assert_eq!(values[0], None); |
||||
assert_eq!(values[1], Some(b"v1".to_vec())); |
||||
assert_eq!(values[2], Some(b"v2".to_vec())); |
||||
}; |
||||
|
||||
let values = db |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
|
||||
let values = DBAccess::multi_get_opt(&db, &[b"k0", b"k1", b"k2"], &Default::default()) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
|
||||
let values = db |
||||
.snapshot() |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
|
||||
let none_values = initial_snap |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_eq!(none_values, vec![None; 3]); |
||||
|
||||
let k1_only = k1_snap |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_eq!(k1_only, vec![None, Some(b"v1".to_vec()), None]); |
||||
|
||||
let txn = db.transaction(); |
||||
let values = txn |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn multi_get_cf() { |
||||
let path = DBPath::new("_rust_rocksdb_multi_get_cf"); |
||||
|
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let db: OptimisticTransactionDB = |
||||
OptimisticTransactionDB::open_cf(&opts, &path, &["cf0", "cf1", "cf2"]).unwrap(); |
||||
|
||||
let cf0 = db.cf_handle("cf0").unwrap(); |
||||
|
||||
let cf1 = db.cf_handle("cf1").unwrap(); |
||||
db.put_cf(&cf1, b"k1", b"v1").unwrap(); |
||||
|
||||
let cf2 = db.cf_handle("cf2").unwrap(); |
||||
db.put_cf(&cf2, b"k2", b"v2").unwrap(); |
||||
|
||||
let values = db |
||||
.multi_get_cf(vec![(&cf0, b"k0"), (&cf1, b"k1"), (&cf2, b"k2")]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
assert_eq!(3, values.len()); |
||||
assert_eq!(values[0], None); |
||||
assert_eq!(values[1], Some(b"v1".to_vec())); |
||||
assert_eq!(values[2], Some(b"v2".to_vec())); |
||||
|
||||
let txn = db.transaction(); |
||||
let values = txn |
||||
.multi_get_cf(vec![(&cf0, b"k0"), (&cf1, b"k1"), (&cf2, b"k2")]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_eq!(3, values.len()); |
||||
assert_eq!(values[0], None); |
||||
assert_eq!(values[1], Some(b"v1".to_vec())); |
||||
assert_eq!(values[2], Some(b"v2".to_vec())); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn destroy_on_open() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_destroy_on_open"); |
||||
let _db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
let opts = Options::default(); |
||||
// The TransactionDB will still be open when we try to destroy it and the lock should fail.
|
||||
match DB::destroy(&opts, &path) { |
||||
Err(s) => { |
||||
let message = s.to_string(); |
||||
assert_eq!(s.kind(), ErrorKind::IOError); |
||||
assert!(message.contains("_rust_rocksdb_optimistic_transaction_db_destroy_on_open")); |
||||
assert!(message.contains("/LOCK:")); |
||||
} |
||||
Ok(_) => panic!("should fail"), |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn writebatch() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_writebatch"); |
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
{ |
||||
// test put
|
||||
let mut batch = WriteBatchWithTransaction::<true>::default(); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
assert_eq!(batch.len(), 0); |
||||
assert!(batch.is_empty()); |
||||
batch.put(b"k1", b"v1111"); |
||||
batch.put(b"k2", b"v2222"); |
||||
batch.put(b"k3", b"v3333"); |
||||
assert_eq!(batch.len(), 3); |
||||
assert!(!batch.is_empty()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
let p = db.write(batch); |
||||
assert!(p.is_ok()); |
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1"); |
||||
assert_eq!(r.unwrap().unwrap(), b"v1111"); |
||||
} |
||||
{ |
||||
// test delete
|
||||
let mut batch = WriteBatchWithTransaction::<true>::default(); |
||||
batch.delete(b"k1"); |
||||
assert_eq!(batch.len(), 1); |
||||
assert!(!batch.is_empty()); |
||||
let p = db.write(batch); |
||||
assert!(p.is_ok()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
} |
||||
{ |
||||
// test size_in_bytes
|
||||
let mut batch = WriteBatchWithTransaction::<true>::default(); |
||||
let before = batch.size_in_bytes(); |
||||
batch.put(b"k1", b"v1234567890"); |
||||
let after = batch.size_in_bytes(); |
||||
assert!(before + 10 <= after); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn iterator_test() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_iteratortest"); |
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let k1: Box<[u8]> = b"k1".to_vec().into_boxed_slice(); |
||||
let k2: Box<[u8]> = b"k2".to_vec().into_boxed_slice(); |
||||
let k3: Box<[u8]> = b"k3".to_vec().into_boxed_slice(); |
||||
let k4: Box<[u8]> = b"k4".to_vec().into_boxed_slice(); |
||||
let v1: Box<[u8]> = b"v1111".to_vec().into_boxed_slice(); |
||||
let v2: Box<[u8]> = b"v2222".to_vec().into_boxed_slice(); |
||||
let v3: Box<[u8]> = b"v3333".to_vec().into_boxed_slice(); |
||||
let v4: Box<[u8]> = b"v4444".to_vec().into_boxed_slice(); |
||||
|
||||
db.put(&*k1, &*v1).unwrap(); |
||||
db.put(&*k2, &*v2).unwrap(); |
||||
db.put(&*k3, &*v3).unwrap(); |
||||
let expected = vec![ |
||||
(k1.clone(), v1.clone()), |
||||
(k2.clone(), v2.clone()), |
||||
(k3.clone(), v3.clone()), |
||||
]; |
||||
|
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test that it's idempotent
|
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test in reverse
|
||||
let iter = db.iterator(IteratorMode::End); |
||||
let mut tmp_vec = iter.collect::<Vec<_>>(); |
||||
tmp_vec.reverse(); |
||||
|
||||
let old_iter = db.iterator(IteratorMode::Start); |
||||
db.put(&*k4, &*v4).unwrap(); |
||||
let expected2 = vec![ |
||||
(k1, v1), |
||||
(k2, v2), |
||||
(k3.clone(), v3.clone()), |
||||
(k4.clone(), v4.clone()), |
||||
]; |
||||
assert_eq!(old_iter.collect::<Vec<_>>(), expected); |
||||
|
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected2); |
||||
|
||||
let iter = db.iterator(IteratorMode::From(b"k3", Direction::Forward)); |
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(k3, v3), (k4, v4)]); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn snapshot_test() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_snapshottest"); |
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
|
||||
assert!(db.put(b"k1", b"v1111").is_ok()); |
||||
|
||||
let snap = db.snapshot(); |
||||
assert_eq!(snap.get(b"k1").unwrap().unwrap(), b"v1111"); |
||||
|
||||
assert!(db.put(b"k2", b"v2222").is_ok()); |
||||
|
||||
assert!(db.get(b"k2").unwrap().is_some()); |
||||
assert!(snap.get(b"k2").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn prefix_extract_and_iterate_test() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_prefix_extract_and_iterate"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(2)); |
||||
|
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open(&opts, &path).unwrap(); |
||||
db.put(b"p1_k1", b"v1").unwrap(); |
||||
db.put(b"p2_k2", b"v2").unwrap(); |
||||
db.put(b"p1_k3", b"v3").unwrap(); |
||||
db.put(b"p1_k4", b"v4").unwrap(); |
||||
db.put(b"p2_k5", b"v5").unwrap(); |
||||
|
||||
let mut readopts = ReadOptions::default(); |
||||
readopts.set_prefix_same_as_start(true); |
||||
readopts.set_iterate_lower_bound(b"p1".to_vec()); |
||||
readopts.set_pin_data(true); |
||||
|
||||
let iter = db.iterator_opt(IteratorMode::Start, readopts); |
||||
let expected: Vec<_> = vec![(b"p1_k1", b"v1"), (b"p1_k3", b"v3"), (b"p1_k4", b"v4")] |
||||
.into_iter() |
||||
.map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) |
||||
.collect(); |
||||
assert_eq!(expected, iter.collect::<Vec<_>>()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn cuckoo() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_cuckoo"); |
||||
|
||||
{ |
||||
let mut opts = Options::default(); |
||||
let mut factory_opts = CuckooTableOptions::default(); |
||||
factory_opts.set_hash_ratio(0.8); |
||||
factory_opts.set_max_search_depth(20); |
||||
factory_opts.set_cuckoo_block_size(10); |
||||
factory_opts.set_identity_as_first_hash(true); |
||||
factory_opts.set_use_module_hash(false); |
||||
|
||||
opts.set_cuckoo_table_factory(&factory_opts); |
||||
opts.create_if_missing(true); |
||||
|
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open(&opts, &path).unwrap(); |
||||
db.put(b"k1", b"v1").unwrap(); |
||||
db.put(b"k2", b"v2").unwrap(); |
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1"); |
||||
|
||||
assert_eq!(r.unwrap().unwrap(), b"v1"); |
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k2"); |
||||
|
||||
assert_eq!(r.unwrap().unwrap(), b"v2"); |
||||
assert!(db.delete(b"k1").is_ok()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_transaction"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open(&opts, &path).unwrap(); |
||||
|
||||
// put outside of transaction
|
||||
db.put(b"k1", b"v1").unwrap(); |
||||
assert_eq!(db.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
|
||||
{ |
||||
let txn1 = db.transaction(); |
||||
txn1.put(b"k1", b"v2").unwrap(); |
||||
|
||||
// get outside of transaction
|
||||
assert_eq!(db.get(b"k1").unwrap().unwrap().as_slice(), b"v1"); |
||||
|
||||
// modify same key in another transaction
|
||||
let txn2 = db.transaction(); |
||||
txn2.put(b"k1", b"v3").unwrap(); |
||||
txn2.commit().unwrap(); |
||||
|
||||
// txn1 should fail with ErrorKind::Busy
|
||||
let err = txn1.commit().unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::Busy); |
||||
} |
||||
|
||||
{ |
||||
let txn1 = db.transaction(); |
||||
txn1.put(b"k2", b"v2").unwrap(); |
||||
|
||||
let txn2 = db.transaction(); |
||||
assert!(txn2.get_for_update(b"k2", true).unwrap().is_none()); |
||||
|
||||
// txn1 commit, txn2 should fail with Busy.
|
||||
txn1.commit().unwrap(); |
||||
assert_eq!(txn2.commit().unwrap_err().kind(), ErrorKind::Busy); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_iterator() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_transaction_iterator"); |
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let k1: Box<[u8]> = b"k1".to_vec().into_boxed_slice(); |
||||
let k2: Box<[u8]> = b"k2".to_vec().into_boxed_slice(); |
||||
let k3: Box<[u8]> = b"k3".to_vec().into_boxed_slice(); |
||||
let k4: Box<[u8]> = b"k4".to_vec().into_boxed_slice(); |
||||
let v1: Box<[u8]> = b"v1111".to_vec().into_boxed_slice(); |
||||
let v2: Box<[u8]> = b"v2222".to_vec().into_boxed_slice(); |
||||
let v3: Box<[u8]> = b"v3333".to_vec().into_boxed_slice(); |
||||
let v4: Box<[u8]> = b"v4444".to_vec().into_boxed_slice(); |
||||
|
||||
db.put(&*k1, &*v1).unwrap(); |
||||
db.put(&*k2, &*v2).unwrap(); |
||||
db.put(&*k3, &*v3).unwrap(); |
||||
let expected = vec![ |
||||
(k1.clone(), v1.clone()), |
||||
(k2.clone(), v2.clone()), |
||||
(k3.clone(), v3.clone()), |
||||
]; |
||||
|
||||
let txn = db.transaction(); |
||||
|
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test that it's idempotent
|
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test in reverse
|
||||
let iter = txn.iterator(IteratorMode::End); |
||||
let mut tmp_vec = iter.collect::<Vec<_>>(); |
||||
tmp_vec.reverse(); |
||||
|
||||
let old_iter = txn.iterator(IteratorMode::Start); |
||||
txn.put(&*k4, &*v4).unwrap(); |
||||
let expected2 = vec![ |
||||
(k1, v1), |
||||
(k2, v2), |
||||
(k3.clone(), v3.clone()), |
||||
(k4.clone(), v4.clone()), |
||||
]; |
||||
assert_eq!(old_iter.collect::<Vec<_>>(), expected); |
||||
|
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected2); |
||||
|
||||
let iter = txn.iterator(IteratorMode::From(b"k3", Direction::Forward)); |
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(k3, v3), (k4, v4)]); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_rollback() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_transaction_rollback"); |
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
let txn = db.transaction(); |
||||
|
||||
txn.rollback().unwrap(); |
||||
|
||||
txn.put(b"k1", b"v1").unwrap(); |
||||
txn.set_savepoint(); |
||||
txn.put(b"k2", b"v2").unwrap(); |
||||
|
||||
assert_eq!(txn.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
assert_eq!(txn.get(b"k2").unwrap().unwrap(), b"v2"); |
||||
|
||||
txn.rollback_to_savepoint().unwrap(); |
||||
assert_eq!(txn.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(txn.get(b"k2").unwrap().is_none()); |
||||
|
||||
txn.rollback().unwrap(); |
||||
assert!(txn.get(b"k1").unwrap().is_none()); |
||||
|
||||
txn.commit().unwrap(); |
||||
|
||||
assert!(db.get(b"k2").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_cf() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_transaction_cf"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let db: OptimisticTransactionDB = |
||||
OptimisticTransactionDB::open_cf(&opts, &path, ["cf1", "cf2"]).unwrap(); |
||||
|
||||
let cf1 = db.cf_handle("cf1").unwrap(); |
||||
let cf2 = db.cf_handle("cf2").unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
txn.put(b"k0", b"v0").unwrap(); |
||||
txn.put_cf(&cf1, b"k1", b"v1").unwrap(); |
||||
txn.put_cf(&cf2, b"k2", b"v2").unwrap(); |
||||
|
||||
assert_eq!(txn.get(b"k0").unwrap().unwrap(), b"v0"); |
||||
assert!(txn.get(b"k1").unwrap().is_none()); |
||||
assert!(txn.get(b"k2").unwrap().is_none()); |
||||
|
||||
assert!(txn.get_cf(&cf1, b"k0").unwrap().is_none()); |
||||
assert_eq!(txn.get_cf(&cf1, b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(txn.get_cf(&cf1, b"k2").unwrap().is_none()); |
||||
|
||||
assert!(txn.get_cf(&cf2, b"k0").unwrap().is_none()); |
||||
assert!(txn.get_cf(&cf2, b"k1").unwrap().is_none()); |
||||
assert_eq!(txn.get_cf(&cf2, b"k2").unwrap().unwrap(), b"v2"); |
||||
|
||||
txn.commit().unwrap(); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_snapshot() { |
||||
let path = DBPath::new("_rust_rocksdb_optimistic_transaction_db_transaction_snapshot"); |
||||
{ |
||||
let db: OptimisticTransactionDB = OptimisticTransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
let snapshot = txn.snapshot(); |
||||
assert!(snapshot.get(b"k1").unwrap().is_none()); |
||||
db.put(b"k1", b"v1").unwrap(); |
||||
assert_eq!(snapshot.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
|
||||
let mut opts = OptimisticTransactionOptions::default(); |
||||
opts.set_snapshot(true); |
||||
let txn = db.transaction_opt(&WriteOptions::default(), &opts); |
||||
db.put(b"k2", b"v2").unwrap(); |
||||
{ |
||||
let snapshot = SnapshotWithThreadMode::new(&txn); |
||||
assert!(snapshot.get(b"k2").unwrap().is_none()); |
||||
assert_eq!(txn.get(b"k2").unwrap().unwrap(), b"v2"); |
||||
} |
||||
txn.get_for_update(b"k2", true).unwrap(); |
||||
assert_eq!(txn.commit().unwrap_err().kind(), ErrorKind::Busy); |
||||
|
||||
let txn = db.transaction_opt(&WriteOptions::default(), &opts); |
||||
let snapshot = txn.snapshot(); |
||||
txn.put(b"k3", b"v3").unwrap(); |
||||
assert!(db.get(b"k3").unwrap().is_none()); |
||||
// put operation should also visible to snapshot,
|
||||
// because this snapshot is associated with a transaction
|
||||
assert_eq!(snapshot.get(b"k3").unwrap().unwrap(), b"v3"); |
||||
} |
||||
} |
@ -0,0 +1,689 @@ |
||||
// Copyright 2021 Yiyuan Liu
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
mod util; |
||||
|
||||
use pretty_assertions::assert_eq; |
||||
|
||||
use rocksdb::{ |
||||
CuckooTableOptions, DBAccess, Direction, Error, ErrorKind, IteratorMode, Options, ReadOptions, |
||||
SliceTransform, TransactionDB, TransactionDBOptions, TransactionOptions, |
||||
WriteBatchWithTransaction, WriteOptions, DB, |
||||
}; |
||||
use util::DBPath; |
||||
|
||||
#[test] |
||||
fn open_default() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_open_default"); |
||||
|
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
assert!(db.put(b"k1", b"v1111").is_ok()); |
||||
|
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1"); |
||||
|
||||
assert_eq!(r.unwrap().unwrap(), b"v1111"); |
||||
assert!(db.delete(b"k1").is_ok()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn open_cf() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_open_cf"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let db: TransactionDB = TransactionDB::open_cf( |
||||
&opts, |
||||
&TransactionDBOptions::default(), |
||||
&path, |
||||
["cf1", "cf2"], |
||||
) |
||||
.unwrap(); |
||||
|
||||
let cf1 = db.cf_handle("cf1").unwrap(); |
||||
let cf2 = db.cf_handle("cf2").unwrap(); |
||||
|
||||
db.put(b"k0", b"v0").unwrap(); |
||||
db.put_cf(&cf1, b"k1", b"v1").unwrap(); |
||||
db.put_cf(&cf2, b"k2", b"v2").unwrap(); |
||||
|
||||
assert_eq!(db.get(b"k0").unwrap().unwrap(), b"v0"); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
assert!(db.get(b"k2").unwrap().is_none()); |
||||
|
||||
assert!(db.get_cf(&cf1, b"k0").unwrap().is_none()); |
||||
assert_eq!(db.get_cf(&cf1, b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(db.get_cf(&cf1, b"k2").unwrap().is_none()); |
||||
|
||||
assert!(db.get_cf(&cf2, b"k0").unwrap().is_none()); |
||||
assert!(db.get_cf(&cf2, b"k1").unwrap().is_none()); |
||||
assert_eq!(db.get_cf(&cf2, b"k2").unwrap().unwrap(), b"v2"); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn put_get() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_put_get"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
assert!(db.put(b"k1", b"v1111").is_ok()); |
||||
assert!(db.put(b"k2", b"v22222222").is_ok()); |
||||
|
||||
let v1 = db.get(b"k1").unwrap().unwrap(); |
||||
let v2 = db.get(b"k2").unwrap().unwrap(); |
||||
assert_eq!(v1.as_slice(), b"v1111"); |
||||
assert_eq!(v2.as_slice(), b"v22222222"); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn multi_get() { |
||||
let path = DBPath::new("_rust_rocksdb_multi_get"); |
||||
|
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
let initial_snap = db.snapshot(); |
||||
db.put(b"k1", b"v1").unwrap(); |
||||
let k1_snap = db.snapshot(); |
||||
db.put(b"k2", b"v2").unwrap(); |
||||
|
||||
let _ = db.multi_get(&[b"k0"; 40]); |
||||
|
||||
let assert_values = |values: Vec<_>| { |
||||
assert_eq!(3, values.len()); |
||||
assert_eq!(values[0], None); |
||||
assert_eq!(values[1], Some(b"v1".to_vec())); |
||||
assert_eq!(values[2], Some(b"v2".to_vec())); |
||||
}; |
||||
|
||||
let values = db |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
|
||||
let values = DBAccess::multi_get_opt(&db, &[b"k0", b"k1", b"k2"], &Default::default()) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
|
||||
let values = db |
||||
.snapshot() |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
|
||||
let none_values = initial_snap |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_eq!(none_values, vec![None; 3]); |
||||
|
||||
let k1_only = k1_snap |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_eq!(k1_only, vec![None, Some(b"v1".to_vec()), None]); |
||||
|
||||
let txn = db.transaction(); |
||||
let values = txn |
||||
.multi_get(&[b"k0", b"k1", b"k2"]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_values(values); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn multi_get_cf() { |
||||
let path = DBPath::new("_rust_rocksdb_multi_get_cf"); |
||||
|
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let db: TransactionDB = TransactionDB::open_cf( |
||||
&opts, |
||||
&TransactionDBOptions::default(), |
||||
&path, |
||||
&["cf0", "cf1", "cf2"], |
||||
) |
||||
.unwrap(); |
||||
|
||||
let cf0 = db.cf_handle("cf0").unwrap(); |
||||
|
||||
let cf1 = db.cf_handle("cf1").unwrap(); |
||||
db.put_cf(&cf1, b"k1", b"v1").unwrap(); |
||||
|
||||
let cf2 = db.cf_handle("cf2").unwrap(); |
||||
db.put_cf(&cf2, b"k2", b"v2").unwrap(); |
||||
|
||||
let values = db |
||||
.multi_get_cf(vec![(&cf0, b"k0"), (&cf1, b"k1"), (&cf2, b"k2")]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
assert_eq!(3, values.len()); |
||||
assert_eq!(values[0], None); |
||||
assert_eq!(values[1], Some(b"v1".to_vec())); |
||||
assert_eq!(values[2], Some(b"v2".to_vec())); |
||||
|
||||
let txn = db.transaction(); |
||||
let values = txn |
||||
.multi_get_cf(vec![(&cf0, b"k0"), (&cf1, b"k1"), (&cf2, b"k2")]) |
||||
.into_iter() |
||||
.map(Result::unwrap) |
||||
.collect::<Vec<_>>(); |
||||
|
||||
assert_eq!(3, values.len()); |
||||
assert_eq!(values[0], None); |
||||
assert_eq!(values[1], Some(b"v1".to_vec())); |
||||
assert_eq!(values[2], Some(b"v2".to_vec())); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn destroy_on_open() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_destroy_on_open"); |
||||
let _db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
let opts = Options::default(); |
||||
// The TransactionDB will still be open when we try to destroy it and the lock should fail.
|
||||
match DB::destroy(&opts, &path) { |
||||
Err(s) => { |
||||
let message = s.to_string(); |
||||
assert_eq!(s.kind(), ErrorKind::IOError); |
||||
assert!(message.contains("_rust_rocksdb_transaction_db_destroy_on_open")); |
||||
assert!(message.contains("/LOCK:")); |
||||
} |
||||
Ok(_) => panic!("should fail"), |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn writebatch() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_writebatch"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
{ |
||||
// test put
|
||||
let mut batch = WriteBatchWithTransaction::<true>::default(); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
assert_eq!(batch.len(), 0); |
||||
assert!(batch.is_empty()); |
||||
batch.put(b"k1", b"v1111"); |
||||
batch.put(b"k2", b"v2222"); |
||||
batch.put(b"k3", b"v3333"); |
||||
assert_eq!(batch.len(), 3); |
||||
assert!(!batch.is_empty()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
let p = db.write(batch); |
||||
assert!(p.is_ok()); |
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1"); |
||||
assert_eq!(r.unwrap().unwrap(), b"v1111"); |
||||
} |
||||
{ |
||||
// test delete
|
||||
let mut batch = WriteBatchWithTransaction::<true>::default(); |
||||
batch.delete(b"k1"); |
||||
assert_eq!(batch.len(), 1); |
||||
assert!(!batch.is_empty()); |
||||
let p = db.write(batch); |
||||
assert!(p.is_ok()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
} |
||||
{ |
||||
// test size_in_bytes
|
||||
let mut batch = WriteBatchWithTransaction::<true>::default(); |
||||
let before = batch.size_in_bytes(); |
||||
batch.put(b"k1", b"v1234567890"); |
||||
let after = batch.size_in_bytes(); |
||||
assert!(before + 10 <= after); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn iterator_test() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_iteratortest"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let k1: Box<[u8]> = b"k1".to_vec().into_boxed_slice(); |
||||
let k2: Box<[u8]> = b"k2".to_vec().into_boxed_slice(); |
||||
let k3: Box<[u8]> = b"k3".to_vec().into_boxed_slice(); |
||||
let k4: Box<[u8]> = b"k4".to_vec().into_boxed_slice(); |
||||
let v1: Box<[u8]> = b"v1111".to_vec().into_boxed_slice(); |
||||
let v2: Box<[u8]> = b"v2222".to_vec().into_boxed_slice(); |
||||
let v3: Box<[u8]> = b"v3333".to_vec().into_boxed_slice(); |
||||
let v4: Box<[u8]> = b"v4444".to_vec().into_boxed_slice(); |
||||
|
||||
db.put(&*k1, &*v1).unwrap(); |
||||
db.put(&*k2, &*v2).unwrap(); |
||||
db.put(&*k3, &*v3).unwrap(); |
||||
let expected = vec![ |
||||
(k1.clone(), v1.clone()), |
||||
(k2.clone(), v2.clone()), |
||||
(k3.clone(), v3.clone()), |
||||
]; |
||||
|
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test that it's idempotent
|
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test in reverse
|
||||
let iter = db.iterator(IteratorMode::End); |
||||
let mut tmp_vec = iter.collect::<Vec<_>>(); |
||||
tmp_vec.reverse(); |
||||
|
||||
let old_iter = db.iterator(IteratorMode::Start); |
||||
db.put(&*k4, &*v4).unwrap(); |
||||
let expected2 = vec![ |
||||
(k1, v1), |
||||
(k2, v2), |
||||
(k3.clone(), v3.clone()), |
||||
(k4.clone(), v4.clone()), |
||||
]; |
||||
assert_eq!(old_iter.collect::<Vec<_>>(), expected); |
||||
|
||||
let iter = db.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected2); |
||||
|
||||
let iter = db.iterator(IteratorMode::From(b"k3", Direction::Forward)); |
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(k3, v3), (k4, v4)]); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn snapshot_test() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_snapshottest"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
assert!(db.put(b"k1", b"v1111").is_ok()); |
||||
|
||||
let snap = db.snapshot(); |
||||
assert_eq!(snap.get(b"k1").unwrap().unwrap(), b"v1111"); |
||||
|
||||
assert!(db.put(b"k2", b"v2222").is_ok()); |
||||
|
||||
assert!(db.get(b"k2").unwrap().is_some()); |
||||
assert!(snap.get(b"k2").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn prefix_extract_and_iterate_test() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_prefix_extract_and_iterate"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
opts.set_prefix_extractor(SliceTransform::create_fixed_prefix(2)); |
||||
let txn_db_opts = TransactionDBOptions::default(); |
||||
|
||||
let db: TransactionDB = TransactionDB::open(&opts, &txn_db_opts, &path).unwrap(); |
||||
db.put(b"p1_k1", b"v1").unwrap(); |
||||
db.put(b"p2_k2", b"v2").unwrap(); |
||||
db.put(b"p1_k3", b"v3").unwrap(); |
||||
db.put(b"p1_k4", b"v4").unwrap(); |
||||
db.put(b"p2_k5", b"v5").unwrap(); |
||||
|
||||
let mut readopts = ReadOptions::default(); |
||||
readopts.set_prefix_same_as_start(true); |
||||
readopts.set_iterate_lower_bound(b"p1".to_vec()); |
||||
readopts.set_pin_data(true); |
||||
|
||||
let iter = db.iterator_opt(IteratorMode::Start, readopts); |
||||
let expected: Vec<_> = vec![(b"p1_k1", b"v1"), (b"p1_k3", b"v3"), (b"p1_k4", b"v4")] |
||||
.into_iter() |
||||
.map(|(k, v)| (k.to_vec().into_boxed_slice(), v.to_vec().into_boxed_slice())) |
||||
.collect(); |
||||
assert_eq!(expected, iter.collect::<Vec<_>>()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn cuckoo() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_cuckoo"); |
||||
|
||||
{ |
||||
let mut opts = Options::default(); |
||||
let txn_db_opts = TransactionDBOptions::default(); |
||||
let mut factory_opts = CuckooTableOptions::default(); |
||||
factory_opts.set_hash_ratio(0.8); |
||||
factory_opts.set_max_search_depth(20); |
||||
factory_opts.set_cuckoo_block_size(10); |
||||
factory_opts.set_identity_as_first_hash(true); |
||||
factory_opts.set_use_module_hash(false); |
||||
|
||||
opts.set_cuckoo_table_factory(&factory_opts); |
||||
opts.create_if_missing(true); |
||||
|
||||
let db: TransactionDB = TransactionDB::open(&opts, &txn_db_opts, &path).unwrap(); |
||||
db.put(b"k1", b"v1").unwrap(); |
||||
db.put(b"k2", b"v2").unwrap(); |
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k1"); |
||||
|
||||
assert_eq!(r.unwrap().unwrap(), b"v1"); |
||||
let r: Result<Option<Vec<u8>>, Error> = db.get(b"k2"); |
||||
|
||||
assert_eq!(r.unwrap().unwrap(), b"v2"); |
||||
assert!(db.delete(b"k1").is_ok()); |
||||
assert!(db.get(b"k1").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_transaction"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
let mut txn_db_opts = TransactionDBOptions::default(); |
||||
txn_db_opts.set_txn_lock_timeout(10); |
||||
|
||||
let db: TransactionDB = TransactionDB::open(&opts, &txn_db_opts, &path).unwrap(); |
||||
|
||||
// put outside of transaction
|
||||
db.put(b"k1", b"v1").unwrap(); |
||||
assert_eq!(db.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
|
||||
let txn1 = db.transaction(); |
||||
txn1.put(b"k1", b"v2").unwrap(); |
||||
|
||||
// get outside of transaction
|
||||
assert_eq!(db.get(b"k1").unwrap().unwrap().as_slice(), b"v1"); |
||||
|
||||
// modify same key in another transaction, should get TimedOut
|
||||
let txn2 = db.transaction(); |
||||
let err = txn2.put(b"k1", b"v3").unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::TimedOut); |
||||
|
||||
// modify same key directly, should also get TimedOut
|
||||
let err = db.put(b"k1", b"v4").unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::TimedOut); |
||||
|
||||
txn1.commit().unwrap(); |
||||
assert_eq!(db.get(b"k1").unwrap().unwrap().as_slice(), b"v2"); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_iterator() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_transaction_iterator"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let k1: Box<[u8]> = b"k1".to_vec().into_boxed_slice(); |
||||
let k2: Box<[u8]> = b"k2".to_vec().into_boxed_slice(); |
||||
let k3: Box<[u8]> = b"k3".to_vec().into_boxed_slice(); |
||||
let k4: Box<[u8]> = b"k4".to_vec().into_boxed_slice(); |
||||
let v1: Box<[u8]> = b"v1111".to_vec().into_boxed_slice(); |
||||
let v2: Box<[u8]> = b"v2222".to_vec().into_boxed_slice(); |
||||
let v3: Box<[u8]> = b"v3333".to_vec().into_boxed_slice(); |
||||
let v4: Box<[u8]> = b"v4444".to_vec().into_boxed_slice(); |
||||
|
||||
db.put(&*k1, &*v1).unwrap(); |
||||
db.put(&*k2, &*v2).unwrap(); |
||||
db.put(&*k3, &*v3).unwrap(); |
||||
let expected = vec![ |
||||
(k1.clone(), v1.clone()), |
||||
(k2.clone(), v2.clone()), |
||||
(k3.clone(), v3.clone()), |
||||
]; |
||||
|
||||
let txn = db.transaction(); |
||||
|
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test that it's idempotent
|
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected); |
||||
|
||||
// Test in reverse
|
||||
let iter = txn.iterator(IteratorMode::End); |
||||
let mut tmp_vec = iter.collect::<Vec<_>>(); |
||||
tmp_vec.reverse(); |
||||
|
||||
let old_iter = txn.iterator(IteratorMode::Start); |
||||
txn.put(&*k4, &*v4).unwrap(); |
||||
let expected2 = vec![ |
||||
(k1, v1), |
||||
(k2, v2), |
||||
(k3.clone(), v3.clone()), |
||||
(k4.clone(), v4.clone()), |
||||
]; |
||||
assert_eq!(old_iter.collect::<Vec<_>>(), expected); |
||||
|
||||
let iter = txn.iterator(IteratorMode::Start); |
||||
assert_eq!(iter.collect::<Vec<_>>(), expected2); |
||||
|
||||
let iter = txn.iterator(IteratorMode::From(b"k3", Direction::Forward)); |
||||
assert_eq!(iter.collect::<Vec<_>>(), vec![(k3, v3), (k4, v4)]); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_rollback() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_transaction_rollback"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
let txn = db.transaction(); |
||||
|
||||
txn.rollback().unwrap(); |
||||
|
||||
txn.put(b"k1", b"v1").unwrap(); |
||||
txn.set_savepoint(); |
||||
txn.put(b"k2", b"v2").unwrap(); |
||||
|
||||
assert_eq!(txn.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
assert_eq!(txn.get(b"k2").unwrap().unwrap(), b"v2"); |
||||
|
||||
txn.rollback_to_savepoint().unwrap(); |
||||
assert_eq!(txn.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(txn.get(b"k2").unwrap().is_none()); |
||||
|
||||
txn.rollback().unwrap(); |
||||
assert!(txn.get(b"k1").unwrap().is_none()); |
||||
|
||||
txn.commit().unwrap(); |
||||
|
||||
assert!(db.get(b"k2").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_cf() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_transaction_cf"); |
||||
{ |
||||
let mut opts = Options::default(); |
||||
opts.create_if_missing(true); |
||||
opts.create_missing_column_families(true); |
||||
let db: TransactionDB = TransactionDB::open_cf( |
||||
&opts, |
||||
&TransactionDBOptions::default(), |
||||
&path, |
||||
["cf1", "cf2"], |
||||
) |
||||
.unwrap(); |
||||
|
||||
let cf1 = db.cf_handle("cf1").unwrap(); |
||||
let cf2 = db.cf_handle("cf2").unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
txn.put(b"k0", b"v0").unwrap(); |
||||
txn.put_cf(&cf1, b"k1", b"v1").unwrap(); |
||||
txn.put_cf(&cf2, b"k2", b"v2").unwrap(); |
||||
|
||||
assert_eq!(txn.get(b"k0").unwrap().unwrap(), b"v0"); |
||||
assert!(txn.get(b"k1").unwrap().is_none()); |
||||
assert!(txn.get(b"k2").unwrap().is_none()); |
||||
|
||||
assert!(txn.get_cf(&cf1, b"k0").unwrap().is_none()); |
||||
assert_eq!(txn.get_cf(&cf1, b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(txn.get_cf(&cf1, b"k2").unwrap().is_none()); |
||||
|
||||
assert!(txn.get_cf(&cf2, b"k0").unwrap().is_none()); |
||||
assert!(txn.get_cf(&cf2, b"k1").unwrap().is_none()); |
||||
assert_eq!(txn.get_cf(&cf2, b"k2").unwrap().unwrap(), b"v2"); |
||||
|
||||
txn.commit().unwrap(); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn transaction_snapshot() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_transaction_snapshot"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
let snapshot = txn.snapshot(); |
||||
assert!(snapshot.get(b"k1").unwrap().is_none()); |
||||
db.put(b"k1", b"v1").unwrap(); |
||||
assert_eq!(snapshot.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
|
||||
let mut opts = TransactionOptions::default(); |
||||
opts.set_snapshot(true); |
||||
let txn = db.transaction_opt(&WriteOptions::default(), &opts); |
||||
db.put(b"k2", b"v2").unwrap(); |
||||
let snapshot = txn.snapshot(); |
||||
assert!(snapshot.get(b"k2").unwrap().is_none()); |
||||
assert_eq!(txn.get(b"k2").unwrap().unwrap(), b"v2"); |
||||
assert_eq!( |
||||
txn.get_for_update(b"k2", true).unwrap_err().kind(), |
||||
ErrorKind::Busy |
||||
); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn two_phase_commit() { |
||||
let path = DBPath::new("_rust_rocksdb_transaction_db_2pc"); |
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
txn.put(b"k1", b"v1").unwrap(); |
||||
txn.set_name(b"txn1").unwrap(); |
||||
txn.prepare().unwrap(); |
||||
txn.commit().unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
txn.put(b"k2", b"v2").unwrap(); |
||||
let err = txn.prepare().unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::InvalidArgument); |
||||
|
||||
let mut opt = TransactionOptions::new(); |
||||
opt.set_skip_prepare(false); |
||||
let txn = db.transaction_opt(&WriteOptions::default(), &opt); |
||||
txn.put(b"k3", b"v3").unwrap(); |
||||
let err = txn.prepare().unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::InvalidArgument); |
||||
} |
||||
|
||||
DB::destroy(&Options::default(), &path).unwrap(); |
||||
|
||||
{ |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
let txn = db.transaction(); |
||||
txn.put(b"k1", b"v1").unwrap(); |
||||
txn.set_name(b"t1").unwrap(); |
||||
txn.prepare().unwrap(); |
||||
|
||||
let txn2 = db.transaction(); |
||||
txn2.put(b"k2", b"v1").unwrap(); |
||||
txn2.set_name(b"t2").unwrap(); |
||||
txn2.prepare().unwrap(); |
||||
|
||||
let txn3 = db.transaction(); |
||||
let err = txn3.set_name(b"t1").unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::InvalidArgument); |
||||
|
||||
// k1 and k2 should locked after we restore prepared transactions.
|
||||
let err = db.put(b"k1", b"v2").unwrap_err(); |
||||
assert_eq!(err.kind(), ErrorKind::TimedOut); |
||||
} |
||||
|
||||
{ |
||||
// recovery
|
||||
let mut opt = TransactionDBOptions::new(); |
||||
opt.set_default_lock_timeout(1); |
||||
let db: TransactionDB = TransactionDB::open_default(&path).unwrap(); |
||||
|
||||
// get prepared transactions
|
||||
let txns = db.prepared_transactions(); |
||||
assert_eq!(txns.len(), 2); |
||||
|
||||
for (_, txn) in txns.into_iter().enumerate() { |
||||
let name = txn.get_name().unwrap(); |
||||
|
||||
if name == b"t1" { |
||||
txn.commit().unwrap(); |
||||
} else if name == b"t2" { |
||||
txn.rollback().unwrap(); |
||||
} else { |
||||
unreachable!(); |
||||
} |
||||
} |
||||
|
||||
assert_eq!(db.get(b"k1").unwrap().unwrap(), b"v1"); |
||||
assert!(db.get(b"k2").unwrap().is_none()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn test_snapshot_outlive_transaction_db() { |
||||
let t = trybuild::TestCases::new(); |
||||
t.compile_fail("tests/fail/snapshot_outlive_transaction_db.rs"); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_txn_outlive_transaction_db() { |
||||
let t = trybuild::TestCases::new(); |
||||
t.compile_fail("tests/fail/transaction_outlive_transaction_db.rs"); |
||||
} |
||||
|
||||
#[test] |
||||
fn test_snapshot_outlive_txn() { |
||||
let t = trybuild::TestCases::new(); |
||||
t.compile_fail("tests/fail/snapshot_outlive_transaction.rs"); |
||||
} |
Loading…
Reference in new issue