|
|
@ -67,16 +67,18 @@ pub enum DBRecoveryMode { |
|
|
|
/// Making an atomic commit of several writes:
|
|
|
|
/// Making an atomic commit of several writes:
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
/// use rocksdb::{DB, WriteBatch};
|
|
|
|
/// use rocksdb::{DB, Options, WriteBatch};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let db = DB::open_default("path/for/rocksdb/storage1").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage1";
|
|
|
|
/// {
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut batch = WriteBatch::default();
|
|
|
|
/// let mut batch = WriteBatch::default();
|
|
|
|
/// batch.put(b"my key", b"my value");
|
|
|
|
/// batch.put(b"my key", b"my value");
|
|
|
|
/// batch.put(b"key2", b"value2");
|
|
|
|
/// batch.put(b"key2", b"value2");
|
|
|
|
/// batch.put(b"key3", b"value3");
|
|
|
|
/// batch.put(b"key3", b"value3");
|
|
|
|
/// db.write(batch); // Atomically commits the batch
|
|
|
|
/// db.write(batch); // Atomically commits the batch
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
pub struct WriteBatch { |
|
|
|
pub struct WriteBatch { |
|
|
|
inner: *mut ffi::rocksdb_writebatch_t, |
|
|
|
inner: *mut ffi::rocksdb_writebatch_t, |
|
|
@ -89,11 +91,15 @@ pub struct ReadOptions { |
|
|
|
/// A consistent view of the database at the point of creation.
|
|
|
|
/// A consistent view of the database at the point of creation.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
/// use rocksdb::{DB, IteratorMode};
|
|
|
|
/// use rocksdb::{DB, IteratorMode, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let db = DB::open_default("path/for/rocksdb/storage3").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage3";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let snapshot = db.snapshot(); // Creates a longer-term snapshot of the DB, but closed when goes out of scope
|
|
|
|
/// let snapshot = db.snapshot(); // Creates a longer-term snapshot of the DB, but closed when goes out of scope
|
|
|
|
/// let mut iter = snapshot.iterator(IteratorMode::Start); // Make as many iterators as you'd like from one snapshot
|
|
|
|
/// let mut iter = snapshot.iterator(IteratorMode::Start); // Make as many iterators as you'd like from one snapshot
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
///
|
|
|
|
pub struct Snapshot<'a> { |
|
|
|
pub struct Snapshot<'a> { |
|
|
@ -110,9 +116,11 @@ pub struct Snapshot<'a> { |
|
|
|
/// widely recognised Rust idioms.
|
|
|
|
/// widely recognised Rust idioms.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
/// use rocksdb::DB;
|
|
|
|
/// use rocksdb::{DB, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let mut db = DB::open_default("path/for/rocksdb/storage4").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage4";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Forwards iteration
|
|
|
|
/// // Forwards iteration
|
|
|
@ -144,6 +152,8 @@ pub struct Snapshot<'a> { |
|
|
|
/// println!("Saw {:?} {:?}", iter.key(), iter.value());
|
|
|
|
/// println!("Saw {:?} {:?}", iter.key(), iter.value());
|
|
|
|
/// iter.prev();
|
|
|
|
/// iter.prev();
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
pub struct DBRawIterator { |
|
|
|
pub struct DBRawIterator { |
|
|
|
inner: *mut ffi::rocksdb_iterator_t, |
|
|
|
inner: *mut ffi::rocksdb_iterator_t, |
|
|
@ -153,9 +163,11 @@ pub struct DBRawIterator { |
|
|
|
/// ranges and direction.
|
|
|
|
/// ranges and direction.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
/// use rocksdb::{DB, Direction, IteratorMode};
|
|
|
|
/// use rocksdb::{DB, Direction, IteratorMode, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let mut db = DB::open_default("path/for/rocksdb/storage2").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage2";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut iter = db.iterator(IteratorMode::Start); // Always iterates forward
|
|
|
|
/// let mut iter = db.iterator(IteratorMode::Start); // Always iterates forward
|
|
|
|
/// for (key, value) in iter {
|
|
|
|
/// for (key, value) in iter {
|
|
|
|
/// println!("Saw {:?} {:?}", key, value);
|
|
|
|
/// println!("Saw {:?} {:?}", key, value);
|
|
|
@ -175,6 +187,8 @@ pub struct DBRawIterator { |
|
|
|
/// for (key, value) in iter {
|
|
|
|
/// for (key, value) in iter {
|
|
|
|
/// println!("Saw {:?} {:?}", key, value);
|
|
|
|
/// println!("Saw {:?} {:?}", key, value);
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
pub struct DBIterator { |
|
|
|
pub struct DBIterator { |
|
|
|
raw: DBRawIterator, |
|
|
|
raw: DBRawIterator, |
|
|
@ -228,23 +242,22 @@ impl DBRawIterator { |
|
|
|
/// # Examples
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// ```rust
|
|
|
|
/// use rocksdb::DB;
|
|
|
|
/// use rocksdb::{DB, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let mut db = DB::open_default("path/for/rocksdb/storage5").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage5";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Iterate all keys from the start in lexicographic order
|
|
|
|
/// // Iterate all keys from the start in lexicographic order
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.seek_to_first();
|
|
|
|
/// iter.seek_to_first();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// while iter.valid() {
|
|
|
|
/// while iter.valid() {
|
|
|
|
/// println!("{:?} {:?}", iter.key(), iter.value());
|
|
|
|
/// println!("{:?} {:?}", iter.key(), iter.value());
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.next();
|
|
|
|
/// iter.next();
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Read just the first key
|
|
|
|
/// // Read just the first key
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.seek_to_first();
|
|
|
|
/// iter.seek_to_first();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// if iter.valid() {
|
|
|
|
/// if iter.valid() {
|
|
|
@ -252,6 +265,8 @@ impl DBRawIterator { |
|
|
|
/// } else {
|
|
|
|
/// } else {
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
pub fn seek_to_first(&mut self) { |
|
|
|
pub fn seek_to_first(&mut self) { |
|
|
|
unsafe { |
|
|
|
unsafe { |
|
|
@ -264,23 +279,22 @@ impl DBRawIterator { |
|
|
|
/// # Examples
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// ```rust
|
|
|
|
/// use rocksdb::DB;
|
|
|
|
/// use rocksdb::{DB, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let mut db = DB::open_default("path/for/rocksdb/storage6").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage6";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Iterate all keys from the end in reverse lexicographic order
|
|
|
|
/// // Iterate all keys from the end in reverse lexicographic order
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.seek_to_last();
|
|
|
|
/// iter.seek_to_last();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// while iter.valid() {
|
|
|
|
/// while iter.valid() {
|
|
|
|
/// println!("{:?} {:?}", iter.key(), iter.value());
|
|
|
|
/// println!("{:?} {:?}", iter.key(), iter.value());
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.prev();
|
|
|
|
/// iter.prev();
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Read just the last key
|
|
|
|
/// // Read just the last key
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.seek_to_last();
|
|
|
|
/// iter.seek_to_last();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// if iter.valid() {
|
|
|
|
/// if iter.valid() {
|
|
|
@ -288,6 +302,8 @@ impl DBRawIterator { |
|
|
|
/// } else {
|
|
|
|
/// } else {
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
pub fn seek_to_last(&mut self) { |
|
|
|
pub fn seek_to_last(&mut self) { |
|
|
|
unsafe { |
|
|
|
unsafe { |
|
|
@ -303,13 +319,14 @@ impl DBRawIterator { |
|
|
|
/// # Examples
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// ```rust
|
|
|
|
/// use rocksdb::DB;
|
|
|
|
/// use rocksdb::{DB, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let mut db = DB::open_default("path/for/rocksdb/storage7").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage7";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Read the first key that starts with 'a'
|
|
|
|
/// // Read the first key that starts with 'a'
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.seek(b"a");
|
|
|
|
/// iter.seek(b"a");
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// if iter.valid() {
|
|
|
|
/// if iter.valid() {
|
|
|
@ -317,6 +334,8 @@ impl DBRawIterator { |
|
|
|
/// } else {
|
|
|
|
/// } else {
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
/// ```
|
|
|
|
/// ```
|
|
|
|
pub fn seek(&mut self, key: &[u8]) { |
|
|
|
pub fn seek(&mut self, key: &[u8]) { |
|
|
|
unsafe { |
|
|
|
unsafe { |
|
|
@ -337,13 +356,14 @@ impl DBRawIterator { |
|
|
|
/// # Examples
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// ```rust
|
|
|
|
/// use rocksdb::DB;
|
|
|
|
/// use rocksdb::{DB, Options};
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// let mut db = DB::open_default("path/for/rocksdb/storage8").unwrap();
|
|
|
|
/// let path = "_path_for_rocksdb_storage8";
|
|
|
|
|
|
|
|
/// {
|
|
|
|
|
|
|
|
/// let db = DB::open_default(path).unwrap();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
/// let mut iter = db.raw_iterator();
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// // Read the last key that starts with 'a'
|
|
|
|
/// // Read the last key that starts with 'a'
|
|
|
|
///
|
|
|
|
|
|
|
|
/// iter.seek_for_prev(b"b");
|
|
|
|
/// iter.seek_for_prev(b"b");
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// if iter.valid() {
|
|
|
|
/// if iter.valid() {
|
|
|
@ -351,6 +371,9 @@ impl DBRawIterator { |
|
|
|
/// } else {
|
|
|
|
/// } else {
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// // There are no keys in the database
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// let _ = DB::destroy(&Options::default(), path);
|
|
|
|
|
|
|
|
/// ```
|
|
|
|
pub fn seek_for_prev(&mut self, key: &[u8]) { |
|
|
|
pub fn seek_for_prev(&mut self, key: &[u8]) { |
|
|
|
unsafe { |
|
|
|
unsafe { |
|
|
|
ffi::rocksdb_iter_seek_for_prev( |
|
|
|
ffi::rocksdb_iter_seek_for_prev( |
|
|
@ -640,9 +663,7 @@ impl DB { |
|
|
|
|
|
|
|
|
|
|
|
if let Err(e) = fs::create_dir_all(&path) { |
|
|
|
if let Err(e) = fs::create_dir_all(&path) { |
|
|
|
return Err(Error::new(format!( |
|
|
|
return Err(Error::new(format!( |
|
|
|
"Failed to create RocksDB\ |
|
|
|
"Failed to create RocksDB directory: `{:?}`.", e |
|
|
|
directory: `{:?}`.", |
|
|
|
|
|
|
|
e |
|
|
|
|
|
|
|
))); |
|
|
|
))); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -1395,6 +1416,39 @@ impl DBVector { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Retrieves a list of column families names from a given path.
|
|
|
|
|
|
|
|
pub fn get_cf_names<P: AsRef<Path>>(path: P) -> Result<Vec<String>, Error> { |
|
|
|
|
|
|
|
let opts = Options::default(); |
|
|
|
|
|
|
|
let cpath = to_cpath(path)?; |
|
|
|
|
|
|
|
let result: Vec<String>; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsafe { |
|
|
|
|
|
|
|
let mut cflen: size_t = 0; |
|
|
|
|
|
|
|
let column_fams_raw = ffi_try!(ffi::rocksdb_list_column_families( |
|
|
|
|
|
|
|
opts.inner, |
|
|
|
|
|
|
|
cpath.as_ptr() as *const _, |
|
|
|
|
|
|
|
&mut cflen, |
|
|
|
|
|
|
|
)); |
|
|
|
|
|
|
|
let column_fams = slice::from_raw_parts(column_fams_raw, cflen as usize); |
|
|
|
|
|
|
|
result = column_fams |
|
|
|
|
|
|
|
.iter() |
|
|
|
|
|
|
|
.map(|cf| CStr::from_ptr(*cf).to_string_lossy().into_owned()) |
|
|
|
|
|
|
|
.collect(); |
|
|
|
|
|
|
|
ffi::rocksdb_list_column_families_destroy(column_fams_raw, cflen); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ok(result) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn to_cpath<P: AsRef<Path>>(path: P) -> Result<CString, Error> { |
|
|
|
|
|
|
|
match CString::new(path.as_ref().to_string_lossy().as_bytes()) { |
|
|
|
|
|
|
|
Ok(c) => Ok(c), |
|
|
|
|
|
|
|
Err(_) => Err(Error::new( |
|
|
|
|
|
|
|
"Failed to convert path to CString when opening DB.".to_owned(), |
|
|
|
|
|
|
|
)), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
#[test] |
|
|
|
fn test_db_vector() { |
|
|
|
fn test_db_vector() { |
|
|
|
use std::mem; |
|
|
|
use std::mem; |
|
|
@ -1425,6 +1479,7 @@ fn external() { |
|
|
|
#[test] |
|
|
|
#[test] |
|
|
|
fn errors_do_stuff() { |
|
|
|
fn errors_do_stuff() { |
|
|
|
let path = "_rust_rocksdb_error"; |
|
|
|
let path = "_rust_rocksdb_error"; |
|
|
|
|
|
|
|
{ |
|
|
|
let _db = DB::open_default(path).unwrap(); |
|
|
|
let _db = DB::open_default(path).unwrap(); |
|
|
|
let opts = Options::default(); |
|
|
|
let opts = Options::default(); |
|
|
|
// The DB will still be open when we try to destroy it and the lock should fail.
|
|
|
|
// The DB will still be open when we try to destroy it and the lock should fail.
|
|
|
@ -1436,6 +1491,10 @@ fn errors_do_stuff() { |
|
|
|
} |
|
|
|
} |
|
|
|
Ok(_) => panic!("should fail"), |
|
|
|
Ok(_) => panic!("should fail"), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let opts = Options::default(); |
|
|
|
|
|
|
|
let result = DB::destroy(&opts, path); |
|
|
|
|
|
|
|
assert!(result.is_ok()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
#[test] |
|
|
@ -1562,4 +1621,31 @@ fn set_option_test() { |
|
|
|
]; |
|
|
|
]; |
|
|
|
db.set_options(&multiple_options).unwrap(); |
|
|
|
db.set_options(&multiple_options).unwrap(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
assert!(DB::destroy(&Options::default(), path).is_ok()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
|
|
|
fn get_cf_names_test() { |
|
|
|
|
|
|
|
let path = "_rust_rocksdb_get_cf_names"; |
|
|
|
|
|
|
|
let opts = Options::default(); |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
let db = DB::open_default(path).unwrap(); |
|
|
|
|
|
|
|
let cf_one = db.create_cf("one", &opts).unwrap(); |
|
|
|
|
|
|
|
let result = db.put_cf(cf_one, b"1", b"1"); |
|
|
|
|
|
|
|
assert!(result.is_ok()); |
|
|
|
|
|
|
|
let cf_two = db.create_cf("two", &opts).unwrap(); |
|
|
|
|
|
|
|
let result = db.put_cf(cf_two, b"2", b"2"); |
|
|
|
|
|
|
|
assert!(result.is_ok()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
let cf_names = get_cf_names(path).unwrap(); |
|
|
|
|
|
|
|
let cfs = cf_names.iter().map(String::as_str).collect::<Vec<_>>(); |
|
|
|
|
|
|
|
assert_eq!(cfs, vec!["default", "one", "two"]); |
|
|
|
|
|
|
|
let db = DB::open_cf(&opts, path, cfs.as_slice()).unwrap(); |
|
|
|
|
|
|
|
let cf_one = db.cf_handle("one").unwrap(); |
|
|
|
|
|
|
|
assert_eq!(db.get_cf(cf_one, b"1").unwrap().unwrap().as_ref(), b"1"); |
|
|
|
|
|
|
|
let cf_two = db.cf_handle("two").unwrap(); |
|
|
|
|
|
|
|
assert_eq!(db.get_cf(cf_two, b"2").unwrap().unwrap().as_ref(), b"2"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
assert!(DB::destroy(&opts, path).is_ok()); |
|
|
|
} |
|
|
|
} |
|
|
|