diff --git a/src/transactions/transaction_db.rs b/src/transactions/transaction_db.rs index 09a488a94..833c4300e 100644 --- a/src/transactions/transaction_db.rs +++ b/src/transactions/transaction_db.rs @@ -23,6 +23,9 @@ use std::{ sync::{Arc, Mutex}, }; +use crate::CStrLike; +use std::ffi::CStr; + use crate::{ column_family::UnboundColumnFamily, db::{convert_values, DBAccess}, @@ -1002,6 +1005,74 @@ impl TransactionDB { Err(Error::new(format!("Invalid column family: {name}"))) } } + + /// Implementation for property_value et al methods. + /// + /// `name` is the name of the property. It will be converted into a CString + /// and passed to `get_property` as argument. `get_property` reads the + /// specified property and either returns NULL or a pointer to a C allocated + /// string; this method takes ownership of that string and will free it at + /// the end. That string is parsed using `parse` callback which produces + /// the returned result. + fn property_value_impl( + name: impl CStrLike, + get_property: impl FnOnce(*const c_char) -> *mut c_char, + parse: impl FnOnce(&str) -> Result, + ) -> Result, Error> { + let value = match name.bake() { + Ok(prop_name) => get_property(prop_name.as_ptr()), + Err(e) => { + return Err(Error::new(format!( + "Failed to convert property name to CString: {e}" + ))); + } + }; + if value.is_null() { + return Ok(None); + } + let result = match unsafe { CStr::from_ptr(value) }.to_str() { + Ok(s) => parse(s).map(|value| Some(value)), + Err(e) => Err(Error::new(format!( + "Failed to convert property value to string: {e}" + ))), + }; + unsafe { + ffi::rocksdb_free(value as *mut c_void); + } + result + } + + /// Retrieves a RocksDB property by name. + /// + /// Full list of properties could be find + /// [here](https://github.com/facebook/rocksdb/blob/08809f5e6cd9cc4bc3958dd4d59457ae78c76660/include/rocksdb/db.h#L428-L634). + pub fn property_value(&self, name: impl CStrLike) -> Result, Error> { + Self::property_value_impl( + name, + |prop_name| unsafe { ffi::rocksdb_transactiondb_property_value(self.inner, prop_name) }, + |str_value| Ok(str_value.to_owned()), + ) + } + + fn parse_property_int_value(value: &str) -> Result { + value.parse::().map_err(|err| { + Error::new(format!( + "Failed to convert property value {value} to int: {err}" + )) + }) + } + + /// Retrieves a RocksDB property and casts it to an integer. + /// + /// Full list of properties that return int values could be find + /// [here](https://github.com/facebook/rocksdb/blob/08809f5e6cd9cc4bc3958dd4d59457ae78c76660/include/rocksdb/db.h#L654-L689). + pub fn property_int_value(&self, name: impl CStrLike) -> Result, Error> { + Self::property_value_impl( + name, + |prop_name| unsafe { ffi::rocksdb_transactiondb_property_value(self.inner, prop_name) }, + Self::parse_property_int_value, + ) + } } impl Drop for TransactionDB { diff --git a/tests/test_transaction_db_property.rs b/tests/test_transaction_db_property.rs new file mode 100644 index 000000000..abb507ffd --- /dev/null +++ b/tests/test_transaction_db_property.rs @@ -0,0 +1,62 @@ +// Copyright 2020 Tyler Neely +// +// 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::{properties, Options, TransactionDB, TransactionDBOptions}; +use util::DBPath; + +#[test] +fn transaction_db_property_test() { + let path = DBPath::new("_rust_rocksdb_transaction_db_property_test"); + { + let mut options = Options::default(); + options.create_if_missing(true); + options.enable_statistics(); + let tx_db_options = TransactionDBOptions::default(); + let db = TransactionDB::open(&options, &tx_db_options, &path).unwrap(); + + db.put("key1", "value1").unwrap(); + db.put("key2", "value2").unwrap(); + db.put("key3", "value3").unwrap(); + + let prop_name: &std::ffi::CStr = properties::STATS; + let value = db.property_value(prop_name).unwrap().unwrap(); + + assert!(value.contains("Compaction Stats")); + assert!(value.contains("Cumulative writes: 3 writes")); + } +} + +#[test] +fn transaction_db_int_property_test() { + let path = DBPath::new("_rust_rocksdb_transaction_db_int_property_test"); + { + let mut options = Options::default(); + options.create_if_missing(true); + options.enable_statistics(); + let tx_db_options = TransactionDBOptions::default(); + let db = TransactionDB::open(&options, &tx_db_options, &path).unwrap(); + + db.put("key1", "value1").unwrap(); + db.put("key2", "value2").unwrap(); + + let prop_name: properties::PropertyName = properties::ESTIMATE_NUM_KEYS.to_owned(); + let value = db.property_int_value(&prop_name).unwrap().unwrap(); + + assert_eq!(value, 2); + } +}