Add proptest support (#68)

* Add proptest support

Signed-off-by: Ana Hobden <operator@hoverbear.org>

* fmt

Signed-off-by: Ana Hobden <operator@hoverbear.org>

* Make proptest feature more consistent

Signed-off-by: Ana Hobden <operator@hoverbear.org>

* Fixes #69

Signed-off-by: Ana Hobden <operator@hoverbear.org>

* fmt

Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
Ana Hobden 2019-07-04 17:35:11 -07:00 committed by GitHub
parent 75eae033a7
commit 1815a3bf54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 634 additions and 671 deletions

View File

@ -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"

489
src/kv.rs
View File

@ -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<u8>`
/// 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<u8> = 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<Key>`, 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<u8>);
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<Key> {
if self.zero_terminated() {
self.0.pop().unwrap();
Bound::Excluded(self)
} else {
Bound::Included(self)
}
}
#[inline]
fn into_upper_bound(mut self) -> Bound<Key> {
if self.zero_terminated() {
self.0.pop().unwrap();
Bound::Included(self)
} else {
Bound::Excluded(self)
}
}
}
impl From<Vec<u8>> for Key {
fn from(v: Vec<u8>) -> Self {
Key(v)
}
}
impl From<String> 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<Vec<u8>> for Key {
fn into(self) -> Vec<u8> {
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<u8>`
/// 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<u8> = 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<Value>`, 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<u8>);
impl Value {
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Self {
Value(v)
}
}
impl From<String> 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<Vec<u8>> for Value {
fn into(self) -> Vec<u8> {
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<KvPair>`, 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<Key>, value: impl Into<Value>) -> 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<Key>) {
self.0 = k.into();
}
/// Set the `Value` part of the `KvPair`.
#[inline]
pub fn set_value(&mut self, v: impl Into<Value>) {
self.1 = v.into();
}
}
impl<K, V> From<(K, V)> for KvPair
where
K: Into<Key>,
V: Into<Value>,
{
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<BoundRange>`.
///
/// In TiKV, keys are an ordered sequence of bytes. This means we can have ranges over those
/// bytes. Eg `001` is before `010`.
///
/// `Into<BoundRange>` has implementations for common range types like `a..b`, `a..=b` where `a` and `b`
/// `impl Into<Key>`. You can implement `Into<BoundRange>` 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<Key> = 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<BoundRange>`.
/// which means all of the above types can be passed directly to those functions.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BoundRange {
from: Bound<Key>,
to: Bound<Key>,
}
impl BoundRange {
/// Create a new BoundRange.
///
/// The caller must ensure that `from` is not `Unbounded`.
fn new(from: Bound<Key>, to: Bound<Key>) -> 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<Key>) {
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<T: Into<Key> + Clone> PartialEq<(Bound<T>, Bound<T>)> for BoundRange {
fn eq(&self, other: &(Bound<T>, Bound<T>)) -> bool {
self.from == convert_to_bound_key(other.0.clone())
&& self.to == convert_to_bound_key(other.1.clone())
}
}
impl<T: Into<Key>> From<Range<T>> for BoundRange {
fn from(other: Range<T>) -> BoundRange {
BoundRange::new(
Bound::Included(other.start.into()),
Bound::Excluded(other.end.into()),
)
}
}
impl<T: Into<Key>> From<RangeFrom<T>> for BoundRange {
fn from(other: RangeFrom<T>) -> BoundRange {
BoundRange::new(Bound::Included(other.start.into()), Bound::Unbounded)
}
}
impl<T: Into<Key>> From<RangeInclusive<T>> for BoundRange {
fn from(other: RangeInclusive<T>) -> BoundRange {
let (start, end) = other.into_inner();
BoundRange::new(Bound::Included(start.into()), Bound::Included(end.into()))
}
}
impl<T: Into<Key>> From<(T, Option<T>)> for BoundRange {
fn from(other: (T, Option<T>)) -> 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<T: Into<Key>> 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<T: Into<Key> + Eq> TryFrom<(Bound<T>, Bound<T>)> for BoundRange {
type Error = Error;
fn try_from(bounds: (Bound<T>, Bound<T>)) -> Result<BoundRange> {
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<K: Into<Key>>(b: Bound<K>) -> Bound<Key> {
match b {
Bound::Included(k) => Bound::Included(k.into()),
Bound::Excluded(k) => Bound::Excluded(k.into()),
Bound::Unbounded => Bound::Unbounded,
}
}

189
src/kv/bound_range.rs Normal file
View File

@ -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<BoundRange>`.
///
/// In TiKV, keys are an ordered sequence of bytes. This means we can have ranges over those
/// bytes. Eg `001` is before `010`.
///
/// `Into<BoundRange>` has implementations for common range types like `a..b`, `a..=b` where `a` and `b`
/// `impl Into<Key>`. You can implement `Into<BoundRange>` 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<Key> = 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<BoundRange>`.
/// 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<Key>,
to: Bound<Key>,
}
impl BoundRange {
/// Create a new BoundRange.
///
/// The caller must ensure that `from` is not `Unbounded`.
fn new(from: Bound<Key>, to: Bound<Key>) -> 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<Key>) {
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<T: Into<Key> + Clone> PartialEq<(Bound<T>, Bound<T>)> for BoundRange {
fn eq(&self, other: &(Bound<T>, Bound<T>)) -> bool {
self.from == convert_to_bound_key(other.0.clone())
&& self.to == convert_to_bound_key(other.1.clone())
}
}
impl<T: Into<Key>> From<Range<T>> for BoundRange {
fn from(other: Range<T>) -> BoundRange {
BoundRange::new(
Bound::Included(other.start.into()),
Bound::Excluded(other.end.into()),
)
}
}
impl<T: Into<Key>> From<RangeFrom<T>> for BoundRange {
fn from(other: RangeFrom<T>) -> BoundRange {
BoundRange::new(Bound::Included(other.start.into()), Bound::Unbounded)
}
}
impl<T: Into<Key>> From<RangeInclusive<T>> for BoundRange {
fn from(other: RangeInclusive<T>) -> BoundRange {
let (start, end) = other.into_inner();
BoundRange::new(Bound::Included(start.into()), Bound::Included(end.into()))
}
}
impl<T: Into<Key>> From<(T, Option<T>)> for BoundRange {
fn from(other: (T, Option<T>)) -> 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<T: Into<Key>> 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<T: Into<Key> + Eq> TryFrom<(Bound<T>, Bound<T>)> for BoundRange {
type Error = Error;
fn try_from(bounds: (Bound<T>, Bound<T>)) -> Result<BoundRange> {
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<K: Into<Key>>(b: Bound<K>) -> Bound<Key> {
match b {
Bound::Included(k) => Bound::Included(k.into()),
Bound::Excluded(k) => Bound::Excluded(k.into()),
Bound::Unbounded => Bound::Unbounded,
}
}

129
src/kv/key.rs Normal file
View File

@ -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<u8>`
/// 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<u8> = 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<Key>`, 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::<Vec<u8>>((size_range(crate::proptests::PROPTEST_KEY_MAX), ()))"
)
)]
pub(super) Vec<u8>,
);
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<Key> {
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<Key> {
if self.zero_terminated() {
self.0.pop().unwrap();
Bound::Included(self)
} else {
Bound::Excluded(self)
}
}
}
impl From<Vec<u8>> for Key {
fn from(v: Vec<u8>) -> Self {
Key(v)
}
}
impl From<String> 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<Vec<u8>> for Key {
fn into(self) -> Vec<u8> {
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))
}
}

103
src/kv/kvpair.rs Normal file
View File

@ -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<KvPair>`, 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<Key>, value: impl Into<Value>) -> 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<Key>) {
self.0 = k.into();
}
/// Set the `Value` part of the `KvPair`.
#[inline]
pub fn set_value(&mut self, v: impl Into<Value>) {
self.1 = v.into();
}
}
impl<K, V> From<(K, V)> for KvPair
where
K: Into<Key>,
V: Into<Value>,
{
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)),
}
}
}

22
src/kv/mod.rs Normal file
View File

@ -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(())
}
}

101
src/kv/value.rs Normal file
View File

@ -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<u8>`
/// 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<u8> = 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<Value>`, 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::<Vec<u8>>((size_range(crate::proptests::PROPTEST_VALUE_MAX), ()))"
)
)]
pub(super) Vec<u8>,
);
impl Value {
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<Vec<u8>> for Value {
fn from(v: Vec<u8>) -> Self {
Value(v)
}
}
impl From<String> 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<Vec<u8>> for Value {
fn into(self) -> Vec<u8> {
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)),
}
}
}

View File

@ -78,6 +78,8 @@ mod compat;
mod config;
mod errors;
mod kv;
#[cfg(test)]
mod proptests;
pub mod raw;
mod rpc;
pub mod transaction;

31
src/proptests/mod.rs Normal file
View File

@ -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<T: core::fmt::Debug>(
single_strategy: impl Strategy<Value = T>,
max_batch_size: impl Into<Option<usize>>,
) -> impl Strategy<Value = Vec<T>> {
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<String> {
var(ENV_PD_ADDR)
.expect(&format!("Expected {}:", ENV_PD_ADDR))
.split(",")
.map(From::from)
.collect()
}

55
src/proptests/raw.rs Normal file
View File

@ -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::<KvPair>(),
) {
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::<KvPair>(), None),
) {
let client = block_on(Client::connect(Config::new(pd_addr()))).unwrap();
let keys = kvs.iter().map(|kv| kv.key()).cloned().collect::<Vec<_>>();
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();
}
}

View File

@ -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<String> {
var(ENV_PD_ADDR)
.expect(&format!("Expected {}:", ENV_PD_ADDR))
.split(",")
.map(From::from)
.collect()
}

View File

@ -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<Key>,
) {
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(&not_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<KvPair> = (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<Key> = 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;
}

View File

@ -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;