From c7bcd4f3a6a6363586c00e92eadd334358c57d40 Mon Sep 17 00:00:00 2001 From: David Bottiau Date: Thu, 22 Aug 2024 10:35:54 +0200 Subject: [PATCH] feat: allow cbor uuid serialization for record id part (#4491) Co-authored-by: Micha de Vries Co-authored-by: Tobie Morgan Hitchcock Co-authored-by: Mees Delzenne --- core/src/idx/planner/rewriter.rs | 2 +- core/src/key/thing/mod.rs | 4 ++-- core/src/rpc/format/cbor/convert.rs | 27 ++++++++++++++++++++------- core/src/sql/id/mod.rs | 8 ++++++-- core/src/sql/uuid.rs | 4 +++- core/src/sql/value/value.rs | 1 + core/src/syn/parser/thing.rs | 6 +++++- lib/tests/create.rs | 2 +- 8 files changed, 39 insertions(+), 15 deletions(-) diff --git a/core/src/idx/planner/rewriter.rs b/core/src/idx/planner/rewriter.rs index 0c812743..639d4d5f 100644 --- a/core/src/idx/planner/rewriter.rs +++ b/core/src/idx/planner/rewriter.rs @@ -125,7 +125,7 @@ impl<'a> KnnConditionRewriter<'a> { fn eval_id(&self, id: &Id) -> Option { match id { - Id::Number(_) | Id::String(_) | Id::Generate(_) => Some(id.clone()), + Id::Number(_) | Id::String(_) | Id::Generate(_) | Id::Uuid(_) => Some(id.clone()), Id::Array(a) => self.eval_array(a).map(Id::Array), Id::Object(o) => self.eval_object(o).map(Id::Object), Id::Range(r) => self.eval_id_range(r).map(|v| Id::Range(Box::new(v))), diff --git a/core/src/key/thing/mod.rs b/core/src/key/thing/mod.rs index d7145a6c..3559f23c 100644 --- a/core/src/key/thing/mod.rs +++ b/core/src/key/thing/mod.rs @@ -86,7 +86,7 @@ mod tests { let id1 = thing.id; let val = Thing::new("testns", "testdb", "testtb", id1); let enc = Thing::encode(&val).unwrap(); - assert_eq!(enc, b"/*testns\0*testdb\0*testtb\0*\0\0\0\x02\0\0\0\x04test\0\x01"); + assert_eq!(enc, b"/*testns\0*testdb\0*testtb\0*\0\0\0\x03\0\0\0\x04test\0\x01"); let dec = Thing::decode(&enc).unwrap(); assert_eq!(val, dec); @@ -96,7 +96,7 @@ mod tests { let id2 = thing.id; let val = Thing::new("testns", "testdb", "testtb", id2); let enc = Thing::encode(&val).unwrap(); - assert_eq!(enc, b"/*testns\0*testdb\0*testtb\0*\0\0\0\x02\0\0\0\x07\0\0\0\0\0\0\0\x10\xf8\xe2\x38\xf2\xe7\x34\x47\xb8\x9a\x16\x47\x6b\x29\x1b\xd7\x8a\x01"); + assert_eq!(enc, b"/*testns\0*testdb\0*testtb\0*\0\0\0\x03\0\0\0\x07\0\0\0\0\0\0\0\x10\xf8\xe2\x38\xf2\xe7\x34\x47\xb8\x9a\x16\x47\x6b\x29\x1b\xd7\x8a\x01"); let dec = Thing::decode(&enc).unwrap(); assert_eq!(val, dec); diff --git a/core/src/rpc/format/cbor/convert.rs b/core/src/rpc/format/cbor/convert.rs index 615850dd..5b1af9c9 100644 --- a/core/src/rpc/format/cbor/convert.rs +++ b/core/src/rpc/format/cbor/convert.rs @@ -116,13 +116,7 @@ impl TryFrom for 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"), - }, + TAG_SPEC_UUID => v.deref().to_owned().try_into().map(Value::Uuid), // A literal decimal TAG_STRING_DECIMAL => match *v { Data::Text(v) => match Decimal::from_str(v.as_str()) { @@ -393,6 +387,7 @@ impl TryFrom for Cbor { match v.id { Id::Number(v) => Data::Integer(v.into()), Id::String(v) => Data::Text(v), + Id::Uuid(v) => Cbor::try_from(Value::from(v))?.0, Id::Array(v) => Cbor::try_from(Value::from(v))?.0, Id::Object(v) => Cbor::try_from(Value::from(v))?.0, Id::Generate(_) => { @@ -564,6 +559,8 @@ impl TryFrom for Id { Data::Array(v) => Ok(Id::Array(v.try_into()?)), Data::Map(v) => Ok(Id::Object(v.try_into()?)), Data::Tag(TAG_RANGE, v) => Ok(Id::Range(Box::new(IdRange::try_from(*v)?))), + Data::Tag(TAG_STRING_UUID, v) => v.deref().to_owned().try_into().map(Id::Uuid), + Data::Tag(TAG_SPEC_UUID, v) => v.deref().to_owned().try_into().map(Id::Uuid), _ => Err("Expected a CBOR integer, text, array or map"), } } @@ -578,6 +575,9 @@ impl TryFrom for Data { Id::Array(v) => Ok(Cbor::try_from(Value::from(v))?.0), Id::Object(v) => Ok(Cbor::try_from(Value::from(v))?.0), Id::Range(v) => Ok(Data::Tag(TAG_RANGE, Box::new(v.deref().to_owned().try_into()?))), + Id::Uuid(v) => { + Ok(Data::Tag(TAG_SPEC_UUID, Box::new(Data::Bytes(v.into_bytes().into())))) + } Id::Generate(_) => Err("Cannot encode an ungenerated Record ID into CBOR"), } } @@ -604,3 +604,16 @@ impl TryFrom> for Object { )) } } + +impl TryFrom for Uuid { + type Error = &'static str; + fn try_from(val: Data) -> Result { + match val { + Data::Bytes(v) if v.len() == 16 => match v.as_slice().try_into() { + Ok(v) => Ok(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"), + } + } +} diff --git a/core/src/sql/id/mod.rs b/core/src/sql/id/mod.rs index 229c3508..ac5e4f8b 100644 --- a/core/src/sql/id/mod.rs +++ b/core/src/sql/id/mod.rs @@ -35,6 +35,7 @@ pub enum Gen { pub enum Id { Number(i64), String(String), + Uuid(Uuid), Array(Array), Object(Object), Generate(Gen), @@ -79,7 +80,7 @@ impl From for Id { impl From for Id { fn from(v: Uuid) -> Self { - Self::String(v.to_raw()) + Self::Uuid(v) } } @@ -188,13 +189,14 @@ impl Id { } /// Generate a new random UUID pub fn uuid() -> Self { - Self::String(Uuid::new_v7().to_raw()) + Self::Uuid(Uuid::new_v7()) } /// Convert the Id to a raw String pub fn to_raw(&self) -> String { match self { Self::Number(v) => v.to_string(), Self::String(v) => v.to_string(), + Self::Uuid(v) => v.to_string(), Self::Array(v) => v.to_string(), Self::Object(v) => v.to_string(), Self::Generate(v) => match v { @@ -212,6 +214,7 @@ impl Display for Id { match self { Self::Number(v) => Display::fmt(v, f), Self::String(v) => Display::fmt(&escape_rid(v), f), + Self::Uuid(v) => Display::fmt(v, f), Self::Array(v) => Display::fmt(v, f), Self::Object(v) => Display::fmt(v, f), Self::Generate(v) => match v { @@ -236,6 +239,7 @@ impl Id { match self { Id::Number(v) => Ok(Id::Number(*v)), Id::String(v) => Ok(Id::String(v.clone())), + Id::Uuid(v) => Ok(Id::Uuid(*v)), Id::Array(v) => match v.compute(stk, ctx, opt, doc).await? { Value::Array(v) => Ok(Id::Array(v)), _ => unreachable!(), diff --git a/core/src/sql/uuid.rs b/core/src/sql/uuid.rs index 8a38f594..bdb8f552 100644 --- a/core/src/sql/uuid.rs +++ b/core/src/sql/uuid.rs @@ -11,7 +11,9 @@ use super::Datetime; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Uuid"; #[revisioned(revision = 1)] -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)] +#[derive( + Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize, Hash, +)] #[serde(rename = "$surrealdb::private::sql::Uuid")] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[non_exhaustive] diff --git a/core/src/sql/value/value.rs b/core/src/sql/value/value.rs index 01b2ef6a..48207443 100644 --- a/core/src/sql/value/value.rs +++ b/core/src/sql/value/value.rs @@ -600,6 +600,7 @@ impl From for Value { match v { Id::Number(v) => v.into(), Id::String(v) => v.into(), + Id::Uuid(v) => v.into(), Id::Array(v) => v.into(), Id::Object(v) => v.into(), Id::Generate(v) => match v { diff --git a/core/src/syn/parser/thing.rs b/core/src/syn/parser/thing.rs index 65b3876e..f9540450 100644 --- a/core/src/syn/parser/thing.rs +++ b/core/src/syn/parser/thing.rs @@ -32,7 +32,10 @@ impl Parser<'_> { fn kind_cast_start_id(kind: TokenKind) -> bool { Self::tokenkind_can_start_ident(kind) - || matches!(kind, TokenKind::Digits | t!("{") | t!("[") | t!("+") | t!("-")) + || matches!( + kind, + TokenKind::Digits | t!("{") | t!("[") | t!("+") | t!("-") | t!("u'") | t!("u\"") + ) } pub async fn parse_thing_or_range( @@ -187,6 +190,7 @@ impl Parser<'_> { pub async fn parse_id(&mut self, stk: &mut Stk) -> ParseResult { let token = self.peek_whitespace(); match token.kind { + t!("u'") | t!("u\"") => Ok(Id::Uuid(self.next_token_value()?)), t!("{") => { self.pop_peek(); // object record id diff --git a/lib/tests/create.rs b/lib/tests/create.rs index 81955a99..f89bd98a 100644 --- a/lib/tests/create.rs +++ b/lib/tests/create.rs @@ -94,7 +94,7 @@ async fn create_with_id() -> Result<(), Error> { let val = Value::parse( "[ { - id: city:⟨8e60244d-95f6-4f95-9e30-09a98977efb0⟩, + id: city:u'8e60244d-95f6-4f95-9e30-09a98977efb0', name: 'London' } ]",