Change datetime to serialize as secs/nanos instead of string. (#1951)
This commit is contained in:
parent
6f79f1556a
commit
12cb551156
5 changed files with 150 additions and 33 deletions
|
@ -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 {
|
||||
|
||||
|
|
|
@ -87,6 +87,19 @@ impl Serialize for Regex {
|
|||
|
||||
impl<'de> Deserialize<'de> for Regex {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct RegexNewtypeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RegexNewtypeVisitor {
|
||||
type Value = Regex;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a regex newtype")
|
||||
}
|
||||
|
||||
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
|
@ -111,6 +124,10 @@ impl<'de> Deserialize<'de> for Regex {
|
|||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_newtype_struct(TOKEN, RegexNewtypeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn regex(i: &str) -> IResult<&str, Regex> {
|
||||
let (i, _) = char('/')(i)?;
|
||||
let (i, v) = escaped(is_not("\\/"), '\\', anychar)(i)?;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
|
|
|
@ -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())?)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue