diff --git a/Cargo.toml b/Cargo.toml index 385222ac..f7e14462 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,3 +43,5 @@ clap = "2.32" tempdir = "0.3" runtime = "0.3.0-alpha.3" runtime-tokio = "0.3.0-alpha.3" +proptest = "0.9" +proptest-derive = "0.1.0" \ No newline at end of file diff --git a/src/kv.rs b/src/kv.rs deleted file mode 100644 index a06535cd..00000000 --- a/src/kv.rs +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. - -use derive_new::new; -use std::cmp::{Eq, PartialEq}; -use std::convert::TryFrom; -use std::ops::{Bound, Range, RangeFrom, RangeInclusive}; -use std::{fmt, str, u8}; - -use crate::{Error, Result}; - -struct HexRepr<'a>(pub &'a [u8]); - -impl<'a> fmt::Display for HexRepr<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for byte in self.0 { - write!(f, "{:02X}", byte)?; - } - Ok(()) - } -} - -/// The key part of a key/value pair. -/// -/// In TiKV, keys are an ordered sequence of bytes. This has an advantage over choosing `String` as -/// valid `UTF-8` is not required. This means that the user is permitted to store any data they wish, -/// as long as it can be represented by bytes. (Which is to say, pretty much anything!) -/// -/// This type also implements `From` for many types. With one exception, these are all done without -/// reallocation. Using a `&'static str`, like many examples do for simplicity, has an internal -/// allocation cost. -/// -/// This type wraps around an owned value, so it should be treated it like `String` or `Vec` -/// over a `&str` or `&[u8]`. -/// -/// ```rust -/// use tikv_client::Key; -/// -/// let static_str: &'static str = "TiKV"; -/// let from_static_str = Key::from(static_str); -/// -/// let string: String = String::from(static_str); -/// let from_string = Key::from(string); -/// assert_eq!(from_static_str, from_string); -/// -/// let vec: Vec = static_str.as_bytes().to_vec(); -/// let from_vec = Key::from(vec); -/// assert_eq!(from_static_str, from_vec); -/// -/// let bytes = static_str.as_bytes().to_vec(); -/// let from_bytes = Key::from(bytes); -/// assert_eq!(from_static_str, from_bytes); -/// ``` -/// -/// **But, you should not need to worry about all this:** Many functions which accept a `Key` -/// accept an `Into`, which means all of the above types can be passed directly to those -/// functions. -#[derive(new, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct Key(Vec); - -impl Key { - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - #[inline] - fn zero_terminated(&self) -> bool { - self.0.last().map(|i| *i == 0).unwrap_or(false) - } - - #[inline] - fn push_zero(&mut self) { - self.0.push(0) - } - - #[inline] - fn into_lower_bound(mut self) -> Bound { - if self.zero_terminated() { - self.0.pop().unwrap(); - Bound::Excluded(self) - } else { - Bound::Included(self) - } - } - - #[inline] - fn into_upper_bound(mut self) -> Bound { - if self.zero_terminated() { - self.0.pop().unwrap(); - Bound::Included(self) - } else { - Bound::Excluded(self) - } - } -} - -impl From> for Key { - fn from(v: Vec) -> Self { - Key(v) - } -} - -impl From for Key { - fn from(v: String) -> Key { - Key(v.into_bytes()) - } -} - -impl From<&'static str> for Key { - fn from(v: &'static str) -> Key { - Key(v.as_bytes().to_vec()) - } -} - -impl Into> for Key { - fn into(self) -> Vec { - self.0 - } -} - -impl<'a> Into<&'a [u8]> for &'a Key { - fn into(self) -> &'a [u8] { - &self.0 - } -} - -impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Key({})", HexRepr(&self.0)) - } -} - -/// The value part of a key/value pair. -/// -/// In TiKV, values are an ordered sequence of bytes. This has an advantage over choosing `String` -/// as valid `UTF-8` is not required. This means that the user is permitted to store any data they wish, -/// as long as it can be represented by bytes. (Which is to say, pretty much anything!) -/// -/// This type also implements `From` for many types. With one exception, these are all done without -/// reallocation. Using a `&'static str`, like many examples do for simplicity, has an internal -/// allocation cost. -/// -/// This type wraps around an owned value, so it should be treated it like `String` or `Vec` -/// over a `&str` or `&[u8]`. -/// -/// ```rust -/// use tikv_client::Value; -/// -/// let static_str: &'static str = "TiKV"; -/// let from_static_str = Value::from(static_str); -/// -/// let string: String = String::from(static_str); -/// let from_string = Value::from(string); -/// assert_eq!(from_static_str, from_string); -/// -/// let vec: Vec = static_str.as_bytes().to_vec(); -/// let from_vec = Value::from(vec); -/// assert_eq!(from_static_str, from_vec); -/// -/// let bytes = static_str.as_bytes().to_vec(); -/// let from_bytes = Value::from(bytes); -/// assert_eq!(from_static_str, from_bytes); -/// ``` -/// -/// **But, you should not need to worry about all this:** Many functions which accept a `Value` -/// accept an `Into`, which means all of the above types can be passed directly to those -/// functions. -#[derive(new, Default, Clone, Eq, PartialEq, Hash)] -pub struct Value(Vec); - -impl Value { - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl From> for Value { - fn from(v: Vec) -> Self { - Value(v) - } -} - -impl From for Value { - fn from(v: String) -> Value { - Value(v.into_bytes()) - } -} - -impl From<&'static str> for Value { - fn from(v: &'static str) -> Value { - Value(v.as_bytes().to_vec()) - } -} - -impl Into> for Value { - fn into(self) -> Vec { - self.0 - } -} - -impl<'a> Into<&'a [u8]> for &'a Value { - fn into(self) -> &'a [u8] { - &self.0 - } -} - -impl fmt::Debug for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match str::from_utf8(&self.0) { - Ok(s) => write!(f, "Value({:?})", s), - Err(_) => write!(f, "Value({})", HexRepr(&self.0)), - } - } -} - -/// A key/value pair. -/// -/// ```rust -/// # use tikv_client::{Key, Value, KvPair}; -/// let key = "key"; -/// let value = "value"; -/// let constructed = KvPair::new(key, value); -/// let from_tuple = KvPair::from((key, value)); -/// assert_eq!(constructed, from_tuple); -/// ``` -/// -/// Many functions which accept a `KvPair` accept an `Into`, which means all of the above -/// types (Like a `(Key, Value)`) can be passed directly to those functions. -#[derive(Default, Clone, Eq, PartialEq)] -pub struct KvPair(Key, Value); - -impl KvPair { - /// Create a new `KvPair`. - #[inline] - pub fn new(key: impl Into, value: impl Into) -> Self { - KvPair(key.into(), value.into()) - } - - /// Immutably borrow the `Key` part of the `KvPair`. - #[inline] - pub fn key(&self) -> &Key { - &self.0 - } - - /// Immutably borrow the `Value` part of the `KvPair`. - #[inline] - pub fn value(&self) -> &Value { - &self.1 - } - - #[inline] - pub fn into_key(self) -> Key { - self.0 - } - - #[inline] - pub fn into_value(self) -> Value { - self.1 - } - - /// Mutably borrow the `Key` part of the `KvPair`. - #[inline] - pub fn key_mut(&mut self) -> &mut Key { - &mut self.0 - } - - /// Mutably borrow the `Value` part of the `KvPair`. - #[inline] - pub fn value_mut(&mut self) -> &mut Value { - &mut self.1 - } - - /// Set the `Key` part of the `KvPair`. - #[inline] - pub fn set_key(&mut self, k: impl Into) { - self.0 = k.into(); - } - - /// Set the `Value` part of the `KvPair`. - #[inline] - pub fn set_value(&mut self, v: impl Into) { - self.1 = v.into(); - } -} - -impl From<(K, V)> for KvPair -where - K: Into, - V: Into, -{ - fn from((k, v): (K, V)) -> Self { - KvPair(k.into(), v.into()) - } -} - -impl Into<(Key, Value)> for KvPair { - fn into(self) -> (Key, Value) { - (self.0, self.1) - } -} - -impl fmt::Debug for KvPair { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let KvPair(key, value) = self; - match str::from_utf8(&value.0) { - Ok(s) => write!(f, "KvPair({}, {:?})", HexRepr(&key.0), s), - Err(_) => write!(f, "KvPair({}, {})", HexRepr(&key.0), HexRepr(&value.0)), - } - } -} - -/// A struct for expressing ranges. This type is semi-opaque and is not really meant for users to -/// deal with directly. Most functions which operate on ranges will accept any types which -/// implement `Into`. -/// -/// In TiKV, keys are an ordered sequence of bytes. This means we can have ranges over those -/// bytes. Eg `001` is before `010`. -/// -/// `Into` has implementations for common range types like `a..b`, `a..=b` where `a` and `b` -/// `impl Into`. You can implement `Into` for your own types by using `try_from`. -/// -/// Invariant: a range may not be unbounded below. -/// -/// ```rust -/// use tikv_client::{BoundRange, Key}; -/// use std::ops::{Range, RangeInclusive, RangeTo, RangeToInclusive, RangeFrom, RangeFull, Bound}; -/// # use std::convert::TryInto; -/// -/// let explict_range: Range = Range { start: Key::from("Rust"), end: Key::from("TiKV") }; -/// let from_explict_range: BoundRange = explict_range.into(); -/// -/// let range: Range<&str> = "Rust".."TiKV"; -/// let from_range: BoundRange = range.into(); -/// assert_eq!(from_explict_range, from_range); -/// -/// let range: RangeInclusive<&str> = "Rust"..="TiKV"; -/// let from_range: BoundRange = range.into(); -/// assert_eq!( -/// from_range, -/// (Bound::Included(Key::from("Rust")), Bound::Included(Key::from("TiKV"))), -/// ); -/// -/// let range_from: RangeFrom<&str> = "Rust"..; -/// let from_range_from: BoundRange = range_from.into(); -/// assert_eq!( -/// from_range_from, -/// (Bound::Included(Key::from("Rust")), Bound::Unbounded), -/// ); -/// ``` -/// -/// **But, you should not need to worry about all this:** Most functions which operate -/// on ranges will accept any types which implement `Into`. -/// which means all of the above types can be passed directly to those functions. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct BoundRange { - from: Bound, - to: Bound, -} - -impl BoundRange { - /// Create a new BoundRange. - /// - /// The caller must ensure that `from` is not `Unbounded`. - fn new(from: Bound, to: Bound) -> BoundRange { - // Debug assert because this function is private. - debug_assert!(from != Bound::Unbounded); - BoundRange { from, to } - } - - /// Ranges used in scanning TiKV have a particularity to them. - /// - /// The **start** of a scan is inclusive, unless appended with an '\0', then it is exclusive. - /// - /// The **end** of a scan is exclusive, unless appended with an '\0', then it is inclusive. - /// - /// ```rust - /// use tikv_client::{BoundRange, Key}; - /// // Exclusive - /// let range = "a".."z"; - /// assert_eq!( - /// BoundRange::from(range).into_keys(), - /// (Key::from("a"), Some(Key::from("z"))), - /// ); - /// // Inclusive - /// let range = "a"..="z"; - /// assert_eq!( - /// BoundRange::from(range).into_keys(), - /// (Key::from("a"), Some(Key::from("z\0"))), - /// ); - /// // Open - /// let range = "a"..; - /// assert_eq!( - /// BoundRange::from(range).into_keys(), - /// (Key::from("a"), None), - /// ); - // ``` - pub fn into_keys(self) -> (Key, Option) { - let start = match self.from { - Bound::Included(v) => v, - Bound::Excluded(mut v) => { - v.push_zero(); - v - } - Bound::Unbounded => unreachable!(), - }; - let end = match self.to { - Bound::Included(mut v) => { - v.push_zero(); - Some(v) - } - Bound::Excluded(v) => Some(v), - Bound::Unbounded => None, - }; - (start, end) - } -} - -impl + Clone> PartialEq<(Bound, Bound)> for BoundRange { - fn eq(&self, other: &(Bound, Bound)) -> bool { - self.from == convert_to_bound_key(other.0.clone()) - && self.to == convert_to_bound_key(other.1.clone()) - } -} - -impl> From> for BoundRange { - fn from(other: Range) -> BoundRange { - BoundRange::new( - Bound::Included(other.start.into()), - Bound::Excluded(other.end.into()), - ) - } -} - -impl> From> for BoundRange { - fn from(other: RangeFrom) -> BoundRange { - BoundRange::new(Bound::Included(other.start.into()), Bound::Unbounded) - } -} - -impl> From> for BoundRange { - fn from(other: RangeInclusive) -> BoundRange { - let (start, end) = other.into_inner(); - BoundRange::new(Bound::Included(start.into()), Bound::Included(end.into())) - } -} - -impl> From<(T, Option)> for BoundRange { - fn from(other: (T, Option)) -> BoundRange { - let to = match other.1 { - None => Bound::Unbounded, - Some(to) => to.into().into_upper_bound(), - }; - - BoundRange::new(other.0.into().into_lower_bound(), to) - } -} - -impl> From<(T, T)> for BoundRange { - fn from(other: (T, T)) -> BoundRange { - BoundRange::new( - other.0.into().into_lower_bound(), - other.1.into().into_upper_bound(), - ) - } -} - -impl + Eq> TryFrom<(Bound, Bound)> for BoundRange { - type Error = Error; - - fn try_from(bounds: (Bound, Bound)) -> Result { - if bounds.0 == Bound::Unbounded { - Err(Error::invalid_key_range()) - } else { - Ok(BoundRange::new( - convert_to_bound_key(bounds.0), - convert_to_bound_key(bounds.1), - )) - } - } -} - -fn convert_to_bound_key>(b: Bound) -> Bound { - match b { - Bound::Included(k) => Bound::Included(k.into()), - Bound::Excluded(k) => Bound::Excluded(k.into()), - Bound::Unbounded => Bound::Unbounded, - } -} diff --git a/src/kv/bound_range.rs b/src/kv/bound_range.rs new file mode 100644 index 00000000..2c39c290 --- /dev/null +++ b/src/kv/bound_range.rs @@ -0,0 +1,189 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use super::Key; +#[cfg(test)] +use proptest_derive::Arbitrary; +use std::cmp::{Eq, PartialEq}; +use std::convert::TryFrom; +use std::ops::{Bound, Range, RangeFrom, RangeInclusive}; + +use crate::{Error, Result}; + +/// A struct for expressing ranges. This type is semi-opaque and is not really meant for users to +/// deal with directly. Most functions which operate on ranges will accept any types which +/// implement `Into`. +/// +/// In TiKV, keys are an ordered sequence of bytes. This means we can have ranges over those +/// bytes. Eg `001` is before `010`. +/// +/// `Into` has implementations for common range types like `a..b`, `a..=b` where `a` and `b` +/// `impl Into`. You can implement `Into` for your own types by using `try_from`. +/// +/// Invariant: a range may not be unbounded below. +/// +/// ```rust +/// use tikv_client::{BoundRange, Key}; +/// use std::ops::{Range, RangeInclusive, RangeTo, RangeToInclusive, RangeFrom, RangeFull, Bound}; +/// # use std::convert::TryInto; +/// +/// let explict_range: Range = Range { start: Key::from("Rust"), end: Key::from("TiKV") }; +/// let from_explict_range: BoundRange = explict_range.into(); +/// +/// let range: Range<&str> = "Rust".."TiKV"; +/// let from_range: BoundRange = range.into(); +/// assert_eq!(from_explict_range, from_range); +/// +/// let range: RangeInclusive<&str> = "Rust"..="TiKV"; +/// let from_range: BoundRange = range.into(); +/// assert_eq!( +/// from_range, +/// (Bound::Included(Key::from("Rust")), Bound::Included(Key::from("TiKV"))), +/// ); +/// +/// let range_from: RangeFrom<&str> = "Rust"..; +/// let from_range_from: BoundRange = range_from.into(); +/// assert_eq!( +/// from_range_from, +/// (Bound::Included(Key::from("Rust")), Bound::Unbounded), +/// ); +/// ``` +/// +/// **But, you should not need to worry about all this:** Most functions which operate +/// on ranges will accept any types which implement `Into`. +/// which means all of the above types can be passed directly to those functions. +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct BoundRange { + from: Bound, + to: Bound, +} + +impl BoundRange { + /// Create a new BoundRange. + /// + /// The caller must ensure that `from` is not `Unbounded`. + fn new(from: Bound, to: Bound) -> BoundRange { + // Debug assert because this function is private. + debug_assert!(from != Bound::Unbounded); + BoundRange { from, to } + } + + /// Ranges used in scanning TiKV have a particularity to them. + /// + /// The **start** of a scan is inclusive, unless appended with an '\0', then it is exclusive. + /// + /// The **end** of a scan is exclusive, unless appended with an '\0', then it is inclusive. + /// + /// ```rust + /// use tikv_client::{BoundRange, Key}; + /// // Exclusive + /// let range = "a".."z"; + /// assert_eq!( + /// BoundRange::from(range).into_keys(), + /// (Key::from("a"), Some(Key::from("z"))), + /// ); + /// // Inclusive + /// let range = "a"..="z"; + /// assert_eq!( + /// BoundRange::from(range).into_keys(), + /// (Key::from("a"), Some(Key::from("z\0"))), + /// ); + /// // Open + /// let range = "a"..; + /// assert_eq!( + /// BoundRange::from(range).into_keys(), + /// (Key::from("a"), None), + /// ); + // ``` + pub fn into_keys(self) -> (Key, Option) { + let start = match self.from { + Bound::Included(v) => v, + Bound::Excluded(mut v) => { + v.push_zero(); + v + } + Bound::Unbounded => unreachable!(), + }; + let end = match self.to { + Bound::Included(mut v) => { + v.push_zero(); + Some(v) + } + Bound::Excluded(v) => Some(v), + Bound::Unbounded => None, + }; + (start, end) + } +} + +impl + Clone> PartialEq<(Bound, Bound)> for BoundRange { + fn eq(&self, other: &(Bound, Bound)) -> bool { + self.from == convert_to_bound_key(other.0.clone()) + && self.to == convert_to_bound_key(other.1.clone()) + } +} + +impl> From> for BoundRange { + fn from(other: Range) -> BoundRange { + BoundRange::new( + Bound::Included(other.start.into()), + Bound::Excluded(other.end.into()), + ) + } +} + +impl> From> for BoundRange { + fn from(other: RangeFrom) -> BoundRange { + BoundRange::new(Bound::Included(other.start.into()), Bound::Unbounded) + } +} + +impl> From> for BoundRange { + fn from(other: RangeInclusive) -> BoundRange { + let (start, end) = other.into_inner(); + BoundRange::new(Bound::Included(start.into()), Bound::Included(end.into())) + } +} + +impl> From<(T, Option)> for BoundRange { + fn from(other: (T, Option)) -> BoundRange { + let to = match other.1 { + None => Bound::Unbounded, + Some(to) => to.into().into_upper_bound(), + }; + + BoundRange::new(other.0.into().into_lower_bound(), to) + } +} + +impl> From<(T, T)> for BoundRange { + fn from(other: (T, T)) -> BoundRange { + BoundRange::new( + other.0.into().into_lower_bound(), + other.1.into().into_upper_bound(), + ) + } +} + +impl + Eq> TryFrom<(Bound, Bound)> for BoundRange { + type Error = Error; + + fn try_from(bounds: (Bound, Bound)) -> Result { + if bounds.0 == Bound::Unbounded { + Err(Error::invalid_key_range()) + } else { + Ok(BoundRange::new( + convert_to_bound_key(bounds.0), + convert_to_bound_key(bounds.1), + )) + } + } +} + +fn convert_to_bound_key>(b: Bound) -> Bound { + match b { + Bound::Included(k) => Bound::Included(k.into()), + Bound::Excluded(k) => Bound::Excluded(k.into()), + Bound::Unbounded => Bound::Unbounded, + } +} diff --git a/src/kv/key.rs b/src/kv/key.rs new file mode 100644 index 00000000..df84a240 --- /dev/null +++ b/src/kv/key.rs @@ -0,0 +1,129 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use super::HexRepr; +#[cfg(test)] +use proptest::{arbitrary::any_with, collection::size_range}; +#[cfg(test)] +use proptest_derive::Arbitrary; +use std::ops::Bound; +use std::{fmt, str, u8}; + +/// The key part of a key/value pair. +/// +/// In TiKV, keys are an ordered sequence of bytes. This has an advantage over choosing `String` as +/// valid `UTF-8` is not required. This means that the user is permitted to store any data they wish, +/// as long as it can be represented by bytes. (Which is to say, pretty much anything!) +/// +/// This type also implements `From` for many types. With one exception, these are all done without +/// reallocation. Using a `&'static str`, like many examples do for simplicity, has an internal +/// allocation cost. +/// +/// This type wraps around an owned value, so it should be treated it like `String` or `Vec` +/// over a `&str` or `&[u8]`. +/// +/// ```rust +/// use tikv_client::Key; +/// +/// let static_str: &'static str = "TiKV"; +/// let from_static_str = Key::from(static_str); +/// +/// let string: String = String::from(static_str); +/// let from_string = Key::from(string); +/// assert_eq!(from_static_str, from_string); +/// +/// let vec: Vec = static_str.as_bytes().to_vec(); +/// let from_vec = Key::from(vec); +/// assert_eq!(from_static_str, from_vec); +/// +/// let bytes = static_str.as_bytes().to_vec(); +/// let from_bytes = Key::from(bytes); +/// assert_eq!(from_static_str, from_bytes); +/// ``` +/// +/// **But, you should not need to worry about all this:** Many functions which accept a `Key` +/// accept an `Into`, which means all of the above types can be passed directly to those +/// functions. +#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Key( + #[cfg_attr( + test, + proptest( + strategy = "any_with::>((size_range(crate::proptests::PROPTEST_KEY_MAX), ()))" + ) + )] + pub(super) Vec, +); + +impl Key { + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[inline] + pub(super) fn zero_terminated(&self) -> bool { + self.0.last().map(|i| *i == 0).unwrap_or(false) + } + + #[inline] + pub(super) fn push_zero(&mut self) { + self.0.push(0) + } + + #[inline] + pub(super) fn into_lower_bound(mut self) -> Bound { + if self.zero_terminated() { + self.0.pop().unwrap(); + Bound::Excluded(self) + } else { + Bound::Included(self) + } + } + + #[inline] + pub(super) fn into_upper_bound(mut self) -> Bound { + if self.zero_terminated() { + self.0.pop().unwrap(); + Bound::Included(self) + } else { + Bound::Excluded(self) + } + } +} + +impl From> for Key { + fn from(v: Vec) -> Self { + Key(v) + } +} + +impl From for Key { + fn from(v: String) -> Key { + Key(v.into_bytes()) + } +} + +impl From<&'static str> for Key { + fn from(v: &'static str) -> Key { + Key(v.as_bytes().to_vec()) + } +} + +impl Into> for Key { + fn into(self) -> Vec { + self.0 + } +} + +impl<'a> Into<&'a [u8]> for &'a Key { + fn into(self) -> &'a [u8] { + &self.0 + } +} + +impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Key({})", HexRepr(&self.0)) + } +} diff --git a/src/kv/kvpair.rs b/src/kv/kvpair.rs new file mode 100644 index 00000000..607e32fe --- /dev/null +++ b/src/kv/kvpair.rs @@ -0,0 +1,103 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use super::{HexRepr, Key, Value}; +#[cfg(test)] +use proptest_derive::Arbitrary; +use std::{fmt, str}; + +/// A key/value pair. +/// +/// ```rust +/// # use tikv_client::{Key, Value, KvPair}; +/// let key = "key"; +/// let value = "value"; +/// let constructed = KvPair::new(key, value); +/// let from_tuple = KvPair::from((key, value)); +/// assert_eq!(constructed, from_tuple); +/// ``` +/// +/// Many functions which accept a `KvPair` accept an `Into`, which means all of the above +/// types (Like a `(Key, Value)`) can be passed directly to those functions. +#[derive(Default, Clone, Eq, PartialEq)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct KvPair(Key, Value); + +impl KvPair { + /// Create a new `KvPair`. + #[inline] + pub fn new(key: impl Into, value: impl Into) -> Self { + KvPair(key.into(), value.into()) + } + + /// Immutably borrow the `Key` part of the `KvPair`. + #[inline] + pub fn key(&self) -> &Key { + &self.0 + } + + /// Immutably borrow the `Value` part of the `KvPair`. + #[inline] + pub fn value(&self) -> &Value { + &self.1 + } + + #[inline] + pub fn into_key(self) -> Key { + self.0 + } + + #[inline] + pub fn into_value(self) -> Value { + self.1 + } + + /// Mutably borrow the `Key` part of the `KvPair`. + #[inline] + pub fn key_mut(&mut self) -> &mut Key { + &mut self.0 + } + + /// Mutably borrow the `Value` part of the `KvPair`. + #[inline] + pub fn value_mut(&mut self) -> &mut Value { + &mut self.1 + } + + /// Set the `Key` part of the `KvPair`. + #[inline] + pub fn set_key(&mut self, k: impl Into) { + self.0 = k.into(); + } + + /// Set the `Value` part of the `KvPair`. + #[inline] + pub fn set_value(&mut self, v: impl Into) { + self.1 = v.into(); + } +} + +impl From<(K, V)> for KvPair +where + K: Into, + V: Into, +{ + fn from((k, v): (K, V)) -> Self { + KvPair(k.into(), v.into()) + } +} + +impl Into<(Key, Value)> for KvPair { + fn into(self) -> (Key, Value) { + (self.0, self.1) + } +} + +impl fmt::Debug for KvPair { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let KvPair(key, value) = self; + match str::from_utf8(&value.0) { + Ok(s) => write!(f, "KvPair({}, {:?})", HexRepr(&key.0), s), + Err(_) => write!(f, "KvPair({}, {})", HexRepr(&key.0), HexRepr(&value.0)), + } + } +} diff --git a/src/kv/mod.rs b/src/kv/mod.rs new file mode 100644 index 00000000..bf1add36 --- /dev/null +++ b/src/kv/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. +use std::{fmt, u8}; + +mod key; +pub use key::Key; +mod value; +pub use value::Value; +mod kvpair; +pub use kvpair::KvPair; +mod bound_range; +pub use bound_range::BoundRange; + +struct HexRepr<'a>(pub &'a [u8]); + +impl<'a> fmt::Display for HexRepr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for byte in self.0 { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} diff --git a/src/kv/value.rs b/src/kv/value.rs new file mode 100644 index 00000000..36c4c94a --- /dev/null +++ b/src/kv/value.rs @@ -0,0 +1,101 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use super::HexRepr; +#[cfg(test)] +use proptest::{arbitrary::any_with, collection::size_range}; +#[cfg(test)] +use proptest_derive::Arbitrary; +use std::{fmt, str, u8}; + +/// The value part of a key/value pair. +/// +/// In TiKV, values are an ordered sequence of bytes. This has an advantage over choosing `String` +/// as valid `UTF-8` is not required. This means that the user is permitted to store any data they wish, +/// as long as it can be represented by bytes. (Which is to say, pretty much anything!) +/// +/// This type also implements `From` for many types. With one exception, these are all done without +/// reallocation. Using a `&'static str`, like many examples do for simplicity, has an internal +/// allocation cost. +/// +/// This type wraps around an owned value, so it should be treated it like `String` or `Vec` +/// over a `&str` or `&[u8]`. +/// +/// ```rust +/// use tikv_client::Value; +/// +/// let static_str: &'static str = "TiKV"; +/// let from_static_str = Value::from(static_str); +/// +/// let string: String = String::from(static_str); +/// let from_string = Value::from(string); +/// assert_eq!(from_static_str, from_string); +/// +/// let vec: Vec = static_str.as_bytes().to_vec(); +/// let from_vec = Value::from(vec); +/// assert_eq!(from_static_str, from_vec); +/// +/// let bytes = static_str.as_bytes().to_vec(); +/// let from_bytes = Value::from(bytes); +/// assert_eq!(from_static_str, from_bytes); +/// ``` +/// +/// **But, you should not need to worry about all this:** Many functions which accept a `Value` +/// accept an `Into`, which means all of the above types can be passed directly to those +/// functions. +#[derive(Default, Clone, Eq, PartialEq, Hash)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Value( + #[cfg_attr( + test, + proptest( + strategy = "any_with::>((size_range(crate::proptests::PROPTEST_VALUE_MAX), ()))" + ) + )] + pub(super) Vec, +); + +impl Value { + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl From> for Value { + fn from(v: Vec) -> Self { + Value(v) + } +} + +impl From for Value { + fn from(v: String) -> Value { + Value(v.into_bytes()) + } +} + +impl From<&'static str> for Value { + fn from(v: &'static str) -> Value { + Value(v.as_bytes().to_vec()) + } +} + +impl Into> for Value { + fn into(self) -> Vec { + self.0 + } +} + +impl<'a> Into<&'a [u8]> for &'a Value { + fn into(self) -> &'a [u8] { + &self.0 + } +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match str::from_utf8(&self.0) { + Ok(s) => write!(f, "Value({:?})", s), + Err(_) => write!(f, "Value({})", HexRepr(&self.0)), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 63ca798c..117b6ec2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,8 @@ mod compat; mod config; mod errors; mod kv; +#[cfg(test)] +mod proptests; pub mod raw; mod rpc; pub mod transaction; diff --git a/src/proptests/mod.rs b/src/proptests/mod.rs new file mode 100644 index 00000000..7e0144d8 --- /dev/null +++ b/src/proptests/mod.rs @@ -0,0 +1,31 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +// Note: This module exists and includes some integration tests because the `/tests/` +// directory tests don't have access to `cfg(tests)` functions and we don't want to force +// users to depend on proptest or manually enable features to test. + +use proptest::strategy::Strategy; +use std::env::var; + +mod raw; + +pub(crate) const ENV_PD_ADDR: &str = "PD_ADDR"; +pub(crate) const PROPTEST_KEY_MAX: usize = 1024 * 2; // 2 KB +pub(crate) const PROPTEST_VALUE_MAX: usize = 1024 * 16; // 16 KB +pub(crate) const PROPTEST_BATCH_SIZE_MAX: usize = 16; + +pub fn arb_batch( + single_strategy: impl Strategy, + max_batch_size: impl Into>, +) -> impl Strategy> { + let max_batch_size = max_batch_size.into().unwrap_or(PROPTEST_BATCH_SIZE_MAX); + proptest::collection::vec(single_strategy, 0..max_batch_size) +} + +pub fn pd_addr() -> Vec { + var(ENV_PD_ADDR) + .expect(&format!("Expected {}:", ENV_PD_ADDR)) + .split(",") + .map(From::from) + .collect() +} diff --git a/src/proptests/raw.rs b/src/proptests/raw.rs new file mode 100644 index 00000000..972016e2 --- /dev/null +++ b/src/proptests/raw.rs @@ -0,0 +1,55 @@ +// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. + +use super::{arb_batch, pd_addr}; +use crate::{raw::Client, Config, KvPair, Value}; +use futures::executor::block_on; +use proptest::{arbitrary::any, proptest}; + +proptest! { + /// Test single point (put, get, delete) operations on keys. + #[test] + #[cfg_attr(not(feature = "integration-tests"), ignore)] + fn point( + pair in any::(), + ) { + let client = block_on(Client::connect(Config::new(pd_addr()))).unwrap(); + + block_on( + client.put(pair.key().clone(), pair.value().clone()) + ).unwrap(); + + let out_value = block_on( + client.get(pair.key().clone()) + ).unwrap(); + assert_eq!(Some(Value::from(pair.value().clone())), out_value); + + block_on( + client.delete(pair.key().clone()) + ).unwrap(); + } +} + +proptest! { + /// Test batch (put, get, delete) operations on keys. + #[test] + #[cfg_attr(not(feature = "integration-tests"), ignore)] + fn batch( + kvs in arb_batch(any::(), None), + ) { + let client = block_on(Client::connect(Config::new(pd_addr()))).unwrap(); + let keys = kvs.iter().map(|kv| kv.key()).cloned().collect::>(); + + block_on( + client.batch_put(kvs.clone()) + ).unwrap(); + + let out_value = block_on( + client.batch_get(keys.clone()) + ).unwrap(); + assert_eq!(kvs, out_value); + + block_on( + client.batch_delete(keys.clone()) + ).unwrap(); + } +} diff --git a/tests/integration_tests/mod.rs b/tests/integration_tests/mod.rs deleted file mode 100644 index f304a669..00000000 --- a/tests/integration_tests/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. - -mod raw; - -use std::env::var; -const ENV_PD_ADDR: &str = "PD_ADDR"; - -pub fn pd_addr() -> Vec { - var(ENV_PD_ADDR) - .expect(&format!("Expected {}:", ENV_PD_ADDR)) - .split(",") - .map(From::from) - .collect() -} diff --git a/tests/integration_tests/raw.rs b/tests/integration_tests/raw.rs deleted file mode 100644 index 1e2260f0..00000000 --- a/tests/integration_tests/raw.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0. - -const NUM_TEST_KEYS: u32 = 100; -use crate::integration_tests::pd_addr; -use tikv_client::{raw::Client, Config, Key, KvPair, Value}; - -fn generate_key(id: i32) -> Key { - format!("testkey_{}", id).into_bytes().into() -} - -fn generate_value(id: i32) -> Value { - format!("testvalue_{}", id).into_bytes().into() -} - -async fn wipe_all(client: &Client) { - let test_key_start = generate_key(0); - let test_key_end = generate_key(NUM_TEST_KEYS as i32 - 1); - client - .delete_range(test_key_start..test_key_end) - .await - .expect("Could not delete test keys"); -} - -async fn connect() -> Client { - let client = Client::connect(Config::new(pd_addr())) - .await - .expect("Could not connect to tikv"); - wipe_all(&client).await; - client -} - -async fn test_empty(client: &Client) { - let test_key_start = generate_key(0); - let test_key_end = generate_key(NUM_TEST_KEYS as i32 - 1); - - assert!(client - .scan(test_key_start..test_key_end, NUM_TEST_KEYS) - .await - .expect("Could not scan") - .is_empty()); -} - -async fn test_existence<'a>( - client: &'a Client, - existing_pairs: &'a [KvPair], - not_existing_keys: Vec, -) { - let test_key_start = generate_key(0); - let test_key_end = generate_key(NUM_TEST_KEYS as i32 - 1); - - for pair in existing_pairs.iter().map(Clone::clone) { - let (key, value) = pair.into(); - assert_eq!( - client - .get(key) - .await - .expect("Could not get value") - .expect("key doesn't exist"), - value.clone(), - ); - } - - for key in not_existing_keys.clone().into_iter() { - let r = client.get(key).await.expect("Cound not get value"); - assert!(r.is_none()); - } - - let mut existing_keys = Vec::with_capacity(existing_pairs.len()); - let mut existing_key_only_pairs = Vec::with_capacity(existing_pairs.len()); - for pair in existing_pairs.iter() { - let key = pair.key().clone(); - existing_keys.push(key.clone()); - existing_key_only_pairs.push(KvPair::new(key, Value::default())); - } - - let mut all_keys = existing_keys.clone(); - all_keys.extend_from_slice(¬_existing_keys); - - assert_eq!( - client - .batch_get(all_keys) - .await - .expect("Could not get value in batch"), - existing_pairs, - ); - - assert_eq!( - client - .batch_get(not_existing_keys) - .await - .expect("Could not get value in batch"), - Vec::new(), - ); - - assert_eq!( - client - .scan(test_key_start.clone()..test_key_end.clone(), NUM_TEST_KEYS) - .await - .expect("Could not scan"), - existing_pairs, - ); - - assert_eq!( - client - .with_key_only(true) - .scan(test_key_start.clone()..test_key_end.clone(), NUM_TEST_KEYS) - .await - .expect("Could not scan"), - existing_key_only_pairs, - ); -} - -#[runtime::test(runtime_tokio::Tokio)] -async fn basic_raw_test() { - let client = connect().await; - - test_empty(&client).await; - - assert!(client.put(generate_key(0), generate_value(0)).await.is_ok()); - let existing = &[KvPair::new(generate_key(0), generate_value(0))]; - test_existence(&client, existing, vec![generate_key(1), generate_key(2)]).await; - - let empty_pairs = Vec::new(); - assert!(client.delete(generate_key(0)).await.is_ok()); - test_existence( - &client, - &empty_pairs, - vec![generate_key(0), generate_key(1), generate_key(2)], - ) - .await; - - let pairs: Vec = (0..10) - .map(|i| KvPair::new(generate_key(i), generate_value(i))) - .collect(); - assert!(client.batch_put(pairs.clone()).await.is_ok()); - test_existence( - &client, - &pairs, - vec![generate_key(10), generate_key(11), generate_key(12)], - ) - .await; - - let keys: Vec = vec![generate_key(8), generate_key(9)]; - assert!(client.batch_delete(keys).await.is_ok()); - let mut pairs = pairs; - pairs.truncate(8); - test_existence( - &client, - &pairs, - vec![generate_key(8), generate_key(9), generate_key(10)], - ) - .await; - - wipe_all(&client).await; - test_existence( - &client, - &empty_pairs, - pairs.into_iter().map(|x| x.into_key()).collect(), - ) - .await; -} diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index 83b68e9b..00000000 --- a/tests/test.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2018 TiKV Project Authors. Licensed under Apache-2.0. - -#![feature(async_await)] -#![type_length_limit = "4500458"] - -#[cfg(feature = "integration-tests")] -mod integration_tests;