From 100a7674344d47344eb0582a1e77590e9a97bbd4 Mon Sep 17 00:00:00 2001 From: lowit Date: Sun, 12 Feb 2023 22:57:56 +0300 Subject: [PATCH 1/4] datetime: first dirty implementation --- .gitignore | 1 + tarantool/Cargo.toml | 1 + tarantool/src/datetime.rs | 130 ++++++++++++++++++++++++++++++++++++++ tarantool/src/lib.rs | 1 + 4 files changed, 133 insertions(+) create mode 100644 tarantool/src/datetime.rs diff --git a/.gitignore b/.gitignore index 1b9ff896..000e572d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target Cargo.lock /.idea/ +.vscode diff --git a/tarantool/Cargo.toml b/tarantool/Cargo.toml index 3cca52a2..76b5ed57 100644 --- a/tarantool/Cargo.toml +++ b/tarantool/Cargo.toml @@ -44,6 +44,7 @@ uuid = "0.8.2" futures = "0.3.25" linkme = "0.2.10" tester = { version = "0.7.0", optional = true } +time = "0.3.17" [target.'cfg(not(all(target_arch = "aarch64", target_os = "macos")))'.dependencies] va_list = "0.1.3" diff --git a/tarantool/src/datetime.rs b/tarantool/src/datetime.rs new file mode 100644 index 00000000..48987747 --- /dev/null +++ b/tarantool/src/datetime.rs @@ -0,0 +1,130 @@ +use serde::{Deserialize, Serialize}; +use std::os::raw::c_char; +use std::{convert::TryInto, fmt::Display}; +use time::{Duration, UtcOffset}; + +type Inner = time::OffsetDateTime; + +const MP_DATETIME: std::os::raw::c_char = 4; + +#[derive(Debug, Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Datetime { + inner: Inner, +} + +impl Datetime { + #[inline(always)] + pub fn from_inner(inner: Inner) -> Self { + inner.into() + } + + #[inline(always)] + pub fn into_inner(self) -> Inner { + self.into() + } + + /// Convert an array of bytes in the endian order into a `DateTime`. + #[inline(always)] + pub fn from_bytes(bytes: [u8; 16]) -> Self { + let sec_bytes: [u8; 8] = bytes[0..8].try_into().unwrap(); + let nsec_bytes: [u8; 4] = bytes[8..12].try_into().unwrap(); + let tzoffest_bytes: [u8; 2] = bytes[12..14].try_into().unwrap(); + + let secs = i64::from_le_bytes(sec_bytes); + let nsecs = u32::from_le_bytes(nsec_bytes); + let tzoffset: i32 = i16::from_le_bytes(tzoffest_bytes).into(); + + let dt = Inner::from_unix_timestamp(secs) + .unwrap() + .to_offset(UtcOffset::from_whole_seconds(tzoffset * 60).unwrap()) + + Duration::nanoseconds(nsecs as i64); + + dt.into() + } + + /// Convert a slice of bytes in the little endian order into a `DateTime`. Return + /// `None` if there's not enough bytes in the slice. + #[inline(always)] + pub fn try_from_slice(bytes: &[u8]) -> Option { + std::convert::TryInto::try_into(bytes) + .ok() + .map(Self::from_bytes) + } + + /// Return an array of bytes in the little endian order + #[inline(always)] + pub fn as_bytes(&self) -> [u8; 16] { + let mut buf: Vec = vec![]; + + buf.extend_from_slice(&self.inner.unix_timestamp().to_le_bytes()); + buf.extend_from_slice(&self.inner.nanosecond().to_le_bytes()); + buf.extend_from_slice(&self.inner.offset().whole_minutes().to_le_bytes()); + buf.resize(16, 0); + + buf.try_into().unwrap() + } +} + +impl From for Datetime { + #[inline(always)] + fn from(inner: Inner) -> Self { + Self { inner } + } +} + +impl From for Inner { + #[inline(always)] + fn from(dt: Datetime) -> Self { + dt.inner + } +} + +impl Display for Datetime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.fmt(f) + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Tuple +//////////////////////////////////////////////////////////////////////////////// + +impl serde::Serialize for Datetime { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + #[derive(Serialize)] + struct _ExtStruct((c_char, serde_bytes::ByteBuf)); + + let data = self.as_bytes(); + _ExtStruct((MP_DATETIME, serde_bytes::ByteBuf::from(&data as &[_]))).serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Datetime { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct _ExtStruct((c_char, serde_bytes::ByteBuf)); + + let _ExtStruct((kind, bytes)) = serde::Deserialize::deserialize(deserializer)?; + + if kind != MP_DATETIME { + return Err(serde::de::Error::custom(format!( + "Expected Datetime, found msgpack ext #{}", + kind + ))); + } + + let data = bytes.into_vec(); + Self::try_from_slice(&data).ok_or_else(|| { + serde::de::Error::custom(format!( + "Not enough bytes for Datetime: expected 16, got {}", + data.len() + )) + }) + } +} diff --git a/tarantool/src/lib.rs b/tarantool/src/lib.rs index b400dd2f..5e7c60a5 100644 --- a/tarantool/src/lib.rs +++ b/tarantool/src/lib.rs @@ -184,6 +184,7 @@ pub mod uuid; #[cfg(all(target_arch = "aarch64", target_os = "macos"))] #[doc(hidden)] mod va_list; +pub mod datetime; /// `#[tarantool::proc]` is a macro attribute for creating stored procedure /// functions. From 2f18021b02599a30c08e39659e536f200f2b00f7 Mon Sep 17 00:00:00 2001 From: lowit Date: Fri, 17 Feb 2023 00:45:16 +0300 Subject: [PATCH 2/4] datetime: add tests --- tarantool/src/datetime.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tarantool/src/datetime.rs b/tarantool/src/datetime.rs index 48987747..87ff87dc 100644 --- a/tarantool/src/datetime.rs +++ b/tarantool/src/datetime.rs @@ -128,3 +128,42 @@ impl<'de> serde::Deserialize<'de> for Datetime { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use time_macros::datetime; + + #[test] + pub fn serialize() { + let exp = [ + 216, 4, 176, 109, 109, 56, 0, 0, 0, 0, 0, 0, 0, 0, 76, 255, 0, 0, + ]; + let dt: Datetime = datetime!(2000-01-01 0:00 -3).into(); + let buf = vec![]; + let mut s = rmp_serde::encode::Serializer::new(buf); + dt.serialize(&mut s).unwrap(); + let result = s.get_ref(); + + assert_eq!(18, result.len()); + + let matching = exp + .iter() + .zip(result.iter()) + .filter(|&(a, b)| a == b) + .count(); + assert!(matching == exp.len() && matching == result.len()); + } + + #[test] + pub fn deserialize() { + let exp: Datetime = datetime!(2000-01-01 0:00 -3).into(); + let data: &[u8] = &[ + 216, 4, 176, 109, 109, 56, 0, 0, 0, 0, 0, 0, 0, 0, 76, 255, 0, 0, + ]; + let mut d = rmp_serde::decode::Deserializer::new(data); + let result = Datetime::deserialize(&mut d).unwrap(); + + assert_eq!(exp, result); + } +} From c888e136328ddfea68cf6a93eccb41304d6aed76 Mon Sep 17 00:00:00 2001 From: lowit Date: Fri, 17 Feb 2023 00:54:23 +0300 Subject: [PATCH 3/4] datetime: add lost dependency --- tarantool/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tarantool/Cargo.toml b/tarantool/Cargo.toml index 76b5ed57..4def4808 100644 --- a/tarantool/Cargo.toml +++ b/tarantool/Cargo.toml @@ -59,3 +59,6 @@ network_client = [] test = ["tester"] all = ["default", "schema", "defer", "test"] internal_test = ["test", "tlua/test"] + +[dev-dependencies] +time-macros = "0.2.6" From 32e279e25068e190a97cab4333e225a815b527a0 Mon Sep 17 00:00:00 2001 From: lowit Date: Fri, 17 Feb 2023 01:15:42 +0300 Subject: [PATCH 4/4] datetime: remove unwraps --- tarantool/src/datetime.rs | 50 ++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/tarantool/src/datetime.rs b/tarantool/src/datetime.rs index 87ff87dc..8859ed80 100644 --- a/tarantool/src/datetime.rs +++ b/tarantool/src/datetime.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::fmt::Display; use std::os::raw::c_char; -use std::{convert::TryInto, fmt::Display}; use time::{Duration, UtcOffset}; type Inner = time::OffsetDateTime; @@ -25,43 +26,48 @@ impl Datetime { /// Convert an array of bytes in the endian order into a `DateTime`. #[inline(always)] - pub fn from_bytes(bytes: [u8; 16]) -> Self { - let sec_bytes: [u8; 8] = bytes[0..8].try_into().unwrap(); - let nsec_bytes: [u8; 4] = bytes[8..12].try_into().unwrap(); - let tzoffest_bytes: [u8; 2] = bytes[12..14].try_into().unwrap(); + pub fn from_bytes(bytes: [u8; 16]) -> Result> { + // pub fn from_bytes(bytes: [u8; 16]) -> Self { + let mut sec_bytes: [u8; 8] = [0; 8]; + sec_bytes.copy_from_slice(&bytes[0..8]); + let mut nsec_bytes: [u8; 4] = [0; 4]; + nsec_bytes.copy_from_slice(&bytes[8..12]); + let mut tzoffest_bytes: [u8; 2] = [0; 2]; + tzoffest_bytes.copy_from_slice(&bytes[12..14]); let secs = i64::from_le_bytes(sec_bytes); let nsecs = u32::from_le_bytes(nsec_bytes); let tzoffset: i32 = i16::from_le_bytes(tzoffest_bytes).into(); - let dt = Inner::from_unix_timestamp(secs) - .unwrap() - .to_offset(UtcOffset::from_whole_seconds(tzoffset * 60).unwrap()) + let dt = Inner::from_unix_timestamp(secs)? + .to_offset(UtcOffset::from_whole_seconds(tzoffset * 60)?) + Duration::nanoseconds(nsecs as i64); - dt.into() + Ok(dt.into()) } /// Convert a slice of bytes in the little endian order into a `DateTime`. Return /// `None` if there's not enough bytes in the slice. #[inline(always)] pub fn try_from_slice(bytes: &[u8]) -> Option { - std::convert::TryInto::try_into(bytes) - .ok() - .map(Self::from_bytes) + let b = std::convert::TryInto::try_into(bytes).ok(); + if let Some(b) = b { + Self::from_bytes(b).ok() + } else { + None + } } /// Return an array of bytes in the little endian order #[inline(always)] pub fn as_bytes(&self) -> [u8; 16] { - let mut buf: Vec = vec![]; + let mut buf: [u8; 16] = [0; 16]; - buf.extend_from_slice(&self.inner.unix_timestamp().to_le_bytes()); - buf.extend_from_slice(&self.inner.nanosecond().to_le_bytes()); - buf.extend_from_slice(&self.inner.offset().whole_minutes().to_le_bytes()); - buf.resize(16, 0); + buf[0..8].copy_from_slice(&self.inner.unix_timestamp().to_le_bytes()); + buf[8..12].copy_from_slice(&self.inner.nanosecond().to_le_bytes()); + buf[12..14].copy_from_slice(&self.inner.offset().whole_minutes().to_le_bytes()); - buf.try_into().unwrap() + buf } } @@ -137,9 +143,9 @@ mod tests { #[test] pub fn serialize() { let exp = [ - 216, 4, 176, 109, 109, 56, 0, 0, 0, 0, 0, 0, 0, 0, 76, 255, 0, 0, + 216, 4, 23, 11, 79, 101, 0, 0, 0, 0, 208, 208, 28, 21, 76, 255, 0, 0, ]; - let dt: Datetime = datetime!(2000-01-01 0:00 -3).into(); + let dt: Datetime = datetime!(2023-11-11 2:03:19.35421 -3).into(); let buf = vec![]; let mut s = rmp_serde::encode::Serializer::new(buf); dt.serialize(&mut s).unwrap(); @@ -157,9 +163,9 @@ mod tests { #[test] pub fn deserialize() { - let exp: Datetime = datetime!(2000-01-01 0:00 -3).into(); + let exp: Datetime = datetime!(2023-11-11 2:03:19.35421 -3).into(); let data: &[u8] = &[ - 216, 4, 176, 109, 109, 56, 0, 0, 0, 0, 0, 0, 0, 0, 76, 255, 0, 0, + 216, 4, 23, 11, 79, 101, 0, 0, 0, 0, 208, 208, 28, 21, 76, 255, 0, 0, ]; let mut d = rmp_serde::decode::Deserializer::new(data); let result = Datetime::deserialize(&mut d).unwrap();