Reduce byte size of datetime and uuid types using cbor format ()

Co-authored-by: David Bottiau <B516QT@login.axa>
Co-authored-by: Micha de Vries <micha@devrie.sh>
This commit is contained in:
David Bottiau 2024-04-03 16:22:21 +02:00 committed by GitHub
parent 32a7a9bce4
commit c7c93108c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 18 deletions
core/src/sql
src/rpc/format/cbor

View file

@ -1,7 +1,7 @@
use crate::sql::duration::Duration;
use crate::sql::strand::Strand;
use crate::syn;
use chrono::{DateTime, SecondsFormat, Utc};
use chrono::{offset::LocalResult, DateTime, SecondsFormat, TimeZone, Utc};
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
@ -69,6 +69,16 @@ impl TryFrom<&str> for Datetime {
}
}
impl TryFrom<(i64, u32)> for Datetime {
type Error = ();
fn try_from(v: (i64, u32)) -> Result<Self, Self::Error> {
match Utc.timestamp_opt(v.0, v.1) {
LocalResult::Single(v) => Ok(Self(v)),
_ => Err(()),
}
}
}
impl Deref for Datetime {
type Target = DateTime<Utc>;
fn deref(&self) -> &Self::Target {

View file

@ -77,6 +77,10 @@ impl Deref for Duration {
}
impl Duration {
/// Create a duration from both seconds and nanoseconds components
pub fn new(secs: u64, nanos: u32) -> Duration {
time::Duration::new(secs, nanos).into()
}
/// Convert the Duration to a raw String
pub fn to_raw(&self) -> String {
self.to_string()

View file

@ -12,13 +12,22 @@ use surrealdb::sql::Thing;
use surrealdb::sql::Uuid;
use surrealdb::sql::Value;
const TAG_DATETIME: u64 = 0;
// Tags from the spec - https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
const TAG_SPEC_DATETIME: u64 = 0;
const TAG_SPEC_UUID: u64 = 37;
// Custom tags
const TAG_NONE: u64 = 6;
const TAG_UUID: u64 = 7;
const TAG_DECIMAL: u64 = 8;
const TAG_DURATION: u64 = 9;
const TAG_RECORDID: u64 = 10;
const TAG_TABLE: u64 = 11;
const TAG_TABLE: u64 = 7;
const TAG_RECORDID: u64 = 8;
const TAG_STRING_UUID: u64 = 9;
const TAG_STRING_DECIMAL: u64 = 10;
// const TAG_BINARY_DECIMAL: u64 = 11;
const TAG_CUSTOM_DATETIME: u64 = 12;
const TAG_STRING_DURATION: u64 = 13;
const TAG_CUSTOM_DURATION: u64 = 14;
// Custom Geometries
const TAG_GEOMETRY_POINT: u64 = 88;
const TAG_GEOMETRY_LINE: u64 = 89;
const TAG_GEOMETRY_POLYGON: u64 = 90;
@ -54,25 +63,61 @@ impl TryFrom<Cbor> for Value {
Data::Tag(t, v) => {
match t {
// A literal datetime
TAG_DATETIME => match *v {
TAG_SPEC_DATETIME => match *v {
Data::Text(v) => match Datetime::try_from(v) {
Ok(v) => Ok(v.into()),
_ => Err("Expected a valid Datetime value"),
},
_ => Err("Expected a CBOR text data type"),
},
// A custom [seconds: i64, nanos: u32] datetime
TAG_CUSTOM_DATETIME => match *v {
Data::Array(v) if v.len() == 2 => {
let mut iter = v.into_iter();
let seconds = match iter.next() {
Some(Data::Integer(v)) => match i64::try_from(v) {
Ok(v) => v,
_ => return Err("Expected a CBOR integer data type"),
},
_ => return Err("Expected a CBOR integer data type"),
};
let nanos = match iter.next() {
Some(Data::Integer(v)) => match u32::try_from(v) {
Ok(v) => v,
_ => return Err("Expected a CBOR integer data type"),
},
_ => return Err("Expected a CBOR integer data type"),
};
match Datetime::try_from((seconds, nanos)) {
Ok(v) => Ok(v.into()),
_ => Err("Expected a valid Datetime value"),
}
}
_ => Err("Expected a CBOR array with 2 elements"),
},
// A literal NONE
TAG_NONE => Ok(Value::None),
// A literal uuid
TAG_UUID => match *v {
TAG_STRING_UUID => match *v {
Data::Text(v) => match Uuid::try_from(v) {
Ok(v) => Ok(v.into()),
_ => Err("Expected a valid UUID value"),
},
_ => Err("Expected a CBOR text data type"),
},
// A byte string uuid
TAG_SPEC_UUID => match *v {
Data::Bytes(v) if v.len() == 16 => match v.as_slice().try_into() {
Ok(v) => Ok(Value::Uuid(Uuid::from(uuid::Uuid::from_bytes(v)))),
Err(_) => Err("Expected a CBOR byte array with 16 elements"),
},
_ => Err("Expected a CBOR byte array with 16 elements"),
},
// A literal decimal
TAG_DECIMAL => match *v {
TAG_STRING_DECIMAL => match *v {
Data::Text(v) => match Number::try_from(v) {
Ok(v) => Ok(v.into()),
_ => Err("Expected a valid Decimal value"),
@ -80,13 +125,38 @@ impl TryFrom<Cbor> for Value {
_ => Err("Expected a CBOR text data type"),
},
// A literal duration
TAG_DURATION => match *v {
TAG_STRING_DURATION => match *v {
Data::Text(v) => match Duration::try_from(v) {
Ok(v) => Ok(v.into()),
_ => Err("Expected a valid Duration value"),
},
_ => Err("Expected a CBOR text data type"),
},
// A custom [seconds: Option<u64>, nanos: Option<u32>] duration
TAG_CUSTOM_DURATION => match *v {
Data::Array(v) if v.len() <= 2 => {
let mut iter = v.into_iter();
let seconds = match iter.next() {
Some(Data::Integer(v)) => match u64::try_from(v) {
Ok(v) => v,
_ => return Err("Expected a CBOR integer data type"),
},
_ => 0,
};
let nanos = match iter.next() {
Some(Data::Integer(v)) => match u32::try_from(v) {
Ok(v) => v,
_ => return Err("Expected a CBOR integer data type"),
},
_ => 0,
};
Ok(Duration::new(seconds, nanos).into())
}
_ => Err("Expected a CBOR array with at most 2 elements"),
},
// A literal recordid
TAG_RECORDID => match *v {
Data::Text(v) => match Thing::try_from(v) {
@ -203,7 +273,7 @@ impl TryFrom<Cbor> for Value {
Ok(Value::Geometry(Geometry::MultiLine(MultiLineString::new(lines))))
}
_ => Err("Expected a CBOR array with Geometry Point values"),
_ => Err("Expected a CBOR array with Geometry Line values"),
},
TAG_GEOMETRY_MULTIPOLYGON => match v.deref() {
Data::Array(v) => {
@ -255,18 +325,41 @@ impl TryFrom<Value> for Cbor {
Number::Int(v) => Ok(Cbor(Data::Integer(v.into()))),
Number::Float(v) => Ok(Cbor(Data::Float(v))),
Number::Decimal(v) => {
Ok(Cbor(Data::Tag(TAG_DECIMAL, Box::new(Data::Text(v.to_string())))))
Ok(Cbor(Data::Tag(TAG_STRING_DECIMAL, Box::new(Data::Text(v.to_string())))))
}
_ => unreachable!(),
},
Value::Strand(v) => Ok(Cbor(Data::Text(v.0))),
Value::Duration(v) => {
Ok(Cbor(Data::Tag(TAG_DURATION, Box::new(Data::Text(v.to_raw())))))
let seconds = v.secs();
let nanos = v.subsec_nanos();
let tag_value = match (seconds, nanos) {
(0, 0) => Box::new(Data::Array(vec![])),
(_, 0) => Box::new(Data::Array(vec![Data::Integer(seconds.into())])),
_ => Box::new(Data::Array(vec![
Data::Integer(seconds.into()),
Data::Integer(nanos.into()),
])),
};
Ok(Cbor(Data::Tag(TAG_CUSTOM_DURATION, tag_value)))
}
Value::Datetime(v) => {
Ok(Cbor(Data::Tag(TAG_DATETIME, Box::new(Data::Text(v.to_raw())))))
let seconds = v.timestamp();
let nanos = v.timestamp_subsec_nanos();
Ok(Cbor(Data::Tag(
TAG_CUSTOM_DATETIME,
Box::new(Data::Array(vec![
Data::Integer(seconds.into()),
Data::Integer(nanos.into()),
])),
)))
}
Value::Uuid(v) => {
Ok(Cbor(Data::Tag(TAG_SPEC_UUID, Box::new(Data::Bytes(v.into_bytes().into())))))
}
Value::Uuid(v) => Ok(Cbor(Data::Tag(TAG_UUID, Box::new(Data::Text(v.to_raw()))))),
Value::Array(v) => Ok(Cbor(Data::Array(
v.into_iter()
.map(|v| {
@ -314,8 +407,8 @@ fn encode_geometry(v: Geometry) -> Data {
Geometry::Point(v) => Data::Tag(
TAG_GEOMETRY_POINT,
Box::new(Data::Array(vec![
Data::Tag(TAG_DECIMAL, Box::new(Data::Text(v.x().to_string()))),
Data::Tag(TAG_DECIMAL, Box::new(Data::Text(v.y().to_string()))),
Data::Tag(TAG_STRING_DECIMAL, Box::new(Data::Text(v.x().to_string()))),
Data::Tag(TAG_STRING_DECIMAL, Box::new(Data::Text(v.y().to_string()))),
])),
),
Geometry::Line(v) => {