Change datetime to serialize as secs/nanos instead of string. (#1951)

This commit is contained in:
Finn Bear 2023-06-13 14:13:10 -07:00 committed by GitHub
parent 6f79f1556a
commit 12cb551156
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 33 deletions

View file

@ -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<Utc>);
pub struct Datetime(#[serde(with = "ts_binary")] pub DateTime<Utc>);
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<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
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<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
{
d.deserialize_tuple(2, TimestampVisitor)
}
struct TimestampVisitor;
impl<'de> de::Visitor<'de> for TimestampVisitor {
type Value = DateTime<Utc>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a unix timestamp tuple")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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 {

View file

@ -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<E>(self, value: &str) -> Result<Self::Value, E>
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
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<E>(self, value: &str) -> Result<Self::Value, E>
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)
}
}

View file

@ -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<Utc>;
type Ok = Datetime;
type Error = Error;
type SerializeSeq = Impossible<DateTime<Utc>, Error>;
type SerializeTuple = Impossible<DateTime<Utc>, Error>;
type SerializeTupleStruct = Impossible<DateTime<Utc>, Error>;
type SerializeTupleVariant = Impossible<DateTime<Utc>, Error>;
type SerializeMap = Impossible<DateTime<Utc>, Error>;
type SerializeStruct = Impossible<DateTime<Utc>, Error>;
type SerializeStructVariant = Impossible<DateTime<Utc>, Error>;
type SerializeSeq = Impossible<Datetime, Error>;
type SerializeTuple = SerializeDatetime;
type SerializeTupleStruct = Impossible<Datetime, Error>;
type SerializeTupleVariant = Impossible<Datetime, Error>;
type SerializeMap = Impossible<Datetime, Error>;
type SerializeStruct = Impossible<Datetime, Error>;
type SerializeStructVariant = Impossible<Datetime, Error>;
const EXPECTED: &'static str = "a struct `DateTime<Utc>`";
const EXPECTED: &'static str = "a struct `Datetime`";
#[inline]
fn collect_str<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
where
T: Display,
{
value.to_string().parse().map_err(Error::custom)
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
debug_assert_eq!(len, 2);
Ok(SerializeDatetime::default())
}
#[inline]
fn serialize_newtype_struct<T>(
self,
_name: &'static str,
name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
debug_assert_eq!(name, crate::sql::datetime::TOKEN);
value.serialize(self.wrap())
}
}
#[derive(Default)]
pub(super) struct SerializeDatetime {
secs: Option<i64>,
nanos: Option<u32>,
}
impl serde::ser::SerializeTuple for SerializeDatetime {
type Ok = Datetime;
type Error = Error;
fn serialize_element<T>(&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<Self::Ok, Self::Error> {
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);
}

View file

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

View file

@ -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<T>(
self,
name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
debug_assert_eq!(name, "Version");
Ok(Some(Version(value.serialize(ser::datetime::Serializer.wrap())?)))
}
}