feat: allow cbor uuid serialization for record id part (#4491)

Co-authored-by: Micha de Vries <micha@devrie.sh>
Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
Co-authored-by: Mees Delzenne <DelSkayn@users.noreply.github.com>
This commit is contained in:
David Bottiau 2024-08-22 10:35:54 +02:00 committed by GitHub
parent f795434345
commit c7bcd4f3a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 39 additions and 15 deletions

View file

@ -125,7 +125,7 @@ impl<'a> KnnConditionRewriter<'a> {
fn eval_id(&self, id: &Id) -> Option<Id> { fn eval_id(&self, id: &Id) -> Option<Id> {
match id { 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::Array(a) => self.eval_array(a).map(Id::Array),
Id::Object(o) => self.eval_object(o).map(Id::Object), 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))), Id::Range(r) => self.eval_id_range(r).map(|v| Id::Range(Box::new(v))),

View file

@ -86,7 +86,7 @@ mod tests {
let id1 = thing.id; let id1 = thing.id;
let val = Thing::new("testns", "testdb", "testtb", id1); let val = Thing::new("testns", "testdb", "testtb", id1);
let enc = Thing::encode(&val).unwrap(); 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(); let dec = Thing::decode(&enc).unwrap();
assert_eq!(val, dec); assert_eq!(val, dec);
@ -96,7 +96,7 @@ mod tests {
let id2 = thing.id; let id2 = thing.id;
let val = Thing::new("testns", "testdb", "testtb", id2); let val = Thing::new("testns", "testdb", "testtb", id2);
let enc = Thing::encode(&val).unwrap(); 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(); let dec = Thing::decode(&enc).unwrap();
assert_eq!(val, dec); assert_eq!(val, dec);

View file

@ -116,13 +116,7 @@ impl TryFrom<Cbor> for Value {
_ => Err("Expected a CBOR text data type"), _ => Err("Expected a CBOR text data type"),
}, },
// A byte string uuid // A byte string uuid
TAG_SPEC_UUID => match *v { TAG_SPEC_UUID => v.deref().to_owned().try_into().map(Value::Uuid),
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 // A literal decimal
TAG_STRING_DECIMAL => match *v { TAG_STRING_DECIMAL => match *v {
Data::Text(v) => match Decimal::from_str(v.as_str()) { Data::Text(v) => match Decimal::from_str(v.as_str()) {
@ -393,6 +387,7 @@ impl TryFrom<Value> for Cbor {
match v.id { match v.id {
Id::Number(v) => Data::Integer(v.into()), Id::Number(v) => Data::Integer(v.into()),
Id::String(v) => Data::Text(v), 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::Array(v) => Cbor::try_from(Value::from(v))?.0,
Id::Object(v) => Cbor::try_from(Value::from(v))?.0, Id::Object(v) => Cbor::try_from(Value::from(v))?.0,
Id::Generate(_) => { Id::Generate(_) => {
@ -564,6 +559,8 @@ impl TryFrom<Data> for Id {
Data::Array(v) => Ok(Id::Array(v.try_into()?)), Data::Array(v) => Ok(Id::Array(v.try_into()?)),
Data::Map(v) => Ok(Id::Object(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_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"), _ => Err("Expected a CBOR integer, text, array or map"),
} }
} }
@ -578,6 +575,9 @@ impl TryFrom<Id> for Data {
Id::Array(v) => Ok(Cbor::try_from(Value::from(v))?.0), Id::Array(v) => Ok(Cbor::try_from(Value::from(v))?.0),
Id::Object(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::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"), Id::Generate(_) => Err("Cannot encode an ungenerated Record ID into CBOR"),
} }
} }
@ -604,3 +604,16 @@ impl TryFrom<Vec<(Data, Data)>> for Object {
)) ))
} }
} }
impl TryFrom<Data> for Uuid {
type Error = &'static str;
fn try_from(val: Data) -> Result<Self, &'static str> {
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"),
}
}
}

View file

@ -35,6 +35,7 @@ pub enum Gen {
pub enum Id { pub enum Id {
Number(i64), Number(i64),
String(String), String(String),
Uuid(Uuid),
Array(Array), Array(Array),
Object(Object), Object(Object),
Generate(Gen), Generate(Gen),
@ -79,7 +80,7 @@ impl From<Object> for Id {
impl From<Uuid> for Id { impl From<Uuid> for Id {
fn from(v: Uuid) -> Self { fn from(v: Uuid) -> Self {
Self::String(v.to_raw()) Self::Uuid(v)
} }
} }
@ -188,13 +189,14 @@ impl Id {
} }
/// Generate a new random UUID /// Generate a new random UUID
pub fn uuid() -> Self { pub fn uuid() -> Self {
Self::String(Uuid::new_v7().to_raw()) Self::Uuid(Uuid::new_v7())
} }
/// Convert the Id to a raw String /// Convert the Id to a raw String
pub fn to_raw(&self) -> String { pub fn to_raw(&self) -> String {
match self { match self {
Self::Number(v) => v.to_string(), Self::Number(v) => v.to_string(),
Self::String(v) => v.to_string(), Self::String(v) => v.to_string(),
Self::Uuid(v) => v.to_string(),
Self::Array(v) => v.to_string(), Self::Array(v) => v.to_string(),
Self::Object(v) => v.to_string(), Self::Object(v) => v.to_string(),
Self::Generate(v) => match v { Self::Generate(v) => match v {
@ -212,6 +214,7 @@ impl Display for Id {
match self { match self {
Self::Number(v) => Display::fmt(v, f), Self::Number(v) => Display::fmt(v, f),
Self::String(v) => Display::fmt(&escape_rid(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::Array(v) => Display::fmt(v, f),
Self::Object(v) => Display::fmt(v, f), Self::Object(v) => Display::fmt(v, f),
Self::Generate(v) => match v { Self::Generate(v) => match v {
@ -236,6 +239,7 @@ impl Id {
match self { match self {
Id::Number(v) => Ok(Id::Number(*v)), Id::Number(v) => Ok(Id::Number(*v)),
Id::String(v) => Ok(Id::String(v.clone())), 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? { Id::Array(v) => match v.compute(stk, ctx, opt, doc).await? {
Value::Array(v) => Ok(Id::Array(v)), Value::Array(v) => Ok(Id::Array(v)),
_ => unreachable!(), _ => unreachable!(),

View file

@ -11,7 +11,9 @@ use super::Datetime;
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Uuid"; pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Uuid";
#[revisioned(revision = 1)] #[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")] #[serde(rename = "$surrealdb::private::sql::Uuid")]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive] #[non_exhaustive]

View file

@ -600,6 +600,7 @@ impl From<Id> for Value {
match v { match v {
Id::Number(v) => v.into(), Id::Number(v) => v.into(),
Id::String(v) => v.into(), Id::String(v) => v.into(),
Id::Uuid(v) => v.into(),
Id::Array(v) => v.into(), Id::Array(v) => v.into(),
Id::Object(v) => v.into(), Id::Object(v) => v.into(),
Id::Generate(v) => match v { Id::Generate(v) => match v {

View file

@ -32,7 +32,10 @@ impl Parser<'_> {
fn kind_cast_start_id(kind: TokenKind) -> bool { fn kind_cast_start_id(kind: TokenKind) -> bool {
Self::tokenkind_can_start_ident(kind) 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( pub async fn parse_thing_or_range(
@ -187,6 +190,7 @@ impl Parser<'_> {
pub async fn parse_id(&mut self, stk: &mut Stk) -> ParseResult<Id> { pub async fn parse_id(&mut self, stk: &mut Stk) -> ParseResult<Id> {
let token = self.peek_whitespace(); let token = self.peek_whitespace();
match token.kind { match token.kind {
t!("u'") | t!("u\"") => Ok(Id::Uuid(self.next_token_value()?)),
t!("{") => { t!("{") => {
self.pop_peek(); self.pop_peek();
// object record id // object record id

View file

@ -94,7 +94,7 @@ async fn create_with_id() -> Result<(), Error> {
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
id: city:8e60244d-95f6-4f95-9e30-09a98977efb0, id: city:u'8e60244d-95f6-4f95-9e30-09a98977efb0',
name: 'London' name: 'London'
} }
]", ]",