diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..8f2a59a9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tikv-client" +version = "0.0.0" +keywords = ["TiKV", "KV", "distributed-systems"] +license = "Apache-2.0" +authors = ["The TiKV Project Developers"] +repository = "https://github.com/tikv/client-rust" +description = "The rust language implementation of TiKV client." + +[lib] +name = "tikv_client" + +[dependencies] +futures = "0.1" +serde = "1.0" +serde_derive = "1.0" +quick-error = "1.2" +grpcio = { version = "0.4", features = [ "secure" ] } diff --git a/examples/raw.rs b/examples/raw.rs new file mode 100644 index 00000000..4c5887a2 --- /dev/null +++ b/examples/raw.rs @@ -0,0 +1,69 @@ +extern crate futures; +extern crate tikv_client; + +use std::path::PathBuf; + +use futures::future::Future; +use tikv_client::*; + +fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]).with_security( + PathBuf::from("/path/to/ca.pem"), + PathBuf::from("/path/to/client.pem"), + PathBuf::from("/path/to/client-key.pem"), + ); + let raw = raw::Client::new(&config) + .wait() + .expect("Could not connect to tikv"); + + let key: Key = b"Company".to_vec().into(); + let value: Value = b"PingCAP".to_vec().into(); + + raw.put(key.clone(), value.clone()) + .cf("test_cf") + .wait() + .expect("Could not put kv pair to tikv"); + println!("Successfully put {:?}:{:?} to tikv", key, value); + + let value = raw + .get(&key) + .cf("test_cf") + .wait() + .expect("Could not get value"); + println!("Found val: {:?} for key: {:?}", value, key); + + raw.delete(&key) + .cf("test_cf") + .wait() + .expect("Could not delete value"); + println!("Key: {:?} deleted", key); + + raw.get(&key) + .cf("test_cf") + .wait() + .expect_err("Get returned value for not existing key"); + + let keys = vec![b"k1".to_vec().into(), b"k2".to_vec().into()]; + + let values = raw + .batch_get(&keys) + .cf("test_cf") + .wait() + .expect("Could not get values"); + println!("Found values: {:?} for keys: {:?}", values, keys); + + let start: Key = b"k1".to_vec().into(); + let end: Key = b"k2".to_vec().into(); + raw.scan(&start..&end, 10) + .cf("test_cf") + .key_only() + .wait() + .expect("Could not scan"); + + let ranges = [&start..&end, &start..&end]; + raw.batch_scan(&ranges, 10) + .cf("test_cf") + .key_only() + .wait() + .expect("Could not batch scan"); +} diff --git a/examples/transaction.rs b/examples/transaction.rs new file mode 100644 index 00000000..032c651e --- /dev/null +++ b/examples/transaction.rs @@ -0,0 +1,87 @@ +extern crate futures; +extern crate tikv_client; + +use std::ops::RangeBounds; +use std::path::PathBuf; + +use futures::{future, Future, Stream}; +use tikv_client::transaction::{Client, IsolationLevel}; +use tikv_client::*; + +fn puts(client: &Client, pairs: impl IntoIterator>) { + let mut txn = client.begin(); + let _: Vec<()> = future::join_all( + pairs + .into_iter() + .map(Into::into) + .map(|p| txn.set(p.key().clone(), p.value().clone())), + ).wait() + .expect("Could not set key value pairs"); + txn.commit().wait().expect("Could not commit transaction"); +} + +fn get(client: &Client, key: &Key) -> Value { + let txn = client.begin(); + txn.get(key).wait().expect("Could not get value") +} + +fn scan(client: &Client, range: impl RangeBounds, mut limit: usize) { + client + .begin() + .scan(range) + .take_while(move |_| { + Ok(if limit == 0 { + false + } else { + limit -= 1; + true + }) + }).for_each(|pair| { + println!("{:?}", pair); + Ok(()) + }).wait() + .expect("Could not scan keys"); +} + +fn dels(client: &Client, keys: impl IntoIterator) { + let mut txn = client.begin(); + txn.set_isolation_level(IsolationLevel::ReadCommitted); + let _: Vec<()> = keys + .into_iter() + .map(|p| { + txn.delete(p).wait().expect("Could not delete key"); + }).collect(); + txn.commit().wait().expect("Could not commit transaction"); +} + +fn main() { + let config = Config::new(vec!["127.0.0.1:3379"]).with_security( + PathBuf::from("/path/to/ca.pem"), + PathBuf::from("/path/to/client.pem"), + PathBuf::from("/path/to/client-key.pem"), + ); + let txn = Client::new(&config) + .wait() + .expect("Could not connect to tikv"); + + // set + let key1: Key = b"key1".to_vec().into(); + let value1: Value = b"value1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + let value2: Value = b"value2".to_vec().into(); + puts(&txn, vec![(key1, value1), (key2, value2)]); + + // get + let key1: Key = b"key1".to_vec().into(); + let value1 = get(&txn, &key1); + println!("{:?}", (key1, value1)); + + // scan + let key1: Key = b"key1".to_vec().into(); + scan(&txn, key1.., 10); + + // delete + let key1: Key = b"key1".to_vec().into(); + let key2: Key = b"key2".to_vec().into(); + dels(&txn, vec![key1, key2]); +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 00000000..02bc4cd1 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,76 @@ +// Copyright 2016 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::error; +use std::result; + +quick_error!{ + #[derive(Debug)] + pub enum Error { + Io(err: ::std::io::Error) { + from() + cause(err) + description(err.description()) + } + Grpc(err: ::grpc::Error) { + from() + cause(err) + description(err.description()) + } + Canceled(err: ::futures::sync::oneshot::Canceled) { + from() + cause(err) + description(err.description()) + } + Other(err: Box) { + from() + cause(err.as_ref()) + description(err.description()) + display("unknown error {:?}", err) + } + RegionForKeyNotFound(key: Vec) { + description("region is not found") + display("region is not found for key {:?}", key) + } + RegionNotFound(id: u64) { + description("region is not found") + display("region {:?} is not found", id) + } + NotLeader(region_id: u64) { + description("peer is not leader") + display("peer is not leader for region {:?}.", region_id) + } + StoreNotMatch { + description("store not match") + display("store not match") + } + KeyNotInRegion(key: Vec, region_id: u64, start_key: Vec, end_key: Vec) { + description("region is not found") + display("key {:?} is not in region {:?}: [{:?}, {:?})", key, region_id, start_key, end_key) + } + StaleEpoch { + description("stale epoch") + display("stale epoch") + } + ServerIsBusy(reason: String) { + description("server is busy") + display("server is busy: {:?}", reason) + } + RaftEntryTooLarge(region_id: u64, entry_size: u64) { + description("raft entry too large") + display("{:?} bytes raft entry of region {:?} is too large", entry_size, region_id) + } + } +} + +pub type Result = result::Result; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..772cc2f0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,111 @@ +extern crate futures; +extern crate serde; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate quick_error; +extern crate grpcio as grpc; + +pub mod errors; +pub mod raw; +pub mod transaction; + +use std::ops::Deref; +use std::path::PathBuf; + +pub use errors::Error; +pub use errors::Result; + +#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Key(Vec); +#[derive(Default, Clone, Eq, PartialEq, Hash, Debug)] +pub struct Value(Vec); +#[derive(Default, Clone, Eq, PartialEq, Debug)] +pub struct KvPair(Key, Value); + +impl Into for Vec { + fn into(self) -> Key { + Key(self) + } +} + +impl AsRef for Key { + fn as_ref(&self) -> &Self { + self + } +} + +impl Deref for Key { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Into for Vec { + fn into(self) -> Value { + Value(self) + } +} + +impl Deref for Value { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl KvPair { + pub fn new(key: Key, value: Value) -> Self { + KvPair(key, value) + } + + pub fn key(&self) -> &Key { + &self.0 + } + + pub fn value(&self) -> &Value { + &self.1 + } +} + +impl Into for (Key, Value) { + fn into(self) -> KvPair { + KvPair(self.0, self.1) + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] +#[serde(default)] +#[serde(rename_all = "kebab-case")] +pub struct Config { + pub pd_endpoints: Vec, + pub ca_path: Option, + pub cert_path: Option, + pub key_path: Option, +} + +impl Config { + pub fn new(pd_endpoints: impl IntoIterator>) -> Self { + Config { + pd_endpoints: pd_endpoints.into_iter().map(Into::into).collect(), + ca_path: None, + cert_path: None, + key_path: None, + } + } + + pub fn with_security( + mut self, + ca_path: impl Into, + cert_path: impl Into, + key_path: impl Into, + ) -> Self { + self.ca_path = Some(ca_path.into()); + self.cert_path = Some(cert_path.into()); + self.key_path = Some(key_path.into()); + self + } +} diff --git a/src/raw.rs b/src/raw.rs new file mode 100644 index 00000000..18a8ea0f --- /dev/null +++ b/src/raw.rs @@ -0,0 +1,430 @@ +use std::ops::RangeBounds; + +use futures::{Future, Poll}; + +use {Config, Error, Key, KvPair, Value}; + +#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct ColumnFamily(String); + +impl From for ColumnFamily +where + T: ToString, +{ + fn from(i: T) -> ColumnFamily { + ColumnFamily(i.to_string()) + } +} + +pub struct Get<'a> { + client: &'a Client, + key: Key, + cf: Option, +} + +impl<'a> Get<'a> { + fn new(client: &'a Client, key: Key) -> Self { + Get { + client, + key, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for Get<'a> { + type Item = Value; + type Error = Error; + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.key; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct BatchGet<'a> { + client: &'a Client, + keys: Vec, + cf: Option, +} + +impl<'a> BatchGet<'a> { + fn new(client: &'a Client, keys: Vec) -> Self { + BatchGet { + client, + keys, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for BatchGet<'a> { + type Item = Vec; + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.keys; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct Put<'a> { + client: &'a Client, + key: Key, + value: Value, + cf: Option, +} + +impl<'a> Put<'a> { + fn new(client: &'a Client, key: Key, value: Value) -> Self { + Put { + client, + key, + value, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for Put<'a> { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.key; + let _ = &self.value; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct BatchPut<'a> { + client: &'a Client, + pairs: Vec, + cf: Option, +} + +impl<'a> BatchPut<'a> { + fn new(client: &'a Client, pairs: Vec) -> Self { + BatchPut { + client, + pairs, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for BatchPut<'a> { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.pairs; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct Delete<'a> { + client: &'a Client, + key: Key, + cf: Option, +} + +impl<'a> Delete<'a> { + fn new(client: &'a Client, key: Key) -> Self { + Delete { + client, + key, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for Delete<'a> { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.key; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct BatchDelete<'a> { + client: &'a Client, + keys: Vec, + cf: Option, +} + +impl<'a> BatchDelete<'a> { + fn new(client: &'a Client, keys: Vec) -> Self { + BatchDelete { + client, + keys, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for BatchDelete<'a> { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.keys; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct Scan<'a> { + client: &'a Client, + range: (Key, Key), + limit: u32, + key_only: bool, + cf: Option, + reverse: bool, +} + +impl<'a> Scan<'a> { + fn new(client: &'a Client, range: (Key, Key), limit: u32) -> Self { + Scan { + client, + range, + limit, + key_only: false, + cf: None, + reverse: false, + } + } + + pub fn key_only(mut self) -> Self { + self.key_only = true; + self + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } + + pub fn reverse(mut self) -> Self { + self.reverse = true; + self + } +} + +impl<'a> Future for Scan<'a> { + type Item = Vec; + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.range; + let _ = &self.limit; + let _ = &self.key_only; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct BatchScan<'a> { + client: &'a Client, + ranges: Vec<(Key, Key)>, + each_limit: u32, + key_only: bool, + cf: Option, + reverse: bool, +} + +impl<'a> BatchScan<'a> { + fn new(client: &'a Client, ranges: Vec<(Key, Key)>, each_limit: u32) -> Self { + BatchScan { + client, + ranges, + each_limit, + key_only: false, + cf: None, + reverse: false, + } + } + + pub fn key_only(mut self) -> Self { + self.key_only = true; + self + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } + + pub fn reverse(mut self) -> Self { + self.reverse = true; + self + } +} + +impl<'a> Future for BatchScan<'a> { + type Item = Vec; + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.ranges; + let _ = &self.each_limit; + let _ = &self.key_only; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct DeleteRange<'a> { + client: &'a Client, + range: (Key, Key), + cf: Option, +} + +impl<'a> DeleteRange<'a> { + fn new(client: &'a Client, range: (Key, Key)) -> Self { + DeleteRange { + client, + range, + cf: None, + } + } + + pub fn cf(mut self, cf: impl Into) -> Self { + self.cf = Some(cf.into()); + self + } +} + +impl<'a> Future for DeleteRange<'a> { + type Item = (); + type Error = (); + + fn poll(&mut self) -> Poll { + let _ = &self.client; + let _ = &self.range; + let _ = &self.cf; + unimplemented!() + } +} + +pub struct Connect { + config: Config, +} + +impl Connect { + fn new(config: Config) -> Self { + Connect { config } + } +} + +impl Future for Connect { + type Item = Client; + type Error = Error; + + fn poll(&mut self) -> Poll { + let _config = &self.config; + unimplemented!() + } +} + +pub struct Client; + +impl Client { + #![cfg_attr(feature = "cargo-clippy", allow(new_ret_no_self))] + pub fn new(config: &Config) -> Connect { + Connect::new(config.clone()) + } + + pub fn get(&self, key: impl AsRef) -> Get { + Get::new(self, key.as_ref().clone()) + } + + pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet { + BatchGet::new(self, keys.as_ref().to_vec()) + } + + pub fn put(&self, key: impl Into, value: impl Into) -> Put { + Put::new(self, key.into(), value.into()) + } + + pub fn batch_put(&self, pairs: impl IntoIterator>) -> BatchPut { + BatchPut::new(self, pairs.into_iter().map(Into::into).collect()) + } + + pub fn delete(&self, key: impl AsRef) -> Delete { + Delete::new(self, key.as_ref().clone()) + } + + pub fn batch_delete(&self, keys: impl AsRef<[Key]>) -> BatchDelete { + BatchDelete::new(self, keys.as_ref().to_vec()) + } + + pub fn scan(&self, range: impl RangeBounds, limit: u32) -> Scan { + Scan::new(self, Self::extract_range(&range), limit) + } + + pub fn batch_scan(&self, ranges: Ranges, each_limit: u32) -> BatchScan + where + Ranges: AsRef<[Bounds]>, + Bounds: RangeBounds, + { + BatchScan::new( + self, + ranges.as_ref().iter().map(Self::extract_range).collect(), + each_limit, + ) + } + + pub fn delete_range(&self, range: impl RangeBounds) -> DeleteRange { + DeleteRange::new(self, Self::extract_range(&range)) + } + + fn extract_range(_range: &impl RangeBounds) -> (Key, Key) { + unimplemented!() + } +} diff --git a/src/transaction.rs b/src/transaction.rs new file mode 100644 index 00000000..6f53efe4 --- /dev/null +++ b/src/transaction.rs @@ -0,0 +1,312 @@ +use std::ops::RangeBounds; + +use futures::{Future, Poll, Stream}; + +use {Config, Error, Key, KvPair, Value}; + +#[derive(Copy, Clone)] +pub struct Timestamp(u64); + +impl Into for u64 { + fn into(self) -> Timestamp { + Timestamp(self) + } +} + +impl Timestamp { + pub fn timestamp(self) -> u64 { + self.0 + } + + pub fn physical(self) -> i64 { + (self.0 >> 16) as i64 + } + + pub fn logical(self) -> i64 { + (self.0 & 0xFFFF as u64) as i64 + } +} + +pub struct Scanner; + +impl Stream for Scanner { + type Item = KvPair; + type Error = Error; + + fn poll(&mut self) -> Poll, Self::Error> { + unimplemented!() + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum IsolationLevel { + SnapshotIsolation, + ReadCommitted, +} + +pub struct Get { + key: Key, +} + +impl Get { + fn new(key: Key) -> Self { + Get { key } + } +} + +impl Future for Get { + type Item = Value; + type Error = Error; + + fn poll(&mut self) -> Poll { + let _key = &self.key; + unimplemented!() + } +} + +pub struct BatchGet { + keys: Vec, +} + +impl BatchGet { + fn new(keys: Vec) -> Self { + BatchGet { keys } + } +} + +impl Future for BatchGet { + type Item = Value; + type Error = Error; + + fn poll(&mut self) -> Poll { + let _keys = &self.keys; + unimplemented!() + } +} + +pub struct Commit { + txn: Transaction, +} + +impl Commit { + fn new(txn: Transaction) -> Self { + Commit { txn } + } +} + +impl Future for Commit { + type Item = (); + type Error = Error; + + fn poll(&mut self) -> Poll { + let _txn = &self.txn; + unimplemented!() + } +} + +pub struct Rollback { + txn: Transaction, +} + +impl Rollback { + fn new(txn: Transaction) -> Self { + Rollback { txn } + } +} + +impl Future for Rollback { + type Item = (); + type Error = Error; + + fn poll(&mut self) -> Poll { + let _txn = &self.txn; + unimplemented!() + } +} + +pub struct LockKeys { + keys: Vec, +} + +impl LockKeys { + fn new(keys: Vec) -> Self { + LockKeys { keys } + } +} + +impl Future for LockKeys { + type Item = (); + type Error = Error; + + fn poll(&mut self) -> Poll { + let _keys = &self.keys; + unimplemented!() + } +} + +pub struct Set { + key: Key, + value: Value, +} + +impl Set { + fn new(key: Key, value: Value) -> Self { + Set { key, value } + } +} + +impl Future for Set { + type Item = (); + type Error = Error; + + fn poll(&mut self) -> Poll { + let _key = &self.key; + let _value = &self.value; + unimplemented!() + } +} + +pub struct Delete { + key: Key, +} + +impl Delete { + fn new(key: Key) -> Self { + Delete { key } + } +} + +impl Future for Delete { + type Item = (); + type Error = Error; + + fn poll(&mut self) -> Poll { + let _key = &self.key; + unimplemented!() + } +} + +pub struct Transaction { + snapshot: Snapshot, +} + +impl Transaction { + pub fn commit(self) -> Commit { + Commit::new(self) + } + + pub fn rollback(self) -> Rollback { + Rollback::new(self) + } + + pub fn lock_keys(&mut self, keys: impl AsRef<[Key]>) -> LockKeys { + LockKeys::new(keys.as_ref().to_vec().clone()) + } + + pub fn is_readonly(&self) -> bool { + unimplemented!() + } + + pub fn start_ts(&self) -> Timestamp { + unimplemented!() + } + + pub fn snapshot(&self) -> Snapshot { + unimplemented!() + } + + pub fn set_isolation_level(&mut self, _level: IsolationLevel) { + unimplemented!() + } + + pub fn get(&self, key: impl AsRef) -> Get { + self.snapshot.get(key) + } + + pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet { + self.snapshot.batch_get(keys) + } + + pub fn scan(&self, range: impl RangeBounds) -> Scanner { + self.snapshot.scan(range) + } + + pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner { + self.snapshot.scan_reverse(range) + } + + pub fn set(&mut self, key: impl Into, value: impl Into) -> Set { + Set::new(key.into(), value.into()) + } + + pub fn delete(&mut self, key: impl AsRef) -> Delete { + Delete::new(key.as_ref().clone()) + } +} + +pub struct Snapshot; + +impl Snapshot { + pub fn get(&self, key: impl AsRef) -> Get { + Get::new(key.as_ref().clone()) + } + + pub fn batch_get(&self, keys: impl AsRef<[Key]>) -> BatchGet { + BatchGet::new(keys.as_ref().to_vec().clone()) + } + + pub fn scan(&self, range: impl RangeBounds) -> Scanner { + drop(range); + unimplemented!() + } + + pub fn scan_reverse(&self, range: impl RangeBounds) -> Scanner { + drop(range); + unimplemented!() + } +} + +pub struct Connect { + config: Config, +} + +impl Connect { + fn new(config: Config) -> Self { + Connect { config } + } +} + +impl Future for Connect { + type Item = Client; + type Error = Error; + + fn poll(&mut self) -> Poll { + let _config = &self.config; + unimplemented!() + } +} + +pub struct Client {} + +impl Client { + #![cfg_attr(feature = "cargo-clippy", allow(new_ret_no_self))] + pub fn new(config: &Config) -> Connect { + Connect::new(config.clone()) + } + + pub fn begin(&self) -> Transaction { + unimplemented!() + } + + pub fn begin_with_timestamp(&self, _timestamp: Timestamp) -> Transaction { + unimplemented!() + } + + pub fn snapshot(&self) -> Snapshot { + unimplemented!() + } + + pub fn current_timestamp(&self) -> Timestamp { + unimplemented!() + } +}