Fix record<T>
casting from string (#4496)
Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
parent
57c7f5ec03
commit
89f1de825a
3 changed files with 83 additions and 21 deletions
|
@ -11,6 +11,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::Table;
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Thing";
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
|
@ -90,6 +92,10 @@ impl Thing {
|
||||||
pub fn to_raw(&self) -> String {
|
pub fn to_raw(&self) -> String {
|
||||||
self.to_string()
|
self.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_record_type(&self, types: &[Table]) -> bool {
|
||||||
|
types.is_empty() || types.iter().any(|tb| tb.0 == self.tb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Thing {
|
impl fmt::Display for Thing {
|
||||||
|
|
|
@ -1028,7 +1028,7 @@ impl Value {
|
||||||
/// Check if this Value is a Thing of a specific type
|
/// Check if this Value is a Thing of a specific type
|
||||||
pub fn is_record_type(&self, types: &[Table]) -> bool {
|
pub fn is_record_type(&self, types: &[Table]) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Value::Thing(v) => types.is_empty() || types.iter().any(|tb| tb.0 == v.tb),
|
Value::Thing(v) => v.is_record_type(types),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1870,7 +1870,7 @@ impl Value {
|
||||||
Value::Bool(v) => Ok(v),
|
Value::Bool(v) => Ok(v),
|
||||||
// Attempt to convert a string value
|
// Attempt to convert a string value
|
||||||
Value::Strand(ref v) => match v.parse::<bool>() {
|
Value::Strand(ref v) => match v.parse::<bool>() {
|
||||||
// The string can be represented as a Float
|
// The string can be parsed as a Float
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
// This string is not a float
|
// This string is not a float
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -1895,7 +1895,7 @@ impl Value {
|
||||||
Value::Number(Number::Float(v)) if v.fract() == 0.0 => Ok(Number::Int(v as i64)),
|
Value::Number(Number::Float(v)) if v.fract() == 0.0 => Ok(Number::Int(v as i64)),
|
||||||
// Attempt to convert a decimal number
|
// Attempt to convert a decimal number
|
||||||
Value::Number(Number::Decimal(v)) if v.is_integer() => match v.try_into() {
|
Value::Number(Number::Decimal(v)) if v.is_integer() => match v.try_into() {
|
||||||
// The Decimal can be represented as an Int
|
// The Decimal can be parsed as an Int
|
||||||
Ok(v) => Ok(Number::Int(v)),
|
Ok(v) => Ok(Number::Int(v)),
|
||||||
// The Decimal is out of bounds
|
// The Decimal is out of bounds
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -1905,7 +1905,7 @@ impl Value {
|
||||||
},
|
},
|
||||||
// Attempt to convert a string value
|
// Attempt to convert a string value
|
||||||
Value::Strand(ref v) => match v.parse::<i64>() {
|
Value::Strand(ref v) => match v.parse::<i64>() {
|
||||||
// The string can be represented as a Float
|
// The string can be parsed as a Float
|
||||||
Ok(v) => Ok(Number::Int(v)),
|
Ok(v) => Ok(Number::Int(v)),
|
||||||
// This string is not a float
|
// This string is not a float
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -1930,7 +1930,7 @@ impl Value {
|
||||||
Value::Number(Number::Int(v)) => Ok(Number::Float(v as f64)),
|
Value::Number(Number::Int(v)) => Ok(Number::Float(v as f64)),
|
||||||
// Attempt to convert a decimal number
|
// Attempt to convert a decimal number
|
||||||
Value::Number(Number::Decimal(v)) => match v.try_into() {
|
Value::Number(Number::Decimal(v)) => match v.try_into() {
|
||||||
// The Decimal can be represented as a Float
|
// The Decimal can be parsed as a Float
|
||||||
Ok(v) => Ok(Number::Float(v)),
|
Ok(v) => Ok(Number::Float(v)),
|
||||||
// The Decimal loses precision
|
// The Decimal loses precision
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -1940,7 +1940,7 @@ impl Value {
|
||||||
},
|
},
|
||||||
// Attempt to convert a string value
|
// Attempt to convert a string value
|
||||||
Value::Strand(ref v) => match v.parse::<f64>() {
|
Value::Strand(ref v) => match v.parse::<f64>() {
|
||||||
// The string can be represented as a Float
|
// The string can be parsed as a Float
|
||||||
Ok(v) => Ok(Number::Float(v)),
|
Ok(v) => Ok(Number::Float(v)),
|
||||||
// This string is not a float
|
// This string is not a float
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -1975,7 +1975,7 @@ impl Value {
|
||||||
},
|
},
|
||||||
// Attempt to convert a string value
|
// Attempt to convert a string value
|
||||||
Value::Strand(ref v) => match Decimal::from_str(v) {
|
Value::Strand(ref v) => match Decimal::from_str(v) {
|
||||||
// The string can be represented as a Decimal
|
// The string can be parsed as a Decimal
|
||||||
Ok(v) => Ok(Number::Decimal(v)),
|
Ok(v) => Ok(Number::Decimal(v)),
|
||||||
// This string is not a Decimal
|
// This string is not a Decimal
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -1998,7 +1998,7 @@ impl Value {
|
||||||
Value::Number(v) => Ok(v),
|
Value::Number(v) => Ok(v),
|
||||||
// Attempt to convert a string value
|
// Attempt to convert a string value
|
||||||
Value::Strand(ref v) => match Number::from_str(v) {
|
Value::Strand(ref v) => match Number::from_str(v) {
|
||||||
// The string can be represented as a Float
|
// The string can be parsed as a number
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
// This string is not a float
|
// This string is not a float
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
|
@ -2073,11 +2073,11 @@ impl Value {
|
||||||
// Uuids are allowed
|
// Uuids are allowed
|
||||||
Value::Uuid(v) => Ok(v),
|
Value::Uuid(v) => Ok(v),
|
||||||
// Attempt to parse a string
|
// Attempt to parse a string
|
||||||
Value::Strand(ref v) => match Uuid::try_from(v.as_str()) {
|
Value::Strand(ref v) => match Uuid::from_str(v) {
|
||||||
// The string can be represented as a uuid
|
// The string can be parsed as a uuid
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
// This string is not a uuid
|
// This string is not a uuid
|
||||||
Err(_) => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
from: self,
|
from: self,
|
||||||
into: "uuid".into(),
|
into: "uuid".into(),
|
||||||
}),
|
}),
|
||||||
|
@ -2109,11 +2109,11 @@ impl Value {
|
||||||
// Datetimes are allowed
|
// Datetimes are allowed
|
||||||
Value::Datetime(v) => Ok(v),
|
Value::Datetime(v) => Ok(v),
|
||||||
// Attempt to parse a string
|
// Attempt to parse a string
|
||||||
Value::Strand(ref v) => match Datetime::try_from(v.as_str()) {
|
Value::Strand(ref v) => match Datetime::from_str(v) {
|
||||||
// The string can be represented as a datetime
|
// The string can be parsed as a datetime
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
// This string is not a datetime
|
// This string is not a datetime
|
||||||
Err(_) => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
from: self,
|
from: self,
|
||||||
into: "datetime".into(),
|
into: "datetime".into(),
|
||||||
}),
|
}),
|
||||||
|
@ -2132,11 +2132,11 @@ impl Value {
|
||||||
// Durations are allowed
|
// Durations are allowed
|
||||||
Value::Duration(v) => Ok(v),
|
Value::Duration(v) => Ok(v),
|
||||||
// Attempt to parse a string
|
// Attempt to parse a string
|
||||||
Value::Strand(ref v) => match Duration::try_from(v.as_str()) {
|
Value::Strand(ref v) => match Duration::from_str(v) {
|
||||||
// The string can be represented as a duration
|
// The string can be parsed as a duration
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
// This string is not a duration
|
// This string is not a duration
|
||||||
Err(_) => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
from: self,
|
from: self,
|
||||||
into: "duration".into(),
|
into: "duration".into(),
|
||||||
}),
|
}),
|
||||||
|
@ -2218,10 +2218,16 @@ impl Value {
|
||||||
match self {
|
match self {
|
||||||
// Records are allowed
|
// Records are allowed
|
||||||
Value::Thing(v) => Ok(v),
|
Value::Thing(v) => Ok(v),
|
||||||
Value::Strand(v) => Thing::try_from(v.as_str()).map_err(move |_| Error::ConvertTo {
|
// Attempt to parse a string
|
||||||
from: Value::Strand(v),
|
Value::Strand(ref v) => match Thing::from_str(v) {
|
||||||
|
// The string can be parsed as a record
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
// This string is not a record
|
||||||
|
_ => Err(Error::ConvertTo {
|
||||||
|
from: self,
|
||||||
into: "record".into(),
|
into: "record".into(),
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
// Anything else raises an error
|
// Anything else raises an error
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
from: self,
|
from: self,
|
||||||
|
@ -2248,6 +2254,16 @@ impl Value {
|
||||||
match self {
|
match self {
|
||||||
// Records are allowed if correct type
|
// Records are allowed if correct type
|
||||||
Value::Thing(v) if self.is_record_type(val) => Ok(v),
|
Value::Thing(v) if self.is_record_type(val) => Ok(v),
|
||||||
|
// Attempt to parse a string
|
||||||
|
Value::Strand(ref v) => match Thing::from_str(v) {
|
||||||
|
// The string can be parsed as a record of this type
|
||||||
|
Ok(v) if v.is_record_type(val) => Ok(v),
|
||||||
|
// This string is not a record of this type
|
||||||
|
_ => Err(Error::ConvertTo {
|
||||||
|
from: self,
|
||||||
|
into: "record".into(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
// Anything else raises an error
|
// Anything else raises an error
|
||||||
_ => Err(Error::ConvertTo {
|
_ => Err(Error::ConvertTo {
|
||||||
from: self,
|
from: self,
|
||||||
|
|
|
@ -22,3 +22,43 @@ async fn cast_string_to_record() -> Result<(), Error> {
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cast_to_record_table() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
<record<a>> a:1;
|
||||||
|
<record<a>> "a:1";
|
||||||
|
<record<b>> a:1;
|
||||||
|
<record<b>> "a:1";
|
||||||
|
"#;
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 4);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("a:1");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("a:1");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
match res.remove(0).result {
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
into,
|
||||||
|
}) if into == "record<b>" && from == Value::parse("a:1") => (),
|
||||||
|
_ => panic!("Casting should have failed with error: Expected a record<b> but cannot convert a:1 into a record<b>"),
|
||||||
|
}
|
||||||
|
//
|
||||||
|
match res.remove(0).result {
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
into,
|
||||||
|
}) if into == "record<b>" && from == Value::parse("'a:1'") => (),
|
||||||
|
_ => panic!("Casting should have failed with error: Expected a record<b> but cannot convert 'a:1' into a record<b>"),
|
||||||
|
}
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue