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::error::IResult;
|
||||||
use crate::sql::escape::quote_str;
|
use crate::sql::escape::quote_str;
|
||||||
use crate::sql::strand::Strand;
|
use crate::sql::strand::Strand;
|
||||||
use chrono::{DateTime, FixedOffset, Offset, SecondsFormat, TimeZone, Utc};
|
use chrono::{
|
||||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, SecondsFormat, TimeZone,
|
||||||
|
Utc,
|
||||||
|
};
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::character::complete::char;
|
use nom::character::complete::char;
|
||||||
use nom::combinator::map;
|
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)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
#[serde(rename = "$surrealdb::private::sql::Datetime")]
|
#[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 {
|
impl Default for Datetime {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -270,6 +272,57 @@ fn sign(i: &str) -> IResult<&str, i32> {
|
||||||
})(i)
|
})(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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,19 @@ impl Serialize for Regex {
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Regex {
|
impl<'de> Deserialize<'de> for Regex {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
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
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
|
@ -109,6 +122,10 @@ impl<'de> Deserialize<'de> for Regex {
|
||||||
|
|
||||||
deserializer.deserialize_str(RegexVisitor)
|
deserializer.deserialize_str(RegexVisitor)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_newtype_struct(TOKEN, RegexNewtypeVisitor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn regex(i: &str) -> IResult<&str, Regex> {
|
pub fn regex(i: &str) -> IResult<&str, Regex> {
|
||||||
|
|
|
@ -1,58 +1,94 @@
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::value::serde::ser;
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::Datetime;
|
||||||
use chrono::offset::Utc;
|
use chrono::offset::Utc;
|
||||||
use chrono::DateTime;
|
use chrono::TimeZone;
|
||||||
|
use ser::Serializer as _;
|
||||||
use serde::ser::Error as _;
|
use serde::ser::Error as _;
|
||||||
use serde::ser::Impossible;
|
use serde::ser::Impossible;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
pub(super) struct Serializer;
|
pub(super) struct Serializer;
|
||||||
|
|
||||||
impl ser::Serializer for Serializer {
|
impl ser::Serializer for Serializer {
|
||||||
type Ok = DateTime<Utc>;
|
type Ok = Datetime;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
type SerializeSeq = Impossible<DateTime<Utc>, Error>;
|
type SerializeSeq = Impossible<Datetime, Error>;
|
||||||
type SerializeTuple = Impossible<DateTime<Utc>, Error>;
|
type SerializeTuple = SerializeDatetime;
|
||||||
type SerializeTupleStruct = Impossible<DateTime<Utc>, Error>;
|
type SerializeTupleStruct = Impossible<Datetime, Error>;
|
||||||
type SerializeTupleVariant = Impossible<DateTime<Utc>, Error>;
|
type SerializeTupleVariant = Impossible<Datetime, Error>;
|
||||||
type SerializeMap = Impossible<DateTime<Utc>, Error>;
|
type SerializeMap = Impossible<Datetime, Error>;
|
||||||
type SerializeStruct = Impossible<DateTime<Utc>, Error>;
|
type SerializeStruct = Impossible<Datetime, Error>;
|
||||||
type SerializeStructVariant = Impossible<DateTime<Utc>, Error>;
|
type SerializeStructVariant = Impossible<Datetime, Error>;
|
||||||
|
|
||||||
const EXPECTED: &'static str = "a struct `DateTime<Utc>`";
|
const EXPECTED: &'static str = "a struct `Datetime`";
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn collect_str<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||||
where
|
debug_assert_eq!(len, 2);
|
||||||
T: Display,
|
Ok(SerializeDatetime::default())
|
||||||
{
|
|
||||||
value.to_string().parse().map_err(Error::custom)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn serialize_newtype_struct<T>(
|
fn serialize_newtype_struct<T>(
|
||||||
self,
|
self,
|
||||||
_name: &'static str,
|
name: &'static str,
|
||||||
value: &T,
|
value: &T,
|
||||||
) -> Result<Self::Ok, Self::Error>
|
) -> Result<Self::Ok, Self::Error>
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
|
debug_assert_eq!(name, crate::sql::datetime::TOKEN);
|
||||||
value.serialize(self.wrap())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ser::Serializer as _;
|
use crate::sql::Datetime;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn now() {
|
fn now() {
|
||||||
let dt = Utc::now();
|
let dt = Datetime::from(Utc::now());
|
||||||
let serialized = dt.serialize(Serializer.wrap()).unwrap();
|
let serialized = dt.serialize(Serializer.wrap()).unwrap();
|
||||||
assert_eq!(dt, serialized);
|
assert_eq!(dt, serialized);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ use crate::sql::value::serde::ser;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use crate::sql::Block;
|
use crate::sql::Block;
|
||||||
use crate::sql::Bytes;
|
use crate::sql::Bytes;
|
||||||
use crate::sql::Datetime;
|
|
||||||
use crate::sql::Duration;
|
use crate::sql::Duration;
|
||||||
use crate::sql::Future;
|
use crate::sql::Future;
|
||||||
use crate::sql::Ident;
|
use crate::sql::Ident;
|
||||||
|
@ -227,7 +226,7 @@ impl ser::Serializer for Serializer {
|
||||||
Ok(Value::Uuid(Uuid(value.serialize(ser::uuid::Serializer.wrap())?)))
|
Ok(Value::Uuid(Uuid(value.serialize(ser::uuid::Serializer.wrap())?)))
|
||||||
}
|
}
|
||||||
sql::datetime::TOKEN => {
|
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()),
|
_ => value.serialize(self.wrap()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::value::serde::ser;
|
use crate::sql::value::serde::ser;
|
||||||
use crate::sql::Datetime;
|
|
||||||
use crate::sql::Version;
|
use crate::sql::Version;
|
||||||
use serde::ser::Impossible;
|
use serde::ser::Impossible;
|
||||||
use serde::ser::Serialize;
|
use serde::ser::Serialize;
|
||||||
|
@ -31,7 +30,20 @@ impl ser::Serializer for Serializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
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