use std::cmp::Ordering;
use std::collections::HashMap;
use std::num::NonZeroI32;
use serde::{Deserialize, Serialize};
use tlua::{Push, PushInto, PushOne, PushOneInto, Void};
use crate::lua_state;
use crate::tlua::{AsLua, LuaRead, ReadResult};
pub type Lsn = u64;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Vclock(HashMap<usize, Lsn>);
impl Vclock {
pub fn current() -> Self {
lua_state()
.eval("return box.info.vclock")
.expect("this should be called after box.cfg")
}
pub fn try_current() -> Result<Self, tlua::LuaError> {
lua_state().eval("return box.info.vclock")
}
pub fn ignore_zero(mut self) -> Self {
println!("{self:?}");
self.0.remove(&0);
self
}
pub fn into_inner(self) -> HashMap<usize, Lsn> {
self.0
}
pub fn get(&self, index: usize) -> Lsn {
self.0.get(&index).copied().unwrap_or(0)
}
}
impl<const N: usize> From<[Lsn; N]> for Vclock {
fn from(from: [Lsn; N]) -> Self {
Self(
from.iter()
.copied()
.enumerate()
.filter(|(_, lsn)| *lsn != 0)
.collect(),
)
}
}
impl PartialOrd for Vclock {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let mut le = true;
let mut ge = true;
for i in self.0.keys().chain(other.0.keys()) {
let a: Lsn = self.0.get(i).copied().unwrap_or(0);
let b: Lsn = other.0.get(i).copied().unwrap_or(0);
le = le && a <= b;
ge = ge && a >= b;
}
if le && ge {
Some(Ordering::Equal)
} else if le && !ge {
Some(Ordering::Less)
} else if !le && ge {
Some(Ordering::Greater)
} else {
None
}
}
}
impl<L> LuaRead<L> for Vclock
where
L: AsLua,
{
fn lua_read_at_position(lua: L, index: NonZeroI32) -> ReadResult<Self, L> {
match HashMap::lua_read_at_position(lua, index) {
Ok(v) => Ok(Self(v)),
Err((l, err)) => {
let err = err
.when("converting Lua table to Vclock")
.expected("{[i] = lsn}");
Err((l, err))
}
}
}
}
impl<L: AsLua> Push<L> for Vclock {
type Err = Void;
fn push_to_lua(&self, lua: L) -> Result<tlua::PushGuard<L>, (Self::Err, L)> {
HashMap::push_to_lua(&self.0, lua).map_err(|_| unreachable!())
}
}
impl<L: AsLua> PushInto<L> for Vclock {
type Err = Void;
fn push_into_lua(self, lua: L) -> Result<tlua::PushGuard<L>, (Self::Err, L)> {
self.push_to_lua(lua)
}
}
impl<L: AsLua> PushOne<L> for Vclock {}
impl<L: AsLua> PushOneInto<L> for Vclock {}
#[cfg(feature = "internal_test")]
mod tests {
use std::collections::HashMap;
use crate::lua_state;
use super::*;
#[crate::test(tarantool = "crate")]
fn test_vclock_current() {
let space_name = crate::temp_space_name!();
let space = crate::space::Space::builder(&space_name).create().unwrap();
space.index_builder("pk").create().unwrap();
let mut vc = Vclock::current();
space.insert(&(1,)).unwrap();
vc.0.entry(1).and_modify(|v| *v += 1);
assert_eq!(Vclock::current(), vc);
}
#[crate::test(tarantool = "crate")]
#[allow(clippy::eq_op)]
fn test_vclock_cmp() {
assert_eq!(
Vclock::from([0, 0, 12, 0]).into_inner(),
HashMap::from([(2, 12)])
);
assert_eq!(
Vclock::from([99, 101]).ignore_zero().into_inner(),
HashMap::from([(1, 101)])
);
let vc_11 = Vclock::from([0, 1, 1]);
let vc_12 = Vclock::from([0, 1, 2]);
let vc_21 = Vclock::from([0, 2, 1]);
assert_eq!(vc_11, vc_11);
assert_ne!(vc_11, vc_12);
assert_ne!(vc_12, vc_21);
assert_ne!(vc_21, vc_11);
assert!(vc_21 > vc_11);
assert!(vc_12 > vc_11);
assert_eq!(vc_12.partial_cmp(&vc_21), None);
assert!(Vclock::from([100, 200]) > Vclock::from([100]));
assert!(Vclock::from([1, 10, 100]) > Vclock::from([1, 9, 88]));
}
#[crate::test(tarantool = "crate")]
fn test_vclock_luaread() {
let l = lua_state();
let luaread = |s| l.eval::<Vclock>(s);
assert_eq!(luaread("return {}").unwrap(), Vclock::from([]));
assert_eq!(luaread("return {[0] = 100}").unwrap(), Vclock::from([100]));
assert_eq!(
luaread("return {101, 102}").unwrap(),
Vclock::from([0, 101, 102])
);
assert_eq!(
luaread("return {[33] = 103}").unwrap(),
Vclock(HashMap::from([(33, 103)]))
);
assert_eq!(
luaread("return {[1] = 'help'}").unwrap_err().to_string(),
"failed reading Lua value: u64 expected, got string
while converting Lua table to Vclock: {[i] = lsn} expected, got table value of wrong type
while reading value(s) returned by Lua: tarantool::vclock::Vclock expected, got table"
);
assert_eq!(
luaread("return {foo = 16}").unwrap_err().to_string(),
"failed reading Lua value: usize expected, got string
while converting Lua table to Vclock: {[i] = lsn} expected, got table key of wrong type
while reading value(s) returned by Lua: tarantool::vclock::Vclock expected, got table"
);
assert_eq!(
luaread("return 'not-a-vclock'").unwrap_err().to_string(),
"failed converting Lua table to Vclock: {[i] = lsn} expected, got string
while reading value(s) returned by Lua: tarantool::vclock::Vclock expected, got string"
);
}
#[crate::test(tarantool = "crate")]
fn test_vclock_luapush() {
let l = lua_state();
let lsns: HashMap<usize, Lsn> = l
.eval_with("return ...", Vclock::from([100, 0, 102]))
.unwrap();
assert_eq!(lsns, HashMap::from([(0, 100), (2, 102)]));
}
#[crate::test(tarantool = "crate")]
fn test_vclock_serde() {
let mp = rmp_serde::to_vec(&HashMap::from([(3, 30)])).unwrap();
assert_eq!(mp, b"\x81\x03\x1e");
let vc: Vclock = rmp_serde::from_read_ref(&mp).unwrap();
assert_eq!(vc, Vclock::from([0, 0, 0, 30]));
assert_eq!(rmp_serde::to_vec(&vc).unwrap(), mp);
let invalid_mp = b"\x81\x00\xa0"; let err: Result<Vclock, _> = rmp_serde::from_read_ref(invalid_mp);
assert_eq!(
err.unwrap_err().to_string(),
"invalid type: string \"\", expected u64"
)
}
}