From 12cb5511567c8510794eed7f15e09816e6a27b31 Mon Sep 17 00:00:00 2001 From: Finn Bear Date: Tue, 13 Jun 2023 14:13:10 -0700 Subject: [PATCH] Change datetime to serialize as secs/nanos instead of string. (#1951) --- lib/src/sql/datetime.rs | 59 +++++++++++++++- lib/src/sql/regex.rs | 31 +++++++-- lib/src/sql/value/serde/ser/datetime/mod.rs | 74 +++++++++++++++------ lib/src/sql/value/serde/ser/value/mod.rs | 3 +- lib/src/sql/value/serde/ser/version/opt.rs | 16 ++++- 5 files changed, 150 insertions(+), 33 deletions(-) diff --git a/lib/src/sql/datetime.rs b/lib/src/sql/datetime.rs index 926f794c..acc6c1e7 100644 --- a/lib/src/sql/datetime.rs +++ b/lib/src/sql/datetime.rs @@ -3,8 +3,10 @@ use crate::sql::duration::Duration; use crate::sql::error::IResult; use crate::sql::escape::quote_str; use crate::sql::strand::Strand; -use chrono::{DateTime, FixedOffset, Offset, SecondsFormat, TimeZone, Utc}; -use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; +use chrono::{ + DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone, + Utc, +}; use nom::branch::alt; use nom::character::complete::char; use nom::combinator::map; @@ -22,7 +24,7 @@ pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Datetime"; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] #[serde(rename = "$surrealdb::private::sql::Datetime")] -pub struct Datetime(pub DateTime); +pub struct Datetime(#[serde(with = "ts_binary")] pub DateTime); impl Default for Datetime { fn default() -> Self { @@ -270,6 +272,57 @@ fn sign(i: &str) -> IResult<&str, i32> { })(i) } +/// Lexicographic, relatively size efficient binary serialization +pub mod ts_binary { + use chrono::{offset::TimeZone, DateTime, Utc}; + use core::fmt; + use serde::{ + de::{self, SeqAccess}, + ser::{self, SerializeTuple}, + }; + + /// Serialize a UTC datetime into an integer number of nanoseconds since the epoch + pub fn serialize(dt: &DateTime, serializer: S) -> Result + where + S: ser::Serializer, + { + let mut tuple = serializer.serialize_tuple(2)?; + tuple.serialize_element(&dt.timestamp())?; + tuple.serialize_element(&dt.timestamp_subsec_nanos())?; + tuple.end() + } + + /// Deserialize a [`DateTime`] from a nanosecond timestamp + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_tuple(2, TimestampVisitor) + } + + struct TimestampVisitor; + + impl<'de> de::Visitor<'de> for TimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp tuple") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let secs = seq.next_element()?.ok_or_else(|| de::Error::custom("invalid timestamp"))?; + let nanos = + seq.next_element()?.ok_or_else(|| de::Error::custom("invalid timestamp"))?; + Utc.timestamp_opt(secs, nanos) + .single() + .ok_or_else(|| de::Error::custom("invalid timestamp")) + } + } +} + #[cfg(test)] mod tests { diff --git a/lib/src/sql/regex.rs b/lib/src/sql/regex.rs index 7b7df2db..d9ebf5f0 100644 --- a/lib/src/sql/regex.rs +++ b/lib/src/sql/regex.rs @@ -90,24 +90,41 @@ impl<'de> Deserialize<'de> for Regex { where D: Deserializer<'de>, { - struct RegexVisitor; + struct RegexNewtypeVisitor; - impl<'de> Visitor<'de> for RegexVisitor { + impl<'de> Visitor<'de> for RegexNewtypeVisitor { type Value = Regex; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a regex str") + formatter.write_str("a regex newtype") } - fn visit_str(self, value: &str) -> Result + fn visit_newtype_struct(self, deserializer: D) -> Result where - E: de::Error, + D: Deserializer<'de>, { - Regex::from_str(value).map_err(|_| de::Error::custom("invalid regex")) + struct RegexVisitor; + + impl<'de> Visitor<'de> for RegexVisitor { + type Value = Regex; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a regex str") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Regex::from_str(value).map_err(|_| de::Error::custom("invalid regex")) + } + } + + deserializer.deserialize_str(RegexVisitor) } } - deserializer.deserialize_str(RegexVisitor) + deserializer.deserialize_newtype_struct(TOKEN, RegexNewtypeVisitor) } } diff --git a/lib/src/sql/value/serde/ser/datetime/mod.rs b/lib/src/sql/value/serde/ser/datetime/mod.rs index aac53d18..7d4adc1a 100644 --- a/lib/src/sql/value/serde/ser/datetime/mod.rs +++ b/lib/src/sql/value/serde/ser/datetime/mod.rs @@ -1,58 +1,94 @@ use crate::err::Error; use crate::sql::value::serde::ser; +use crate::sql::Datetime; use chrono::offset::Utc; -use chrono::DateTime; +use chrono::TimeZone; +use ser::Serializer as _; use serde::ser::Error as _; use serde::ser::Impossible; use serde::Serialize; -use std::fmt::Display; pub(super) struct Serializer; impl ser::Serializer for Serializer { - type Ok = DateTime; + type Ok = Datetime; type Error = Error; - type SerializeSeq = Impossible, Error>; - type SerializeTuple = Impossible, Error>; - type SerializeTupleStruct = Impossible, Error>; - type SerializeTupleVariant = Impossible, Error>; - type SerializeMap = Impossible, Error>; - type SerializeStruct = Impossible, Error>; - type SerializeStructVariant = Impossible, Error>; + type SerializeSeq = Impossible; + type SerializeTuple = SerializeDatetime; + type SerializeTupleStruct = Impossible; + type SerializeTupleVariant = Impossible; + type SerializeMap = Impossible; + type SerializeStruct = Impossible; + type SerializeStructVariant = Impossible; - const EXPECTED: &'static str = "a struct `DateTime`"; + const EXPECTED: &'static str = "a struct `Datetime`"; #[inline] - fn collect_str(self, value: &T) -> Result - where - T: Display, - { - value.to_string().parse().map_err(Error::custom) + fn serialize_tuple(self, len: usize) -> Result { + debug_assert_eq!(len, 2); + Ok(SerializeDatetime::default()) } #[inline] fn serialize_newtype_struct( self, - _name: &'static str, + name: &'static str, value: &T, ) -> Result where T: ?Sized + Serialize, { + debug_assert_eq!(name, crate::sql::datetime::TOKEN); value.serialize(self.wrap()) } } +#[derive(Default)] +pub(super) struct SerializeDatetime { + secs: Option, + nanos: Option, +} + +impl serde::ser::SerializeTuple for SerializeDatetime { + type Ok = Datetime; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize + ?Sized, + { + if self.secs.is_none() { + self.secs = Some(value.serialize(ser::primitive::i64::Serializer.wrap())?); + } else if self.nanos.is_none() { + self.nanos = Some(value.serialize(ser::primitive::u32::Serializer.wrap())?); + } else { + return Err(Error::custom(format!("unexpected `Datetime` 3rd field`"))); + } + Ok(()) + } + + fn end(self) -> Result { + match (self.secs, self.nanos) { + (Some(secs), Some(nanos)) => Utc + .timestamp_opt(secs, nanos) + .single() + .map(Datetime) + .ok_or_else(|| Error::custom("invalid `Datetime`")), + _ => Err(Error::custom("`Datetime` missing required value(s)")), + } + } +} + #[cfg(test)] mod tests { use super::*; - use ser::Serializer as _; + use crate::sql::Datetime; use serde::Serialize; #[test] fn now() { - let dt = Utc::now(); + let dt = Datetime::from(Utc::now()); let serialized = dt.serialize(Serializer.wrap()).unwrap(); assert_eq!(dt, serialized); } diff --git a/lib/src/sql/value/serde/ser/value/mod.rs b/lib/src/sql/value/serde/ser/value/mod.rs index d672c20b..b5292ba2 100644 --- a/lib/src/sql/value/serde/ser/value/mod.rs +++ b/lib/src/sql/value/serde/ser/value/mod.rs @@ -10,7 +10,6 @@ use crate::sql::value::serde::ser; use crate::sql::value::Value; use crate::sql::Block; use crate::sql::Bytes; -use crate::sql::Datetime; use crate::sql::Duration; use crate::sql::Future; use crate::sql::Ident; @@ -227,7 +226,7 @@ impl ser::Serializer for Serializer { Ok(Value::Uuid(Uuid(value.serialize(ser::uuid::Serializer.wrap())?))) } sql::datetime::TOKEN => { - Ok(Value::Datetime(Datetime(value.serialize(ser::datetime::Serializer.wrap())?))) + Ok(Value::Datetime(value.serialize(ser::datetime::Serializer.wrap())?)) } _ => value.serialize(self.wrap()), } diff --git a/lib/src/sql/value/serde/ser/version/opt.rs b/lib/src/sql/value/serde/ser/version/opt.rs index 3e036bbf..b6e4fb0f 100644 --- a/lib/src/sql/value/serde/ser/version/opt.rs +++ b/lib/src/sql/value/serde/ser/version/opt.rs @@ -1,6 +1,5 @@ use crate::err::Error; use crate::sql::value::serde::ser; -use crate::sql::Datetime; use crate::sql::Version; use serde::ser::Impossible; use serde::ser::Serialize; @@ -31,7 +30,20 @@ impl ser::Serializer for Serializer { where T: ?Sized + Serialize, { - Ok(Some(Version(Datetime(value.serialize(ser::datetime::Serializer.wrap())?)))) + value.serialize(self.wrap()) + } + + #[inline] + fn serialize_newtype_struct( + self, + name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + debug_assert_eq!(name, "Version"); + Ok(Some(Version(value.serialize(ser::datetime::Serializer.wrap())?))) } }