Add null
as a specific field type (#2524)
This commit is contained in:
parent
a867eae362
commit
e491789e20
3 changed files with 175 additions and 2 deletions
|
@ -19,6 +19,7 @@ use std::fmt::{self, Display, Formatter};
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Any,
|
Any,
|
||||||
|
Null,
|
||||||
Bool,
|
Bool,
|
||||||
Bytes,
|
Bytes,
|
||||||
Datetime,
|
Datetime,
|
||||||
|
@ -62,6 +63,7 @@ impl Display for Kind {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Kind::Any => f.write_str("any"),
|
Kind::Any => f.write_str("any"),
|
||||||
|
Kind::Null => f.write_str("null"),
|
||||||
Kind::Bool => f.write_str("bool"),
|
Kind::Bool => f.write_str("bool"),
|
||||||
Kind::Bytes => f.write_str("bytes"),
|
Kind::Bytes => f.write_str("bytes"),
|
||||||
Kind::Datetime => f.write_str("datetime"),
|
Kind::Datetime => f.write_str("datetime"),
|
||||||
|
@ -109,6 +111,7 @@ pub fn any(i: &str) -> IResult<&str, Kind> {
|
||||||
pub fn simple(i: &str) -> IResult<&str, Kind> {
|
pub fn simple(i: &str) -> IResult<&str, Kind> {
|
||||||
alt((
|
alt((
|
||||||
map(tag("bool"), |_| Kind::Bool),
|
map(tag("bool"), |_| Kind::Bool),
|
||||||
|
map(tag("null"), |_| Kind::Null),
|
||||||
map(tag("bytes"), |_| Kind::Bytes),
|
map(tag("bytes"), |_| Kind::Bytes),
|
||||||
map(tag("datetime"), |_| Kind::Datetime),
|
map(tag("datetime"), |_| Kind::Datetime),
|
||||||
map(tag("decimal"), |_| Kind::Decimal),
|
map(tag("decimal"), |_| Kind::Decimal),
|
||||||
|
@ -266,6 +269,16 @@ mod tests {
|
||||||
assert_eq!(out, Kind::Any);
|
assert_eq!(out, Kind::Any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kind_null() {
|
||||||
|
let sql = "null";
|
||||||
|
let res = kind(sql);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let out = res.unwrap().1;
|
||||||
|
assert_eq!("null", format!("{}", out));
|
||||||
|
assert_eq!(out, Kind::Null);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kind_bool() {
|
fn kind_bool() {
|
||||||
let sql = "bool";
|
let sql = "bool";
|
||||||
|
|
|
@ -1149,6 +1149,7 @@ impl Value {
|
||||||
// Attempt to convert to the desired type
|
// Attempt to convert to the desired type
|
||||||
let res = match kind {
|
let res = match kind {
|
||||||
Kind::Any => Ok(self),
|
Kind::Any => Ok(self),
|
||||||
|
Kind::Null => self.coerce_to_null(),
|
||||||
Kind::Bool => self.coerce_to_bool().map(Value::from),
|
Kind::Bool => self.coerce_to_bool().map(Value::from),
|
||||||
Kind::Int => self.coerce_to_int().map(Value::from),
|
Kind::Int => self.coerce_to_int().map(Value::from),
|
||||||
Kind::Float => self.coerce_to_float().map(Value::from),
|
Kind::Float => self.coerce_to_float().map(Value::from),
|
||||||
|
@ -1179,7 +1180,6 @@ impl Value {
|
||||||
},
|
},
|
||||||
Kind::Option(k) => match self {
|
Kind::Option(k) => match self {
|
||||||
Self::None => Ok(Self::None),
|
Self::None => Ok(Self::None),
|
||||||
Self::Null => Ok(Self::None),
|
|
||||||
v => v.coerce_to(k),
|
v => v.coerce_to(k),
|
||||||
},
|
},
|
||||||
Kind::Either(k) => {
|
Kind::Either(k) => {
|
||||||
|
@ -1292,6 +1292,19 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to coerce this value to a `null`
|
||||||
|
pub(crate) fn coerce_to_null(self) -> Result<Value, Error> {
|
||||||
|
match self {
|
||||||
|
// Allow any null value
|
||||||
|
Value::Null => Ok(Value::Null),
|
||||||
|
// Anything else raises an error
|
||||||
|
_ => Err(Error::CoerceTo {
|
||||||
|
from: self,
|
||||||
|
into: "null".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to coerce this value to a `bool`
|
/// Try to coerce this value to a `bool`
|
||||||
pub(crate) fn coerce_to_bool(self) -> Result<bool, Error> {
|
pub(crate) fn coerce_to_bool(self) -> Result<bool, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -1675,6 +1688,7 @@ impl Value {
|
||||||
// Attempt to convert to the desired type
|
// Attempt to convert to the desired type
|
||||||
let res = match kind {
|
let res = match kind {
|
||||||
Kind::Any => Ok(self),
|
Kind::Any => Ok(self),
|
||||||
|
Kind::Null => self.convert_to_null(),
|
||||||
Kind::Bool => self.convert_to_bool().map(Value::from),
|
Kind::Bool => self.convert_to_bool().map(Value::from),
|
||||||
Kind::Int => self.convert_to_int().map(Value::from),
|
Kind::Int => self.convert_to_int().map(Value::from),
|
||||||
Kind::Float => self.convert_to_float().map(Value::from),
|
Kind::Float => self.convert_to_float().map(Value::from),
|
||||||
|
@ -1705,7 +1719,6 @@ impl Value {
|
||||||
},
|
},
|
||||||
Kind::Option(k) => match self {
|
Kind::Option(k) => match self {
|
||||||
Self::None => Ok(Self::None),
|
Self::None => Ok(Self::None),
|
||||||
Self::Null => Ok(Self::None),
|
|
||||||
v => v.convert_to(k),
|
v => v.convert_to(k),
|
||||||
},
|
},
|
||||||
Kind::Either(k) => {
|
Kind::Either(k) => {
|
||||||
|
@ -1743,6 +1756,19 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to convert this value to a `null`
|
||||||
|
pub(crate) fn convert_to_null(self) -> Result<Value, Error> {
|
||||||
|
match self {
|
||||||
|
// Allow any boolean value
|
||||||
|
Value::Null => Ok(Value::Null),
|
||||||
|
// Anything else raises an error
|
||||||
|
_ => Err(Error::ConvertTo {
|
||||||
|
from: self,
|
||||||
|
into: "null".into(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to convert this value to a `bool`
|
/// Try to convert this value to a `bool`
|
||||||
pub(crate) fn convert_to_bool(self) -> Result<bool, Error> {
|
pub(crate) fn convert_to_bool(self) -> Result<bool, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -185,3 +185,137 @@ async fn strict_typing_defined() -> Result<(), Error> {
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn strict_typing_none_null() -> Result<(), Error> {
|
||||||
|
let sql = "
|
||||||
|
DEFINE TABLE person SCHEMAFULL;
|
||||||
|
DEFINE FIELD name ON TABLE person TYPE option<string>;
|
||||||
|
UPDATE person:test SET name = 'Tobie';
|
||||||
|
UPDATE person:test SET name = NULL;
|
||||||
|
UPDATE person:test SET name = NONE;
|
||||||
|
--
|
||||||
|
DEFINE TABLE person SCHEMAFULL;
|
||||||
|
DEFINE FIELD name ON TABLE person TYPE option<string | null>;
|
||||||
|
UPDATE person:test SET name = 'Tobie';
|
||||||
|
UPDATE person:test SET name = NULL;
|
||||||
|
UPDATE person:test SET name = NONE;
|
||||||
|
--
|
||||||
|
DEFINE TABLE person SCHEMAFULL;
|
||||||
|
DEFINE FIELD name ON TABLE person TYPE string | null;
|
||||||
|
UPDATE person:test SET name = 'Tobie';
|
||||||
|
UPDATE person:test SET name = NULL;
|
||||||
|
UPDATE person:test SET name = NONE;
|
||||||
|
";
|
||||||
|
let dbs = Datastore::new("memory").await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let res = &mut dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 15);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
name: 'Tobie',
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(matches!(
|
||||||
|
tmp.err(),
|
||||||
|
Some(e) if e.to_string() == "Found NULL for field `name`, with record `person:test`, but expected a option<string>"
|
||||||
|
));
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
name: 'Tobie',
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
name: NULL,
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(tmp.is_ok());
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
name: 'Tobie',
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: person:test,
|
||||||
|
name: NULL,
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
);
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result;
|
||||||
|
assert!(matches!(
|
||||||
|
tmp.err(),
|
||||||
|
Some(e) if e.to_string() == "Found NONE for field `name`, with record `person:test`, but expected a string | null"
|
||||||
|
));
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue