Skip to main content

rust_rocksdb/transactions/
transaction_db.rs

1// Copyright 2021 Yiyuan Liu
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use std::{
17    collections::BTreeMap,
18    ffi::CString,
19    fs, iter,
20    marker::PhantomData,
21    path::{Path, PathBuf},
22    ptr,
23    sync::{Arc, Mutex},
24};
25
26use crate::CStrLike;
27use std::ffi::CStr;
28
29use crate::column_family::ColumnFamilyTtl;
30use crate::{
31    AsColumnFamilyRef, BoundColumnFamily, ColumnFamily, ColumnFamilyDescriptor, DB,
32    DBIteratorWithThreadMode, DBPinnableSlice, DBRawIteratorWithThreadMode,
33    DEFAULT_COLUMN_FAMILY_NAME, Direction, Error, FlushOptions, FlushWalOptions, IteratorMode,
34    MultiThreaded, Options, ReadOptions, SingleThreaded, SnapshotWithThreadMode, ThreadMode,
35    Transaction, TransactionDBOptions, TransactionOptions, WriteBatchWithTransaction, WriteOptions,
36    column_family::UnboundColumnFamily,
37    db::{DBAccess, convert_values},
38    db_options::OptionsMustOutliveDB,
39    ffi,
40    ffi_util::to_cpath,
41};
42use ffi::rocksdb_transaction_t;
43use libc::{c_char, c_int, c_uchar, c_void, size_t};
44
45// Default options are kept per-thread to avoid re-allocating on every call while
46// also preventing cross-thread sharing. Some RocksDB option wrappers hold
47// pointers into internal buffers and are not safe to share across threads.
48// Using thread_local allows cheap reuse in the common "default options" path
49// without synchronization overhead. Callers who need non-defaults must pass
50// explicit options.
51thread_local! { static DEFAULT_READ_OPTS: ReadOptions = ReadOptions::default(); }
52thread_local! { static DEFAULT_WRITE_OPTS: WriteOptions = WriteOptions::default(); }
53thread_local! { static DEFAULT_FLUSH_OPTS: FlushOptions = FlushOptions::default(); }
54// `TransactionOptions::default()` is a C++ `new`/`delete` pair, so building one
55// per `transaction()` call put an allocation on the transaction-begin path.
56// `rocksdb_transaction_begin` only reads the options, so an immutable
57// thread-local instance is safe to share.
58thread_local! { static DEFAULT_TXN_OPTS: TransactionOptions = TransactionOptions::default(); }
59
60#[cfg(not(feature = "multi-threaded-cf"))]
61type DefaultThreadMode = crate::SingleThreaded;
62#[cfg(feature = "multi-threaded-cf")]
63type DefaultThreadMode = crate::MultiThreaded;
64
65/// RocksDB TransactionDB.
66///
67/// Please read the official [guide](https://github.com/facebook/rocksdb/wiki/Transactions)
68/// to learn more about RocksDB TransactionDB.
69///
70/// The default thread mode for [`TransactionDB`] is [`SingleThreaded`]
71/// if feature `multi-threaded-cf` is not enabled.
72///
73/// ```
74/// use rust_rocksdb::{DB, Options, TransactionDB, SingleThreaded};
75/// let tempdir = tempfile::Builder::new()
76///     .prefix("_path_for_transaction_db")
77///     .tempdir()
78///     .expect("Failed to create temporary path for the _path_for_transaction_db");
79/// let path = tempdir.path();
80/// {
81///     let db: TransactionDB = TransactionDB::open_default(path).unwrap();
82///     db.put(b"my key", b"my value").unwrap();
83///
84///     // create transaction
85///     let txn = db.transaction();
86///     txn.put(b"key2", b"value2");
87///     txn.put(b"key3", b"value3");
88///     txn.commit().unwrap();
89/// }
90/// let _ = DB::destroy(&Options::default(), path);
91/// ```
92///
93/// [`SingleThreaded`]: crate::SingleThreaded
94///
95/// A `Snapshot` must not outlive the `TransactionDB` it was created from:
96///
97/// ```compile_fail,E0597
98/// use rust_rocksdb::{SingleThreaded, TransactionDB};
99///
100/// let _snapshot = {
101///     let db = TransactionDB::<SingleThreaded>::open_default("foo").unwrap();
102///     db.snapshot()
103/// };
104/// ```
105pub struct TransactionDB<T: ThreadMode = DefaultThreadMode> {
106    pub(crate) inner: *mut ffi::rocksdb_transactiondb_t,
107    cfs: T,
108    path: PathBuf,
109    // prepared 2pc transactions.
110    prepared: Mutex<Vec<*mut rocksdb_transaction_t>>,
111    _outlive: Vec<OptionsMustOutliveDB>,
112}
113
114unsafe impl<T: ThreadMode> Send for TransactionDB<T> {}
115unsafe impl<T: ThreadMode> Sync for TransactionDB<T> {}
116
117impl<T: ThreadMode> DBAccess for TransactionDB<T> {
118    unsafe fn create_snapshot(&self) -> *const ffi::rocksdb_snapshot_t {
119        unsafe { ffi::rocksdb_transactiondb_create_snapshot(self.inner) }
120    }
121
122    unsafe fn release_snapshot(&self, snapshot: *const ffi::rocksdb_snapshot_t) {
123        unsafe {
124            ffi::rocksdb_transactiondb_release_snapshot(self.inner, snapshot);
125        }
126    }
127
128    unsafe fn create_iterator(&self, readopts: &ReadOptions) -> *mut ffi::rocksdb_iterator_t {
129        unsafe { ffi::rocksdb_transactiondb_create_iterator(self.inner, readopts.inner) }
130    }
131
132    unsafe fn create_iterator_cf(
133        &self,
134        cf_handle: *mut ffi::rocksdb_column_family_handle_t,
135        readopts: &ReadOptions,
136    ) -> *mut ffi::rocksdb_iterator_t {
137        unsafe {
138            ffi::rocksdb_transactiondb_create_iterator_cf(self.inner, readopts.inner, cf_handle)
139        }
140    }
141
142    fn get_opt<K: AsRef<[u8]>>(
143        &self,
144        key: K,
145        readopts: &ReadOptions,
146    ) -> Result<Option<Vec<u8>>, Error> {
147        self.get_opt(key, readopts)
148    }
149
150    fn get_cf_opt<K: AsRef<[u8]>>(
151        &self,
152        cf: &impl AsColumnFamilyRef,
153        key: K,
154        readopts: &ReadOptions,
155    ) -> Result<Option<Vec<u8>>, Error> {
156        self.get_cf_opt(cf, key, readopts)
157    }
158
159    fn get_pinned_opt<K: AsRef<[u8]>>(
160        &'_ self,
161        key: K,
162        readopts: &ReadOptions,
163    ) -> Result<Option<DBPinnableSlice<'_>>, Error> {
164        self.get_pinned_opt(key, readopts)
165    }
166
167    fn get_pinned_cf_opt<K: AsRef<[u8]>>(
168        &'_ self,
169        cf: &impl AsColumnFamilyRef,
170        key: K,
171        readopts: &ReadOptions,
172    ) -> Result<Option<DBPinnableSlice<'_>>, Error> {
173        self.get_pinned_cf_opt(cf, key, readopts)
174    }
175
176    fn multi_get_opt<K, I>(
177        &self,
178        keys: I,
179        readopts: &ReadOptions,
180    ) -> Vec<Result<Option<Vec<u8>>, Error>>
181    where
182        K: AsRef<[u8]>,
183        I: IntoIterator<Item = K>,
184    {
185        self.multi_get_opt(keys, readopts)
186    }
187
188    fn multi_get_cf_opt<'b, K, I, W>(
189        &self,
190        keys_cf: I,
191        readopts: &ReadOptions,
192    ) -> Vec<Result<Option<Vec<u8>>, Error>>
193    where
194        K: AsRef<[u8]>,
195        I: IntoIterator<Item = (&'b W, K)>,
196        W: AsColumnFamilyRef + 'b,
197    {
198        self.multi_get_cf_opt(keys_cf, readopts)
199    }
200}
201
202impl<T: ThreadMode> TransactionDB<T> {
203    /// Opens a database with default options.
204    pub fn open_default<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
205        let mut opts = Options::default();
206        opts.create_if_missing(true);
207        let txn_db_opts = TransactionDBOptions::default();
208        Self::open(&opts, &txn_db_opts, path)
209    }
210
211    /// Opens the database with the specified options.
212    pub fn open<P: AsRef<Path>>(
213        opts: &Options,
214        txn_db_opts: &TransactionDBOptions,
215        path: P,
216    ) -> Result<Self, Error> {
217        Self::open_cf(opts, txn_db_opts, path, None::<&str>)
218    }
219
220    /// Opens a database with the given database options and column family names.
221    ///
222    /// Column families opened using this function will be created with default `Options`.
223    pub fn open_cf<P, I, N>(
224        opts: &Options,
225        txn_db_opts: &TransactionDBOptions,
226        path: P,
227        cfs: I,
228    ) -> Result<Self, Error>
229    where
230        P: AsRef<Path>,
231        I: IntoIterator<Item = N>,
232        N: AsRef<str>,
233    {
234        let cfs = cfs
235            .into_iter()
236            .map(|name| ColumnFamilyDescriptor::new(name.as_ref(), Options::default()));
237
238        Self::open_cf_descriptors_internal(opts, txn_db_opts, path, cfs)
239    }
240
241    /// Opens a database with the given database options and column family descriptors.
242    pub fn open_cf_descriptors<P, I>(
243        opts: &Options,
244        txn_db_opts: &TransactionDBOptions,
245        path: P,
246        cfs: I,
247    ) -> Result<Self, Error>
248    where
249        P: AsRef<Path>,
250        I: IntoIterator<Item = ColumnFamilyDescriptor>,
251    {
252        Self::open_cf_descriptors_internal(opts, txn_db_opts, path, cfs)
253    }
254
255    /// Internal implementation for opening RocksDB.
256    fn open_cf_descriptors_internal<P, I>(
257        opts: &Options,
258        txn_db_opts: &TransactionDBOptions,
259        path: P,
260        cfs: I,
261    ) -> Result<Self, Error>
262    where
263        P: AsRef<Path>,
264        I: IntoIterator<Item = ColumnFamilyDescriptor>,
265    {
266        let cfs: Vec<_> = cfs.into_iter().collect();
267        let outlive = iter::once(opts.outlive.clone())
268            .chain(cfs.iter().map(|cf| cf.options.outlive.clone()))
269            .collect();
270
271        let cpath = to_cpath(&path)?;
272
273        if let Err(e) = fs::create_dir_all(&path) {
274            return Err(Error::new(format!(
275                "Failed to create RocksDB directory: `{e:?}`."
276            )));
277        }
278
279        let db: *mut ffi::rocksdb_transactiondb_t;
280        let mut cf_map = BTreeMap::new();
281
282        if cfs.is_empty() {
283            db = Self::open_raw(opts, txn_db_opts, &cpath)?;
284        } else {
285            let mut cfs_v = cfs;
286            // Always open the default column family.
287            if !cfs_v.iter().any(|cf| cf.name == DEFAULT_COLUMN_FAMILY_NAME) {
288                cfs_v.push(ColumnFamilyDescriptor {
289                    name: String::from(DEFAULT_COLUMN_FAMILY_NAME),
290                    options: Options::default(),
291                    ttl: ColumnFamilyTtl::SameAsDb, // it will have ttl specified in `DBWithThreadMode::open_with_ttl`
292                });
293            }
294            // We need to store our CStrings in an intermediate vector
295            // so that their pointers remain valid.
296            let c_cfs: Vec<CString> = cfs_v
297                .iter()
298                .map(|cf| CString::new(cf.name.as_bytes()).unwrap())
299                .collect();
300
301            let cfnames: Vec<_> = c_cfs.iter().map(|cf| cf.as_ptr()).collect();
302
303            // These handles will be populated by DB.
304            let mut cfhandles: Vec<_> = cfs_v.iter().map(|_| ptr::null_mut()).collect();
305
306            let cfopts: Vec<_> = cfs_v
307                .iter()
308                .map(|cf| cf.options.inner.cast_const())
309                .collect();
310
311            db = Self::open_cf_raw(
312                opts,
313                txn_db_opts,
314                &cpath,
315                &cfs_v,
316                &cfnames,
317                &cfopts,
318                &mut cfhandles,
319            )?;
320
321            for handle in &cfhandles {
322                if handle.is_null() {
323                    return Err(Error::new(
324                        "Received null column family handle from DB.".to_owned(),
325                    ));
326                }
327            }
328
329            for (cf_desc, inner) in cfs_v.iter().zip(cfhandles) {
330                cf_map.insert(cf_desc.name.clone(), inner);
331            }
332        }
333
334        if db.is_null() {
335            return Err(Error::new("Could not initialize database.".to_owned()));
336        }
337
338        let prepared = unsafe {
339            let mut cnt = 0;
340            let ptr = ffi::rocksdb_transactiondb_get_prepared_transactions(db, &raw mut cnt);
341            let mut vec = vec![std::ptr::null_mut(); cnt];
342            if !ptr.is_null() {
343                std::ptr::copy_nonoverlapping(ptr, vec.as_mut_ptr(), cnt);
344                ffi::rocksdb_free(ptr as *mut c_void);
345            }
346            vec
347        };
348
349        Ok(TransactionDB {
350            inner: db,
351            cfs: T::new_cf_map_internal(cf_map),
352            path: path.as_ref().to_path_buf(),
353            prepared: Mutex::new(prepared),
354            _outlive: outlive,
355        })
356    }
357
358    fn open_raw(
359        opts: &Options,
360        txn_db_opts: &TransactionDBOptions,
361        cpath: &CString,
362    ) -> Result<*mut ffi::rocksdb_transactiondb_t, Error> {
363        unsafe {
364            let db = ffi_try!(ffi::rocksdb_transactiondb_open(
365                opts.inner,
366                txn_db_opts.inner,
367                cpath.as_ptr()
368            ));
369            Ok(db)
370        }
371    }
372
373    fn open_cf_raw(
374        opts: &Options,
375        txn_db_opts: &TransactionDBOptions,
376        cpath: &CString,
377        cfs_v: &[ColumnFamilyDescriptor],
378        cfnames: &[*const c_char],
379        cfopts: &[*const ffi::rocksdb_options_t],
380        cfhandles: &mut [*mut ffi::rocksdb_column_family_handle_t],
381    ) -> Result<*mut ffi::rocksdb_transactiondb_t, Error> {
382        unsafe {
383            let db = ffi_try!(ffi::rocksdb_transactiondb_open_column_families(
384                opts.inner,
385                txn_db_opts.inner,
386                cpath.as_ptr(),
387                cfs_v.len() as c_int,
388                cfnames.as_ptr(),
389                cfopts.as_ptr(),
390                cfhandles.as_mut_ptr(),
391            ));
392            Ok(db)
393        }
394    }
395
396    fn create_inner_cf_handle(
397        &self,
398        name: &str,
399        opts: &Options,
400    ) -> Result<*mut ffi::rocksdb_column_family_handle_t, Error> {
401        let cf_name = CString::new(name.as_bytes()).map_err(|_| {
402            Error::new("Failed to convert path to CString when creating cf".to_owned())
403        })?;
404
405        Ok(unsafe {
406            ffi_try!(ffi::rocksdb_transactiondb_create_column_family(
407                self.inner,
408                opts.inner,
409                cf_name.as_ptr(),
410            ))
411        })
412    }
413
414    pub fn list_cf<P: AsRef<Path>>(opts: &Options, path: P) -> Result<Vec<String>, Error> {
415        DB::list_cf(opts, path)
416    }
417
418    pub fn destroy<P: AsRef<Path>>(opts: &Options, path: P) -> Result<(), Error> {
419        DB::destroy(opts, path)
420    }
421
422    pub fn repair<P: AsRef<Path>>(opts: &Options, path: P) -> Result<(), Error> {
423        DB::repair(opts, path)
424    }
425
426    pub fn path(&self) -> &Path {
427        self.path.as_path()
428    }
429
430    /// Flushes the WAL buffer. If `sync` is set to `true`, also syncs
431    /// the data to disk.
432    pub fn flush_wal(&self, sync: bool) -> Result<(), Error> {
433        unsafe {
434            ffi_try!(ffi::rocksdb_transactiondb_flush_wal(
435                self.inner,
436                c_uchar::from(sync)
437            ));
438        }
439        Ok(())
440    }
441
442    /// Flushes the WAL buffer using the given options.
443    ///
444    /// [`flush_wal`] only exposes the sync flag. Use this when the flush needs anything
445    /// else [`FlushWalOptions`] carries, such as the rate limiter priority.
446    ///
447    /// [`flush_wal`]: Self::flush_wal
448    pub fn flush_wal_opt(&self, flushopts: &FlushWalOptions) -> Result<(), Error> {
449        unsafe {
450            ffi_try!(ffi::rocksdb_transactiondb_flush_wal_with_options(
451                self.inner,
452                flushopts.as_ptr()
453            ));
454        }
455        Ok(())
456    }
457
458    /// Flushes database memtables to SST files on the disk.
459    pub fn flush_opt(&self, flushopts: &FlushOptions) -> Result<(), Error> {
460        unsafe {
461            ffi_try!(ffi::rocksdb_transactiondb_flush(
462                self.inner,
463                flushopts.inner
464            ));
465        }
466        Ok(())
467    }
468
469    /// Flushes database memtables to SST files on the disk using default options.
470    pub fn flush(&self) -> Result<(), Error> {
471        DEFAULT_FLUSH_OPTS.with(|opts| self.flush_opt(opts))
472    }
473
474    /// Flushes database memtables to SST files on the disk for a given column family.
475    pub fn flush_cf_opt(
476        &self,
477        cf: &impl AsColumnFamilyRef,
478        flushopts: &FlushOptions,
479    ) -> Result<(), Error> {
480        unsafe {
481            ffi_try!(ffi::rocksdb_transactiondb_flush_cf(
482                self.inner,
483                flushopts.inner,
484                cf.inner()
485            ));
486        }
487        Ok(())
488    }
489
490    /// Flushes multiple column families.
491    ///
492    /// If atomic flush is not enabled, it is equivalent to calling flush_cf multiple times.
493    /// If atomic flush is enabled, it will flush all column families specified in `cfs` up to the latest sequence
494    /// number at the time when flush is requested.
495    pub fn flush_cfs_opt(
496        &self,
497        cfs: &[&impl AsColumnFamilyRef],
498        opts: &FlushOptions,
499    ) -> Result<(), Error> {
500        let mut cfs = cfs.iter().map(|cf| cf.inner()).collect::<Vec<_>>();
501        unsafe {
502            ffi_try!(ffi::rocksdb_transactiondb_flush_cfs(
503                self.inner,
504                opts.inner,
505                cfs.as_mut_ptr(),
506                cfs.len() as c_int,
507            ));
508        }
509        Ok(())
510    }
511
512    /// Flushes database memtables to SST files on the disk for a given column family using default
513    /// options.
514    pub fn flush_cf(&self, cf: &impl AsColumnFamilyRef) -> Result<(), Error> {
515        DEFAULT_FLUSH_OPTS.with(|opts| self.flush_cf_opt(cf, opts))
516    }
517
518    /// Creates a transaction with default options.
519    pub fn transaction(&'_ self) -> Transaction<'_, Self> {
520        DEFAULT_WRITE_OPTS.with(|write_opts| {
521            DEFAULT_TXN_OPTS.with(|txn_opts| self.transaction_opt(write_opts, txn_opts))
522        })
523    }
524
525    /// Creates a transaction with options.
526    pub fn transaction_opt<'a>(
527        &'a self,
528        write_opts: &WriteOptions,
529        txn_opts: &TransactionOptions,
530    ) -> Transaction<'a, Self> {
531        Transaction {
532            inner: unsafe {
533                ffi::rocksdb_transaction_begin(
534                    self.inner,
535                    write_opts.inner,
536                    txn_opts.inner,
537                    std::ptr::null_mut(),
538                )
539            },
540            _marker: PhantomData,
541        }
542    }
543
544    /// Get all prepared transactions for recovery.
545    ///
546    /// This function is expected to call once after open database.
547    /// User should commit or rollback all transactions before start other transactions.
548    pub fn prepared_transactions(&'_ self) -> Vec<Transaction<'_, Self>> {
549        self.prepared
550            .lock()
551            .unwrap()
552            .drain(0..)
553            .map(|inner| Transaction {
554                inner,
555                _marker: PhantomData,
556            })
557            .collect()
558    }
559
560    /// Returns the bytes associated with a key value.
561    pub fn get<K: AsRef<[u8]>>(&self, key: K) -> Result<Option<Vec<u8>>, Error> {
562        self.get_pinned(key).map(|x| x.map(|v| v.as_ref().to_vec()))
563    }
564
565    /// Returns the bytes associated with a key value and the given column family.
566    pub fn get_cf<K: AsRef<[u8]>>(
567        &self,
568        cf: &impl AsColumnFamilyRef,
569        key: K,
570    ) -> Result<Option<Vec<u8>>, Error> {
571        self.get_pinned_cf(cf, key)
572            .map(|x| x.map(|v| v.as_ref().to_vec()))
573    }
574
575    /// Returns the bytes associated with a key value with read options.
576    pub fn get_opt<K: AsRef<[u8]>>(
577        &self,
578        key: K,
579        readopts: &ReadOptions,
580    ) -> Result<Option<Vec<u8>>, Error> {
581        self.get_pinned_opt(key, readopts)
582            .map(|x| x.map(|v| v.as_ref().to_vec()))
583    }
584
585    /// Returns the bytes associated with a key value and the given column family with read options.
586    pub fn get_cf_opt<K: AsRef<[u8]>>(
587        &self,
588        cf: &impl AsColumnFamilyRef,
589        key: K,
590        readopts: &ReadOptions,
591    ) -> Result<Option<Vec<u8>>, Error> {
592        self.get_pinned_cf_opt(cf, key, readopts)
593            .map(|x| x.map(|v| v.as_ref().to_vec()))
594    }
595
596    pub fn get_pinned<K: AsRef<[u8]>>(
597        &'_ self,
598        key: K,
599    ) -> Result<Option<DBPinnableSlice<'_>>, Error> {
600        DEFAULT_READ_OPTS.with(|opts| self.get_pinned_opt(key, opts))
601    }
602
603    /// Returns the bytes associated with a key value and the given column family.
604    pub fn get_pinned_cf<K: AsRef<[u8]>>(
605        &'_ self,
606        cf: &impl AsColumnFamilyRef,
607        key: K,
608    ) -> Result<Option<DBPinnableSlice<'_>>, Error> {
609        DEFAULT_READ_OPTS.with(|opts| self.get_pinned_cf_opt(cf, key, opts))
610    }
611
612    /// Returns the bytes associated with a key value with read options.
613    pub fn get_pinned_opt<K: AsRef<[u8]>>(
614        &'_ self,
615        key: K,
616        readopts: &ReadOptions,
617    ) -> Result<Option<DBPinnableSlice<'_>>, Error> {
618        let key = key.as_ref();
619        unsafe {
620            let val = ffi_try!(ffi::rocksdb_transactiondb_get_pinned(
621                self.inner,
622                readopts.inner,
623                key.as_ptr() as *const c_char,
624                key.len() as size_t,
625            ));
626            if val.is_null() {
627                Ok(None)
628            } else {
629                Ok(Some(DBPinnableSlice::from_c(val)))
630            }
631        }
632    }
633
634    /// Returns the bytes associated with a key value and the given column family with read options.
635    pub fn get_pinned_cf_opt<K: AsRef<[u8]>>(
636        &'_ self,
637        cf: &impl AsColumnFamilyRef,
638        key: K,
639        readopts: &ReadOptions,
640    ) -> Result<Option<DBPinnableSlice<'_>>, Error> {
641        let key = key.as_ref();
642        unsafe {
643            let val = ffi_try!(ffi::rocksdb_transactiondb_get_pinned_cf(
644                self.inner,
645                readopts.inner,
646                cf.inner(),
647                key.as_ptr() as *const c_char,
648                key.len() as size_t,
649            ));
650            if val.is_null() {
651                Ok(None)
652            } else {
653                Ok(Some(DBPinnableSlice::from_c(val)))
654            }
655        }
656    }
657
658    /// Return the values associated with the given keys.
659    pub fn multi_get<K, I>(&self, keys: I) -> Vec<Result<Option<Vec<u8>>, Error>>
660    where
661        K: AsRef<[u8]>,
662        I: IntoIterator<Item = K>,
663    {
664        DEFAULT_READ_OPTS.with(|opts| self.multi_get_opt(keys, opts))
665    }
666
667    /// Return the values associated with the given keys using read options.
668    pub fn multi_get_opt<K, I>(
669        &self,
670        keys: I,
671        readopts: &ReadOptions,
672    ) -> Vec<Result<Option<Vec<u8>>, Error>>
673    where
674        K: AsRef<[u8]>,
675        I: IntoIterator<Item = K>,
676    {
677        let owned_keys: Vec<K> = keys.into_iter().collect();
678        let (ptr_keys, keys_sizes): (Vec<*const c_char>, Vec<usize>) = owned_keys
679            .iter()
680            .map(|k| {
681                let key = k.as_ref();
682                (key.as_ptr() as *const c_char, key.len())
683            })
684            .unzip();
685
686        let mut values: Vec<*mut c_char> = Vec::with_capacity(ptr_keys.len());
687        let mut values_sizes: Vec<usize> = Vec::with_capacity(ptr_keys.len());
688        let mut errors: Vec<*mut c_char> = Vec::with_capacity(ptr_keys.len());
689        unsafe {
690            ffi::rocksdb_transactiondb_multi_get(
691                self.inner,
692                readopts.inner,
693                ptr_keys.len(),
694                ptr_keys.as_ptr(),
695                keys_sizes.as_ptr(),
696                values.as_mut_ptr(),
697                values_sizes.as_mut_ptr(),
698                errors.as_mut_ptr(),
699            );
700        }
701
702        unsafe {
703            values.set_len(ptr_keys.len());
704            values_sizes.set_len(ptr_keys.len());
705            errors.set_len(ptr_keys.len());
706        }
707
708        convert_values(values, values_sizes, errors)
709    }
710
711    /// Return the values associated with the given keys and column families.
712    pub fn multi_get_cf<'a, 'b: 'a, K, I, W>(
713        &'a self,
714        keys: I,
715    ) -> Vec<Result<Option<Vec<u8>>, Error>>
716    where
717        K: AsRef<[u8]>,
718        I: IntoIterator<Item = (&'b W, K)>,
719        W: 'b + AsColumnFamilyRef,
720    {
721        DEFAULT_READ_OPTS.with(|opts| self.multi_get_cf_opt(keys, opts))
722    }
723
724    /// Return the values associated with the given keys and column families using read options.
725    pub fn multi_get_cf_opt<'a, 'b: 'a, K, I, W>(
726        &'a self,
727        keys: I,
728        readopts: &ReadOptions,
729    ) -> Vec<Result<Option<Vec<u8>>, Error>>
730    where
731        K: AsRef<[u8]>,
732        I: IntoIterator<Item = (&'b W, K)>,
733        W: 'b + AsColumnFamilyRef,
734    {
735        let cfs_and_owned_keys: Vec<(&'b W, K)> = keys.into_iter().collect();
736        let (ptr_keys, keys_sizes): (Vec<*const c_char>, Vec<usize>) = cfs_and_owned_keys
737            .iter()
738            .map(|(_, k)| {
739                let key = k.as_ref();
740                (key.as_ptr() as *const c_char, key.len())
741            })
742            .unzip();
743        let ptr_cfs: Vec<*const ffi::rocksdb_column_family_handle_t> = cfs_and_owned_keys
744            .iter()
745            .map(|(c, _)| c.inner().cast_const())
746            .collect();
747        let mut values: Vec<*mut c_char> = Vec::with_capacity(ptr_keys.len());
748        let mut values_sizes: Vec<usize> = Vec::with_capacity(ptr_keys.len());
749        let mut errors: Vec<*mut c_char> = Vec::with_capacity(ptr_keys.len());
750        unsafe {
751            ffi::rocksdb_transactiondb_multi_get_cf(
752                self.inner,
753                readopts.inner,
754                ptr_cfs.as_ptr(),
755                ptr_keys.len(),
756                ptr_keys.as_ptr(),
757                keys_sizes.as_ptr(),
758                values.as_mut_ptr(),
759                values_sizes.as_mut_ptr(),
760                errors.as_mut_ptr(),
761            );
762        }
763
764        unsafe {
765            values.set_len(ptr_keys.len());
766            values_sizes.set_len(ptr_keys.len());
767            errors.set_len(ptr_keys.len());
768        }
769
770        convert_values(values, values_sizes, errors)
771    }
772
773    pub fn put<K, V>(&self, key: K, value: V) -> Result<(), Error>
774    where
775        K: AsRef<[u8]>,
776        V: AsRef<[u8]>,
777    {
778        DEFAULT_WRITE_OPTS.with(|opts| self.put_opt(key, value, opts))
779    }
780
781    pub fn put_cf<K, V>(&self, cf: &impl AsColumnFamilyRef, key: K, value: V) -> Result<(), Error>
782    where
783        K: AsRef<[u8]>,
784        V: AsRef<[u8]>,
785    {
786        DEFAULT_WRITE_OPTS.with(|opts| self.put_cf_opt(cf, key, value, opts))
787    }
788
789    pub fn put_opt<K, V>(&self, key: K, value: V, writeopts: &WriteOptions) -> Result<(), Error>
790    where
791        K: AsRef<[u8]>,
792        V: AsRef<[u8]>,
793    {
794        let key = key.as_ref();
795        let value = value.as_ref();
796        unsafe {
797            ffi_try!(ffi::rocksdb_transactiondb_put(
798                self.inner,
799                writeopts.inner,
800                key.as_ptr() as *const c_char,
801                key.len() as size_t,
802                value.as_ptr() as *const c_char,
803                value.len() as size_t
804            ));
805        }
806        Ok(())
807    }
808
809    pub fn put_cf_opt<K, V>(
810        &self,
811        cf: &impl AsColumnFamilyRef,
812        key: K,
813        value: V,
814        writeopts: &WriteOptions,
815    ) -> Result<(), Error>
816    where
817        K: AsRef<[u8]>,
818        V: AsRef<[u8]>,
819    {
820        let key = key.as_ref();
821        let value = value.as_ref();
822        unsafe {
823            ffi_try!(ffi::rocksdb_transactiondb_put_cf(
824                self.inner,
825                writeopts.inner,
826                cf.inner(),
827                key.as_ptr() as *const c_char,
828                key.len() as size_t,
829                value.as_ptr() as *const c_char,
830                value.len() as size_t
831            ));
832        }
833        Ok(())
834    }
835
836    pub fn write(&self, batch: &WriteBatchWithTransaction<true>) -> Result<(), Error> {
837        DEFAULT_WRITE_OPTS.with(|opts| self.write_opt(batch, opts))
838    }
839
840    pub fn write_opt(
841        &self,
842        batch: &WriteBatchWithTransaction<true>,
843        writeopts: &WriteOptions,
844    ) -> Result<(), Error> {
845        unsafe {
846            ffi_try!(ffi::rocksdb_transactiondb_write(
847                self.inner,
848                writeopts.inner,
849                batch.inner
850            ));
851        }
852        Ok(())
853    }
854
855    pub fn merge<K, V>(&self, key: K, value: V) -> Result<(), Error>
856    where
857        K: AsRef<[u8]>,
858        V: AsRef<[u8]>,
859    {
860        DEFAULT_WRITE_OPTS.with(|opts| self.merge_opt(key, value, opts))
861    }
862
863    pub fn merge_cf<K, V>(&self, cf: &impl AsColumnFamilyRef, key: K, value: V) -> Result<(), Error>
864    where
865        K: AsRef<[u8]>,
866        V: AsRef<[u8]>,
867    {
868        DEFAULT_WRITE_OPTS.with(|opts| self.merge_cf_opt(cf, key, value, opts))
869    }
870
871    pub fn merge_opt<K, V>(&self, key: K, value: V, writeopts: &WriteOptions) -> Result<(), Error>
872    where
873        K: AsRef<[u8]>,
874        V: AsRef<[u8]>,
875    {
876        let key = key.as_ref();
877        let value = value.as_ref();
878        unsafe {
879            ffi_try!(ffi::rocksdb_transactiondb_merge(
880                self.inner,
881                writeopts.inner,
882                key.as_ptr() as *const c_char,
883                key.len() as size_t,
884                value.as_ptr() as *const c_char,
885                value.len() as size_t,
886            ));
887            Ok(())
888        }
889    }
890
891    pub fn merge_cf_opt<K, V>(
892        &self,
893        cf: &impl AsColumnFamilyRef,
894        key: K,
895        value: V,
896        writeopts: &WriteOptions,
897    ) -> Result<(), Error>
898    where
899        K: AsRef<[u8]>,
900        V: AsRef<[u8]>,
901    {
902        let key = key.as_ref();
903        let value = value.as_ref();
904        unsafe {
905            ffi_try!(ffi::rocksdb_transactiondb_merge_cf(
906                self.inner,
907                writeopts.inner,
908                cf.inner(),
909                key.as_ptr() as *const c_char,
910                key.len() as size_t,
911                value.as_ptr() as *const c_char,
912                value.len() as size_t,
913            ));
914            Ok(())
915        }
916    }
917
918    pub fn delete<K: AsRef<[u8]>>(&self, key: K) -> Result<(), Error> {
919        DEFAULT_WRITE_OPTS.with(|opts| self.delete_opt(key, opts))
920    }
921
922    pub fn delete_cf<K: AsRef<[u8]>>(
923        &self,
924        cf: &impl AsColumnFamilyRef,
925        key: K,
926    ) -> Result<(), Error> {
927        DEFAULT_WRITE_OPTS.with(|opts| self.delete_cf_opt(cf, key, opts))
928    }
929
930    pub fn delete_opt<K: AsRef<[u8]>>(
931        &self,
932        key: K,
933        writeopts: &WriteOptions,
934    ) -> Result<(), Error> {
935        let key = key.as_ref();
936        unsafe {
937            ffi_try!(ffi::rocksdb_transactiondb_delete(
938                self.inner,
939                writeopts.inner,
940                key.as_ptr() as *const c_char,
941                key.len() as size_t,
942            ));
943        }
944        Ok(())
945    }
946
947    pub fn delete_cf_opt<K: AsRef<[u8]>>(
948        &self,
949        cf: &impl AsColumnFamilyRef,
950        key: K,
951        writeopts: &WriteOptions,
952    ) -> Result<(), Error> {
953        let key = key.as_ref();
954        unsafe {
955            ffi_try!(ffi::rocksdb_transactiondb_delete_cf(
956                self.inner,
957                writeopts.inner,
958                cf.inner(),
959                key.as_ptr() as *const c_char,
960                key.len() as size_t,
961            ));
962        }
963        Ok(())
964    }
965
966    pub fn iterator<'a: 'b, 'b>(
967        &'a self,
968        mode: IteratorMode,
969    ) -> DBIteratorWithThreadMode<'b, Self> {
970        let readopts = ReadOptions::default();
971        self.iterator_opt(mode, readopts)
972    }
973
974    pub fn iterator_opt<'a: 'b, 'b>(
975        &'a self,
976        mode: IteratorMode,
977        readopts: ReadOptions,
978    ) -> DBIteratorWithThreadMode<'b, Self> {
979        DBIteratorWithThreadMode::new(self, readopts, mode)
980    }
981
982    /// Opens an iterator using the provided ReadOptions.
983    /// This is used when you want to iterate over a specific ColumnFamily with a modified ReadOptions
984    pub fn iterator_cf_opt<'a: 'b, 'b>(
985        &'a self,
986        cf_handle: &impl AsColumnFamilyRef,
987        readopts: ReadOptions,
988        mode: IteratorMode,
989    ) -> DBIteratorWithThreadMode<'b, Self> {
990        DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), readopts, mode)
991    }
992
993    /// Opens an iterator with `set_total_order_seek` enabled.
994    /// This must be used to iterate across prefixes when `set_memtable_factory` has been called
995    /// with a Hash-based implementation.
996    pub fn full_iterator<'a: 'b, 'b>(
997        &'a self,
998        mode: IteratorMode,
999    ) -> DBIteratorWithThreadMode<'b, Self> {
1000        let mut opts = ReadOptions::default();
1001        opts.set_total_order_seek(true);
1002        DBIteratorWithThreadMode::new(self, opts, mode)
1003    }
1004
1005    pub fn prefix_iterator<'a: 'b, 'b, P: AsRef<[u8]>>(
1006        &'a self,
1007        prefix: P,
1008    ) -> DBIteratorWithThreadMode<'b, Self> {
1009        let mut opts = ReadOptions::default();
1010        opts.set_prefix_same_as_start(true);
1011        DBIteratorWithThreadMode::new(
1012            self,
1013            opts,
1014            IteratorMode::From(prefix.as_ref(), Direction::Forward),
1015        )
1016    }
1017
1018    pub fn iterator_cf<'a: 'b, 'b>(
1019        &'a self,
1020        cf_handle: &impl AsColumnFamilyRef,
1021        mode: IteratorMode,
1022    ) -> DBIteratorWithThreadMode<'b, Self> {
1023        let opts = ReadOptions::default();
1024        DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts, mode)
1025    }
1026
1027    pub fn full_iterator_cf<'a: 'b, 'b>(
1028        &'a self,
1029        cf_handle: &impl AsColumnFamilyRef,
1030        mode: IteratorMode,
1031    ) -> DBIteratorWithThreadMode<'b, Self> {
1032        let mut opts = ReadOptions::default();
1033        opts.set_total_order_seek(true);
1034        DBIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts, mode)
1035    }
1036
1037    pub fn prefix_iterator_cf<'a, P: AsRef<[u8]>>(
1038        &'a self,
1039        cf_handle: &impl AsColumnFamilyRef,
1040        prefix: P,
1041    ) -> DBIteratorWithThreadMode<'a, Self> {
1042        let mut opts = ReadOptions::default();
1043        opts.set_prefix_same_as_start(true);
1044        DBIteratorWithThreadMode::<'a, Self>::new_cf(
1045            self,
1046            cf_handle.inner(),
1047            opts,
1048            IteratorMode::From(prefix.as_ref(), Direction::Forward),
1049        )
1050    }
1051
1052    /// Opens a raw iterator over the database, using the default read options
1053    pub fn raw_iterator<'a: 'b, 'b>(&'a self) -> DBRawIteratorWithThreadMode<'b, Self> {
1054        let opts = ReadOptions::default();
1055        DBRawIteratorWithThreadMode::new(self, opts)
1056    }
1057
1058    /// Opens a raw iterator over the given column family, using the default read options
1059    pub fn raw_iterator_cf<'a: 'b, 'b>(
1060        &'a self,
1061        cf_handle: &impl AsColumnFamilyRef,
1062    ) -> DBRawIteratorWithThreadMode<'b, Self> {
1063        let opts = ReadOptions::default();
1064        DBRawIteratorWithThreadMode::new_cf(self, cf_handle.inner(), opts)
1065    }
1066
1067    /// Opens a raw iterator over the database, using the given read options
1068    pub fn raw_iterator_opt<'a: 'b, 'b>(
1069        &'a self,
1070        readopts: ReadOptions,
1071    ) -> DBRawIteratorWithThreadMode<'b, Self> {
1072        DBRawIteratorWithThreadMode::new(self, readopts)
1073    }
1074
1075    /// Opens a raw iterator over the given column family, using the given read options
1076    pub fn raw_iterator_cf_opt<'a: 'b, 'b>(
1077        &'a self,
1078        cf_handle: &impl AsColumnFamilyRef,
1079        readopts: ReadOptions,
1080    ) -> DBRawIteratorWithThreadMode<'b, Self> {
1081        DBRawIteratorWithThreadMode::new_cf(self, cf_handle.inner(), readopts)
1082    }
1083
1084    pub fn snapshot(&'_ self) -> SnapshotWithThreadMode<'_, Self> {
1085        SnapshotWithThreadMode::<Self>::new(self)
1086    }
1087
1088    /// Marks the column family as dropped in RocksDB.
1089    ///
1090    /// Deliberately does not take ownership of the handle. Callers must take
1091    /// the handle out of their map first, so that only one caller can ever
1092    /// *destroy* a given handle, and must put it back if this fails: destroying
1093    /// it on failure would leave the column family still present in the DB with
1094    /// no reachable handle.
1095    ///
1096    /// Other threads can still be holding a live `BoundColumnFamily` clone for
1097    /// this handle; the `Arc` refcount keeps it alive until the last one drops.
1098    fn mark_column_family_dropped(
1099        &self,
1100        cf_inner: *mut ffi::rocksdb_column_family_handle_t,
1101    ) -> Result<(), Error> {
1102        unsafe {
1103            ffi_try!(ffi::rocksdb_drop_column_family(
1104                self.inner as *mut ffi::rocksdb_t,
1105                cf_inner
1106            ));
1107        }
1108        Ok(())
1109    }
1110}
1111
1112impl TransactionDB<SingleThreaded> {
1113    /// Creates column family with given name and options.
1114    pub fn create_cf<N: AsRef<str>>(&mut self, name: N, opts: &Options) -> Result<(), Error> {
1115        let inner = self.create_inner_cf_handle(name.as_ref(), opts)?;
1116        self.cfs
1117            .cfs
1118            .insert(name.as_ref().to_string(), ColumnFamily { inner });
1119        Ok(())
1120    }
1121
1122    /// Returns the underlying column family handle.
1123    pub fn cf_handle(&self, name: &str) -> Option<&ColumnFamily> {
1124        self.cfs.cfs.get(name)
1125    }
1126
1127    /// Drops the column family with the given name
1128    pub fn drop_cf(&mut self, name: &str) -> Result<(), Error> {
1129        let Some(cf) = self.cfs.cfs.remove(name) else {
1130            return Err(Error::new(format!("Invalid column family: {name}")));
1131        };
1132        match self.mark_column_family_dropped(cf.inner) {
1133            // `cf` is dropped here. In single-threaded mode that destroys the
1134            // handle; in `MultiThreaded` mode it drops one `Arc` reference and
1135            // the handle is destroyed once the last `BoundColumnFamily` clone
1136            // handed out by `cf_handle` is gone.
1137            Ok(()) => Ok(()),
1138            Err(e) => {
1139                // The column family is still there, so put the handle back
1140                // rather than destroying the only way to reach it.
1141                self.cfs.cfs.insert(name.to_owned(), cf);
1142                Err(e)
1143            }
1144        }
1145    }
1146}
1147
1148impl TransactionDB<MultiThreaded> {
1149    /// Creates column family with given name and options.
1150    pub fn create_cf<N: AsRef<str>>(&self, name: N, opts: &Options) -> Result<(), Error> {
1151        // Note that we acquire the cfs lock before inserting: otherwise we might race
1152        // another caller who observed the handle as missing.
1153        let mut cfs = self.cfs.cfs.write();
1154        let inner = self.create_inner_cf_handle(name.as_ref(), opts)?;
1155        cfs.insert(
1156            name.as_ref().to_string(),
1157            Arc::new(UnboundColumnFamily { inner }),
1158        );
1159        Ok(())
1160    }
1161
1162    /// Returns the underlying column family handle.
1163    pub fn cf_handle(&'_ self, name: &str) -> Option<Arc<BoundColumnFamily<'_>>> {
1164        self.cfs
1165            .cfs
1166            .read()
1167            .get(name)
1168            .cloned()
1169            .map(UnboundColumnFamily::bound_column_family)
1170    }
1171
1172    /// Drops the column family with the given name by internally locking the inner column
1173    /// family map. This avoids needing `&mut self` reference
1174    pub fn drop_cf(&self, name: &str) -> Result<(), Error> {
1175        // Take the handle out under the write lock before touching RocksDB.
1176        // Looking it up under a read lock and removing it afterwards would let
1177        // two concurrent callers observe the same handle: the first would drop
1178        // and destroy it, and the second would then hand a freed pointer to
1179        // `rocksdb_drop_column_family`.
1180        let Some(cf) = self.cfs.cfs.write().remove(name) else {
1181            return Err(Error::new(format!("Invalid column family: {name}")));
1182        };
1183        match self.mark_column_family_dropped(cf.inner) {
1184            // `cf` is dropped here. In single-threaded mode that destroys the
1185            // handle; in `MultiThreaded` mode it drops one `Arc` reference and
1186            // the handle is destroyed once the last `BoundColumnFamily` clone
1187            // handed out by `cf_handle` is gone.
1188            Ok(()) => Ok(()),
1189            Err(e) => {
1190                // The column family is still there, so put the handle back
1191                // rather than destroying the only way to reach it.
1192                self.cfs.cfs.write().insert(name.to_owned(), cf);
1193                Err(e)
1194            }
1195        }
1196    }
1197
1198    /// Implementation for property_value et al methods.
1199    ///
1200    /// `name` is the name of the property.  It will be converted into a CString
1201    /// and passed to `get_property` as argument.  `get_property` reads the
1202    /// specified property and either returns NULL or a pointer to a C allocated
1203    /// string; this method takes ownership of that string and will free it at
1204    /// the end. That string is parsed using `parse` callback which produces
1205    /// the returned result.
1206    fn property_value_impl<R>(
1207        name: impl CStrLike,
1208        get_property: impl FnOnce(*const c_char) -> *mut c_char,
1209        parse: impl FnOnce(&str) -> Result<R, Error>,
1210    ) -> Result<Option<R>, Error> {
1211        let value = match name.bake() {
1212            Ok(prop_name) => get_property(prop_name.as_ptr()),
1213            Err(e) => {
1214                return Err(Error::new(format!(
1215                    "Failed to convert property name to CString: {e}"
1216                )));
1217            }
1218        };
1219        if value.is_null() {
1220            return Ok(None);
1221        }
1222        let result = match unsafe { CStr::from_ptr(value) }.to_str() {
1223            Ok(s) => parse(s).map(|value| Some(value)),
1224            Err(e) => Err(Error::new(format!(
1225                "Failed to convert property value to string: {e}"
1226            ))),
1227        };
1228        unsafe {
1229            ffi::rocksdb_free(value as *mut c_void);
1230        }
1231        result
1232    }
1233
1234    /// Retrieves a RocksDB property by name.
1235    ///
1236    /// Full list of properties could be find
1237    /// [here](https://github.com/facebook/rocksdb/blob/08809f5e6cd9cc4bc3958dd4d59457ae78c76660/include/rocksdb/db.h#L428-L634).
1238    pub fn property_value(&self, name: impl CStrLike) -> Result<Option<String>, Error> {
1239        Self::property_value_impl(
1240            name,
1241            |prop_name| unsafe { ffi::rocksdb_transactiondb_property_value(self.inner, prop_name) },
1242            |str_value| Ok(str_value.to_owned()),
1243        )
1244    }
1245
1246    fn property_int_value_impl(
1247        name: impl CStrLike,
1248        get_property: impl FnOnce(*const c_char, *mut u64) -> c_int,
1249        get_string_property: impl FnOnce(*const c_char) -> *mut c_char,
1250    ) -> Result<Option<u64>, Error> {
1251        let prop_name = name.bake().map_err(|err| {
1252            Error::new(format!("Failed to convert property name to CString: {err}"))
1253        })?;
1254        let mut value = 0;
1255        if get_property(prop_name.as_ptr(), &raw mut value) == 0 {
1256            return Ok(Some(value));
1257        }
1258
1259        Self::property_value_impl(
1260            prop_name.as_ref(),
1261            get_string_property,
1262            Self::parse_property_int_value,
1263        )
1264    }
1265
1266    fn parse_property_int_value(value: &str) -> Result<u64, Error> {
1267        value.parse::<u64>().map_err(|err| {
1268            Error::new(format!(
1269                "Failed to convert property value {value} to int: {err}"
1270            ))
1271        })
1272    }
1273
1274    /// Retrieves a RocksDB property and casts it to an integer.
1275    ///
1276    /// Full list of properties that return int values could be find
1277    /// [here](https://github.com/facebook/rocksdb/blob/08809f5e6cd9cc4bc3958dd4d59457ae78c76660/include/rocksdb/db.h#L654-L689).
1278    pub fn property_int_value(&self, name: impl CStrLike) -> Result<Option<u64>, Error> {
1279        Self::property_int_value_impl(
1280            name,
1281            |prop_name, value| unsafe {
1282                ffi::rocksdb_transactiondb_property_int(self.inner, prop_name, value)
1283            },
1284            |prop_name| unsafe { ffi::rocksdb_transactiondb_property_value(self.inner, prop_name) },
1285        )
1286    }
1287}
1288
1289impl<T: ThreadMode> Drop for TransactionDB<T> {
1290    fn drop(&mut self) {
1291        unsafe {
1292            self.prepared_transactions().clear();
1293            self.cfs.drop_all_cfs_internal();
1294            ffi::rocksdb_transactiondb_close(self.inner);
1295        }
1296    }
1297}