Literal types (#4557)
This commit is contained in:
parent
d002fa3958
commit
f2ccedb8c1
4 changed files with 273 additions and 5 deletions
|
@ -1,8 +1,14 @@
|
||||||
|
use super::escape::escape_key;
|
||||||
|
use super::{Duration, Number, Strand};
|
||||||
use crate::sql::statements::info::InfoStructure;
|
use crate::sql::statements::info::InfoStructure;
|
||||||
use crate::sql::{fmt::Fmt, Table, Value};
|
use crate::sql::{
|
||||||
|
fmt::{is_pretty, pretty_indent, Fmt, Pretty},
|
||||||
|
Table, Value,
|
||||||
|
};
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::{self, Display, Formatter, Write};
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
|
@ -31,6 +37,7 @@ pub enum Kind {
|
||||||
Array(Box<Kind>, Option<u64>),
|
Array(Box<Kind>, Option<u64>),
|
||||||
Function(Option<Vec<Kind>>, Option<Box<Kind>>),
|
Function(Option<Vec<Kind>>, Option<Box<Kind>>),
|
||||||
Range,
|
Range,
|
||||||
|
Literal(Literal),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Kind {
|
impl Default for Kind {
|
||||||
|
@ -75,7 +82,8 @@ impl Kind {
|
||||||
| Kind::Record(_)
|
| Kind::Record(_)
|
||||||
| Kind::Geometry(_)
|
| Kind::Geometry(_)
|
||||||
| Kind::Function(_, _)
|
| Kind::Function(_, _)
|
||||||
| Kind::Range => return None,
|
| Kind::Range
|
||||||
|
| Kind::Literal(_) => return None,
|
||||||
Kind::Option(x) => {
|
Kind::Option(x) => {
|
||||||
this = x;
|
this = x;
|
||||||
}
|
}
|
||||||
|
@ -140,6 +148,7 @@ impl Display for Kind {
|
||||||
},
|
},
|
||||||
Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)),
|
Kind::Either(k) => write!(f, "{}", Fmt::verbar_separated(k)),
|
||||||
Kind::Range => f.write_str("range"),
|
Kind::Range => f.write_str("range"),
|
||||||
|
Kind::Literal(l) => write!(f, "{}", l),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,3 +158,137 @@ impl InfoStructure for Kind {
|
||||||
self.to_string().into()
|
self.to_string().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[revisioned(revision = 1)]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Literal {
|
||||||
|
String(Strand),
|
||||||
|
Number(Number),
|
||||||
|
Duration(Duration),
|
||||||
|
Array(Vec<Kind>),
|
||||||
|
Object(BTreeMap<String, Kind>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Literal {
|
||||||
|
pub fn to_kind(&self) -> Kind {
|
||||||
|
match self {
|
||||||
|
Self::String(_) => Kind::String,
|
||||||
|
Self::Number(_) => Kind::Number,
|
||||||
|
Self::Duration(_) => Kind::Duration,
|
||||||
|
Self::Array(a) => {
|
||||||
|
if let Some(inner) = a.first() {
|
||||||
|
if a.iter().all(|x| x == inner) {
|
||||||
|
return Kind::Array(Box::new(inner.to_owned()), Some(a.len() as u64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kind::Array(Box::new(Kind::Any), None)
|
||||||
|
}
|
||||||
|
Self::Object(_) => Kind::Object,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_value(&self, value: &Value) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::String(v) => match value {
|
||||||
|
Value::Strand(s) => s == v,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Self::Number(v) => match value {
|
||||||
|
Value::Number(n) => n == v,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Self::Duration(v) => match value {
|
||||||
|
Value::Duration(n) => n == v,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Self::Array(a) => match value {
|
||||||
|
Value::Array(x) => {
|
||||||
|
if a.len() != x.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, inner) in a.iter().enumerate() {
|
||||||
|
if let Some(value) = x.get(i) {
|
||||||
|
if value.to_owned().coerce_to(inner).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Self::Object(o) => match value {
|
||||||
|
Value::Object(x) => {
|
||||||
|
if o.len() != x.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in o.iter() {
|
||||||
|
if let Some(value) = x.get(k) {
|
||||||
|
if value.to_owned().coerce_to(v).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Literal {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Literal::String(s) => write!(f, "{}", s),
|
||||||
|
Literal::Number(n) => write!(f, "{}", n),
|
||||||
|
Literal::Duration(n) => write!(f, "{}", n),
|
||||||
|
Literal::Array(a) => {
|
||||||
|
let mut f = Pretty::from(f);
|
||||||
|
f.write_char('[')?;
|
||||||
|
if !a.is_empty() {
|
||||||
|
let indent = pretty_indent();
|
||||||
|
write!(f, "{}", Fmt::pretty_comma_separated(a.as_slice()))?;
|
||||||
|
drop(indent);
|
||||||
|
}
|
||||||
|
f.write_char(']')
|
||||||
|
}
|
||||||
|
Literal::Object(o) => {
|
||||||
|
let mut f = Pretty::from(f);
|
||||||
|
if is_pretty() {
|
||||||
|
f.write_char('{')?;
|
||||||
|
} else {
|
||||||
|
f.write_str("{ ")?;
|
||||||
|
}
|
||||||
|
if !o.is_empty() {
|
||||||
|
let indent = pretty_indent();
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
Fmt::pretty_comma_separated(o.iter().map(|args| Fmt::new(
|
||||||
|
args,
|
||||||
|
|(k, v), f| write!(f, "{}: {}", escape_key(k), v)
|
||||||
|
)),)
|
||||||
|
)?;
|
||||||
|
drop(indent);
|
||||||
|
}
|
||||||
|
if is_pretty() {
|
||||||
|
f.write_char('}')
|
||||||
|
} else {
|
||||||
|
f.write_str(" }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::doc::CursorDoc;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::fnc::util::string::fuzzy::Fuzzy;
|
use crate::fnc::util::string::fuzzy::Fuzzy;
|
||||||
use crate::sql::id::range::IdRange;
|
use crate::sql::id::range::IdRange;
|
||||||
|
use crate::sql::kind::Literal;
|
||||||
use crate::sql::statements::info::InfoStructure;
|
use crate::sql::statements::info::InfoStructure;
|
||||||
use crate::sql::Closure;
|
use crate::sql::Closure;
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
|
@ -1337,6 +1338,7 @@ impl Value {
|
||||||
into: kind.to_string(),
|
into: kind.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Kind::Literal(lit) => self.coerce_to_literal(lit),
|
||||||
};
|
};
|
||||||
// Check for any conversion errors
|
// Check for any conversion errors
|
||||||
match res {
|
match res {
|
||||||
|
@ -1431,6 +1433,18 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to coerce this value to a Literal, returns a `Value` with the coerced value
|
||||||
|
pub(crate) fn coerce_to_literal(self, literal: &Literal) -> Result<Value, Error> {
|
||||||
|
if literal.validate_value(&self) {
|
||||||
|
Ok(self)
|
||||||
|
} else {
|
||||||
|
Err(Error::CoerceTo {
|
||||||
|
from: self,
|
||||||
|
into: literal.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to coerce this value to a `null`
|
/// Try to coerce this value to a `null`
|
||||||
pub(crate) fn coerce_to_null(self) -> Result<Value, Error> {
|
pub(crate) fn coerce_to_null(self) -> Result<Value, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -1920,6 +1934,7 @@ impl Value {
|
||||||
into: kind.to_string(),
|
into: kind.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Kind::Literal(lit) => self.convert_to_literal(lit),
|
||||||
};
|
};
|
||||||
// Check for any conversion errors
|
// Check for any conversion errors
|
||||||
match res {
|
match res {
|
||||||
|
@ -1938,6 +1953,18 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to convert this value to a Literal, returns a `Value` with the coerced value
|
||||||
|
pub(crate) fn convert_to_literal(self, literal: &Literal) -> Result<Value, Error> {
|
||||||
|
if literal.validate_value(&self) {
|
||||||
|
Ok(self)
|
||||||
|
} else {
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from: self,
|
||||||
|
into: literal.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Try to convert this value to a `null`
|
/// Try to convert this value to a `null`
|
||||||
pub(crate) fn convert_to_null(self) -> Result<Value, Error> {
|
pub(crate) fn convert_to_null(self) -> Result<Value, Error> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use reblessive::Stk;
|
use reblessive::Stk;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::Kind,
|
sql::{kind::Literal, Kind, Strand},
|
||||||
syn::{
|
syn::{
|
||||||
parser::mac::expected,
|
parser::mac::expected,
|
||||||
token::{t, Keyword, Span, TokenKind},
|
token::{t, Keyword, Span, TokenKind},
|
||||||
|
@ -68,6 +70,11 @@ impl Parser<'_> {
|
||||||
|
|
||||||
/// Parse a single kind which is not any, option, or either.
|
/// Parse a single kind which is not any, option, or either.
|
||||||
async fn parse_concrete_kind(&mut self, ctx: &mut Stk) -> ParseResult<Kind> {
|
async fn parse_concrete_kind(&mut self, ctx: &mut Stk) -> ParseResult<Kind> {
|
||||||
|
if Self::token_can_be_literal_kind(self.peek_kind()) {
|
||||||
|
let literal = self.parse_literal_kind(ctx).await?;
|
||||||
|
return Ok(Kind::Literal(literal));
|
||||||
|
}
|
||||||
|
|
||||||
match self.next().kind {
|
match self.next().kind {
|
||||||
t!("BOOL") => Ok(Kind::Bool),
|
t!("BOOL") => Ok(Kind::Bool),
|
||||||
t!("NULL") => Ok(Kind::Null),
|
t!("NULL") => Ok(Kind::Null),
|
||||||
|
@ -152,6 +159,60 @@ impl Parser<'_> {
|
||||||
x => unexpected!(self, x, "a geometry kind name"),
|
x => unexpected!(self, x, "a geometry kind name"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a literal kind
|
||||||
|
async fn parse_literal_kind(&mut self, ctx: &mut Stk) -> ParseResult<Literal> {
|
||||||
|
match self.peek_kind() {
|
||||||
|
t!("'") | t!("\"") | TokenKind::Strand => {
|
||||||
|
let s = self.next_token_value::<Strand>()?;
|
||||||
|
Ok(Literal::String(s))
|
||||||
|
}
|
||||||
|
t!("+") | t!("-") | TokenKind::Number(_) | TokenKind::Digits | TokenKind::Duration => {
|
||||||
|
let token = self.glue_numeric()?;
|
||||||
|
match token.kind {
|
||||||
|
TokenKind::Number(_) => self.next_token_value().map(Literal::Number),
|
||||||
|
TokenKind::Duration => self.next_token_value().map(Literal::Duration),
|
||||||
|
x => unexpected!(self, x, "a value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t!("{") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let mut obj = BTreeMap::new();
|
||||||
|
while !self.eat(t!("}")) {
|
||||||
|
let key = self.parse_object_key()?;
|
||||||
|
expected!(self, t!(":"));
|
||||||
|
let kind = ctx.run(|ctx| self.parse_inner_kind(ctx)).await?;
|
||||||
|
obj.insert(key, kind);
|
||||||
|
self.eat(t!(","));
|
||||||
|
}
|
||||||
|
Ok(Literal::Object(obj))
|
||||||
|
}
|
||||||
|
t!("[") => {
|
||||||
|
self.pop_peek();
|
||||||
|
let mut arr = Vec::new();
|
||||||
|
while !self.eat(t!("]")) {
|
||||||
|
let kind = ctx.run(|ctx| self.parse_inner_kind(ctx)).await?;
|
||||||
|
arr.push(kind);
|
||||||
|
self.eat(t!(","));
|
||||||
|
}
|
||||||
|
Ok(Literal::Array(arr))
|
||||||
|
}
|
||||||
|
_ => unexpected!(self, self.peek().kind, "a literal kind"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn token_can_be_literal_kind(t: TokenKind) -> bool {
|
||||||
|
matches!(
|
||||||
|
t,
|
||||||
|
t!("'")
|
||||||
|
| t!("\"") | TokenKind::Strand
|
||||||
|
| t!("+") | t!("-")
|
||||||
|
| TokenKind::Number(_)
|
||||||
|
| TokenKind::Digits
|
||||||
|
| TokenKind::Duration
|
||||||
|
| t!("{") | t!("[")
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -94,3 +94,40 @@ async fn cast_range_to_array() -> Result<(), Error> {
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cast_with_literal_kind() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
<"a" | "b"> "a";
|
||||||
|
<123 | 456> 123;
|
||||||
|
<123 | "b"> 123;
|
||||||
|
<[number, "abc"]> [123, "abc"];
|
||||||
|
<{ a: 1d | 2d }> { a: 1d };
|
||||||
|
"#;
|
||||||
|
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(), 5);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("'a'");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("123");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("123");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("[123, 'abc']");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse("{ a: 1d }");
|
||||||
|
assert_eq!(tmp, val);
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue