diff --git a/core/src/idx/planner/rewriter.rs b/core/src/idx/planner/rewriter.rs index 56ec19db..6d3ae807 100644 --- a/core/src/idx/planner/rewriter.rs +++ b/core/src/idx/planner/rewriter.rs @@ -152,7 +152,8 @@ impl<'a> KnnConditionRewriter<'a> { | Part::Last | Part::First | Part::Field(_) - | Part::Index(_) => Some(p.clone()), + | Part::Index(_) + | Part::Optional => Some(p.clone()), Part::Where(v) => self.eval_value(v).map(Part::Where), Part::Graph(_) => None, Part::Value(v) => self.eval_value(v).map(Part::Value), diff --git a/core/src/sql/part.rs b/core/src/sql/part.rs index e46aa9ad..dafb4651 100644 --- a/core/src/sql/part.rs +++ b/core/src/sql/part.rs @@ -25,6 +25,7 @@ pub enum Part { Method(#[serde(with = "no_nul_bytes")] String, Vec), #[revision(start = 2)] Destructure(Vec), + Optional, } impl From for Part { @@ -128,6 +129,7 @@ impl fmt::Display for Part { f.write_str(" }") } } + Part::Optional => write!(f, "?"), } } } diff --git a/core/src/sql/value/get.rs b/core/src/sql/value/get.rs index d445e293..bf34dace 100644 --- a/core/src/sql/value/get.rs +++ b/core/src/sql/value/get.rs @@ -106,29 +106,37 @@ impl Value { stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await } Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Graph(_) => match v.rid() { Some(v) => { let v = Value::Thing(v); stk.run(|stk| v.get(stk, ctx, opt, doc, path)).await } - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Field(f) => match v.get(f.as_str()) { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Index(i) => match v.get(&i.to_string()) { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Value(x) => match stk.run(|stk| x.compute(stk, ctx, opt, doc)).await? { Value::Strand(f) => match v.get(f.as_str()) { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, None => Ok(Value::None), }, - _ => Ok(Value::None), + _ => stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await, }, Part::All => stk.run(|stk| self.get(stk, ctx, opt, doc, path.next())).await, Part::Destructure(p) => { @@ -189,15 +197,21 @@ impl Value { } Part::First => match v.first() { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Last => match v.last() { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Index(i) => match v.get(i.to_usize()) { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, - None => Ok(Value::None), + None => { + stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await + } }, Part::Where(w) => { let mut a = Vec::new(); @@ -219,7 +233,7 @@ impl Value { Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await, None => Ok(Value::None), }, - _ => Ok(Value::None), + _ => stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await, }, Part::Method(name, args) => { let v = stk @@ -345,6 +359,15 @@ impl Value { } v => { match p { + Part::Optional => match &self { + Value::None => Ok(Value::None), + _ => { + stk.run(|stk| { + Value::None.get(stk, ctx, opt, doc, path.next_method()) + }) + .await + } + }, Part::Flatten => { stk.run(|stk| v.get(stk, ctx, opt, None, path.next())).await } diff --git a/core/src/syn/parser/idiom.rs b/core/src/syn/parser/idiom.rs index 8f37cf0c..97682a09 100644 --- a/core/src/syn/parser/idiom.rs +++ b/core/src/syn/parser/idiom.rs @@ -135,6 +135,10 @@ impl Parser<'_> { let mut res = start; loop { match self.peek_kind() { + t!("?") => { + self.pop_peek(); + res.push(Part::Optional); + } t!("...") => { self.pop_peek(); res.push(Part::Flatten); diff --git a/lib/tests/idiom.rs b/lib/tests/idiom.rs new file mode 100644 index 00000000..c6583921 --- /dev/null +++ b/lib/tests/idiom.rs @@ -0,0 +1,14 @@ +mod helpers; +mod parse; +use helpers::Test; +use surrealdb::err::Error; + +#[tokio::test] +async fn idiom_chain_part_optional() -> Result<(), Error> { + let sql = r#" + {}.prop.is_bool(); + {}.prop?.is_bool(); + "#; + Test::new(sql).await?.expect_val("false")?.expect_val("None")?; + Ok(()) +}