Feature: Add fallback reason to the explanation (#2647)
This commit is contained in:
parent
5626dccd21
commit
fe78ca3c32
12 changed files with 175 additions and 71 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Iterable;
|
use crate::dbs::Iterable;
|
||||||
use crate::sql::{Explain, Object, Value};
|
use crate::sql::{Explain, Object, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -6,7 +7,11 @@ use std::collections::HashMap;
|
||||||
pub(super) struct Explanation(Vec<ExplainItem>);
|
pub(super) struct Explanation(Vec<ExplainItem>);
|
||||||
|
|
||||||
impl Explanation {
|
impl Explanation {
|
||||||
pub(super) fn new(e: Option<&Explain>, iterables: &Vec<Iterable>) -> (bool, Option<Self>) {
|
pub(super) fn new(
|
||||||
|
ctx: &Context<'_>,
|
||||||
|
e: Option<&Explain>,
|
||||||
|
iterables: &Vec<Iterable>,
|
||||||
|
) -> (bool, Option<Self>) {
|
||||||
match e {
|
match e {
|
||||||
None => (true, None),
|
None => (true, None),
|
||||||
Some(e) => {
|
Some(e) => {
|
||||||
|
@ -14,6 +19,11 @@ impl Explanation {
|
||||||
for i in iterables {
|
for i in iterables {
|
||||||
exp.add_iter(i);
|
exp.add_iter(i);
|
||||||
}
|
}
|
||||||
|
if let Some(qp) = ctx.get_query_planner() {
|
||||||
|
for reason in qp.fallbacks() {
|
||||||
|
exp.add_fallback(reason.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
(e.0, Some(exp))
|
(e.0, Some(exp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +37,10 @@ impl Explanation {
|
||||||
self.0.push(ExplainItem::new_fetch(count));
|
self.0.push(ExplainItem::new_fetch(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_fallback(&mut self, reason: String) {
|
||||||
|
self.0.push(ExplainItem::new_fallback(reason));
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn output(self, results: &mut Vec<Value>) {
|
pub(super) fn output(self, results: &mut Vec<Value>) {
|
||||||
for e in self.0 {
|
for e in self.0 {
|
||||||
results.push(e.into());
|
results.push(e.into());
|
||||||
|
@ -47,6 +61,13 @@ impl ExplainItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_fallback(reason: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name: "Fallback".into(),
|
||||||
|
details: vec![("reason", reason.into())],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_iter(iter: &Iterable) -> Self {
|
fn new_iter(iter: &Iterable) -> Self {
|
||||||
match iter {
|
match iter {
|
||||||
Iterable::Value(v) => Self {
|
Iterable::Value(v) => Self {
|
||||||
|
|
|
@ -278,7 +278,7 @@ impl Iterator {
|
||||||
// Process the query START clause
|
// Process the query START clause
|
||||||
self.setup_start(&cancel_ctx, opt, txn, stm).await?;
|
self.setup_start(&cancel_ctx, opt, txn, stm).await?;
|
||||||
// Extract the expected behaviour depending on the presence of EXPLAIN with or without FULL
|
// Extract the expected behaviour depending on the presence of EXPLAIN with or without FULL
|
||||||
let (do_iterate, mut explanation) = Explanation::new(stm.explain(), &self.entries);
|
let (do_iterate, mut explanation) = Explanation::new(ctx, stm.explain(), &self.entries);
|
||||||
|
|
||||||
if do_iterate {
|
if do_iterate {
|
||||||
// Process prepared values
|
// Process prepared values
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl<'a> Document<'a> {
|
||||||
Index::Search(p) => ic.index_full_text(&mut run, p).await?,
|
Index::Search(p) => ic.index_full_text(&mut run, p).await?,
|
||||||
Index::MTree(_) => {
|
Index::MTree(_) => {
|
||||||
return Err(Error::FeatureNotYetImplemented {
|
return Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "MTree indexing",
|
feature: "MTree indexing".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -606,7 +606,7 @@ pub enum Error {
|
||||||
/// The feature has not yet being implemented
|
/// The feature has not yet being implemented
|
||||||
#[error("Feature not yet implemented: {feature}")]
|
#[error("Feature not yet implemented: {feature}")]
|
||||||
FeatureNotYetImplemented {
|
FeatureNotYetImplemented {
|
||||||
feature: &'static str,
|
feature: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Duplicated match references are not allowed
|
/// Duplicated match references are not allowed
|
||||||
|
|
|
@ -140,13 +140,13 @@ pub mod distance {
|
||||||
|
|
||||||
pub fn hamming((_, _): (String, String)) -> Result<Value, Error> {
|
pub fn hamming((_, _): (String, String)) -> Result<Value, Error> {
|
||||||
Err(Error::FeatureNotYetImplemented {
|
Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "string::distance::hamming() function",
|
feature: "string::distance::hamming() function".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn levenshtein((_, _): (String, String)) -> Result<Value, Error> {
|
pub fn levenshtein((_, _): (String, String)) -> Result<Value, Error> {
|
||||||
Err(Error::FeatureNotYetImplemented {
|
Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "string::distance::levenshtein() function",
|
feature: "string::distance::levenshtein() function".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ pub mod similarity {
|
||||||
|
|
||||||
pub fn jaro((_, _): (String, String)) -> Result<Value, Error> {
|
pub fn jaro((_, _): (String, String)) -> Result<Value, Error> {
|
||||||
Err(Error::FeatureNotYetImplemented {
|
Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "string::similarity::jaro() function",
|
feature: "string::similarity::jaro() function".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ pub mod distance {
|
||||||
|
|
||||||
pub fn mahalanobis((_, _): (Vec<Number>, Vec<Number>)) -> Result<Value, Error> {
|
pub fn mahalanobis((_, _): (Vec<Number>, Vec<Number>)) -> Result<Value, Error> {
|
||||||
Err(Error::FeatureNotYetImplemented {
|
Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "vector::distance::mahalanobis() function",
|
feature: "vector::distance::mahalanobis() function".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ pub mod similarity {
|
||||||
|
|
||||||
pub fn spearman((_, _): (Vec<Number>, Vec<Number>)) -> Result<Value, Error> {
|
pub fn spearman((_, _): (Vec<Number>, Vec<Number>)) -> Result<Value, Error> {
|
||||||
Err(Error::FeatureNotYetImplemented {
|
Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "vector::similarity::spearman() function",
|
feature: "vector::similarity::spearman() function".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ impl QueryExecutor {
|
||||||
..
|
..
|
||||||
} => self.new_search_index_iterator(ir, io).await,
|
} => self.new_search_index_iterator(ir, io).await,
|
||||||
_ => Err(Error::FeatureNotYetImplemented {
|
_ => Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "VectorSearch iterator",
|
feature: "VectorSearch iterator".to_string(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub(crate) struct QueryPlanner<'a> {
|
||||||
/// There is one executor per table
|
/// There is one executor per table
|
||||||
executors: HashMap<String, QueryExecutor>,
|
executors: HashMap<String, QueryExecutor>,
|
||||||
requires_distinct: bool,
|
requires_distinct: bool,
|
||||||
|
fallbacks: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> QueryPlanner<'a> {
|
impl<'a> QueryPlanner<'a> {
|
||||||
|
@ -30,6 +31,7 @@ impl<'a> QueryPlanner<'a> {
|
||||||
cond,
|
cond,
|
||||||
executors: HashMap::default(),
|
executors: HashMap::default(),
|
||||||
requires_distinct: false,
|
requires_distinct: false,
|
||||||
|
fallbacks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,14 +42,14 @@ impl<'a> QueryPlanner<'a> {
|
||||||
t: Table,
|
t: Table,
|
||||||
it: &mut Iterator,
|
it: &mut Iterator,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let res = Tree::build(ctx, self.opt, txn, &t, self.cond).await?;
|
match Tree::build(ctx, self.opt, txn, &t, self.cond).await? {
|
||||||
if let Some((node, im)) = res {
|
Some((node, im)) => {
|
||||||
let mut exe = QueryExecutor::new(self.opt, txn, &t, im).await?;
|
let mut exe = QueryExecutor::new(self.opt, txn, &t, im).await?;
|
||||||
let ok = match PlanBuilder::build(node, self.with)? {
|
match PlanBuilder::build(node, self.with)? {
|
||||||
Plan::SingleIndex(exp, io) => {
|
Plan::SingleIndex(exp, io) => {
|
||||||
let ir = exe.add_iterator(exp);
|
let ir = exe.add_iterator(exp);
|
||||||
it.ingest(Iterable::Index(t.clone(), ir, io));
|
it.ingest(Iterable::Index(t.clone(), ir, io));
|
||||||
true
|
self.executors.insert(t.0.clone(), exe);
|
||||||
}
|
}
|
||||||
Plan::MultiIndex(v) => {
|
Plan::MultiIndex(v) => {
|
||||||
for (exp, io) in v {
|
for (exp, io) in v {
|
||||||
|
@ -55,16 +57,21 @@ impl<'a> QueryPlanner<'a> {
|
||||||
it.ingest(Iterable::Index(t.clone(), ir, io));
|
it.ingest(Iterable::Index(t.clone(), ir, io));
|
||||||
self.requires_distinct = true;
|
self.requires_distinct = true;
|
||||||
}
|
}
|
||||||
true
|
|
||||||
}
|
|
||||||
Plan::TableIterator => false,
|
|
||||||
};
|
|
||||||
self.executors.insert(t.0.clone(), exe);
|
self.executors.insert(t.0.clone(), exe);
|
||||||
if ok {
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
Plan::TableIterator(fallback) => {
|
||||||
|
if let Some(fallback) = fallback {
|
||||||
|
self.fallbacks.push(fallback);
|
||||||
}
|
}
|
||||||
|
self.executors.insert(t.0.clone(), exe);
|
||||||
it.ingest(Iterable::Table(t));
|
it.ingest(Iterable::Table(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
it.ingest(Iterable::Table(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,4 +86,8 @@ impl<'a> QueryPlanner<'a> {
|
||||||
pub(crate) fn requires_distinct(&self) -> bool {
|
pub(crate) fn requires_distinct(&self) -> bool {
|
||||||
self.requires_distinct
|
self.requires_distinct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fallbacks(&self) -> &Vec<String> {
|
||||||
|
&self.fallbacks
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl<'a> PlanBuilder<'a> {
|
||||||
pub(super) fn build(root: Node, with: &'a Option<With>) -> Result<Plan, Error> {
|
pub(super) fn build(root: Node, with: &'a Option<With>) -> Result<Plan, Error> {
|
||||||
if let Some(with) = with {
|
if let Some(with) = with {
|
||||||
if matches!(with, With::NoIndex) {
|
if matches!(with, With::NoIndex) {
|
||||||
return Ok(Plan::TableIterator);
|
return Ok(Plan::TableIterator(Some("WITH NOINDEX".to_string())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut b = PlanBuilder {
|
let mut b = PlanBuilder {
|
||||||
|
@ -30,12 +30,12 @@ impl<'a> PlanBuilder<'a> {
|
||||||
all_exp_with_index: true,
|
all_exp_with_index: true,
|
||||||
};
|
};
|
||||||
// Browse the AST and collect information
|
// Browse the AST and collect information
|
||||||
if !b.eval_node(root)? {
|
if let Err(e) = b.eval_node(root) {
|
||||||
return Ok(Plan::TableIterator);
|
return Ok(Plan::TableIterator(Some(e.to_string())));
|
||||||
}
|
}
|
||||||
// If we didn't found any index, we're done with no index plan
|
// If we didn't found any index, we're done with no index plan
|
||||||
if b.indexes.is_empty() {
|
if b.indexes.is_empty() {
|
||||||
return Ok(Plan::TableIterator);
|
return Ok(Plan::TableIterator(Some("NO INDEX FOUND".to_string())));
|
||||||
}
|
}
|
||||||
// If every boolean operator are AND then we can use the single index plan
|
// If every boolean operator are AND then we can use the single index plan
|
||||||
if b.all_and {
|
if b.all_and {
|
||||||
|
@ -47,7 +47,7 @@ impl<'a> PlanBuilder<'a> {
|
||||||
if b.all_exp_with_index {
|
if b.all_exp_with_index {
|
||||||
return Ok(Plan::MultiIndex(b.indexes));
|
return Ok(Plan::MultiIndex(b.indexes));
|
||||||
}
|
}
|
||||||
Ok(Plan::TableIterator)
|
Ok(Plan::TableIterator(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have an explicit list of index we can use
|
// Check if we have an explicit list of index we can use
|
||||||
|
@ -62,7 +62,7 @@ impl<'a> PlanBuilder<'a> {
|
||||||
io
|
io
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_node(&mut self, node: Node) -> Result<bool, Error> {
|
fn eval_node(&mut self, node: Node) -> Result<(), String> {
|
||||||
match node {
|
match node {
|
||||||
Node::Expression {
|
Node::Expression {
|
||||||
io,
|
io,
|
||||||
|
@ -79,10 +79,12 @@ impl<'a> PlanBuilder<'a> {
|
||||||
} else if self.all_exp_with_index && !is_bool {
|
} else if self.all_exp_with_index && !is_bool {
|
||||||
self.all_exp_with_index = false;
|
self.all_exp_with_index = false;
|
||||||
}
|
}
|
||||||
self.eval_expression(*left, *right)
|
self.eval_node(*left)?;
|
||||||
|
self.eval_node(*right)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Node::Unsupported => Ok(false),
|
Node::Unsupported(reason) => Err(reason),
|
||||||
_ => Ok(true),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,23 +101,13 @@ impl<'a> PlanBuilder<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_expression(&mut self, left: Node, right: Node) -> Result<bool, Error> {
|
|
||||||
if !self.eval_node(left)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
if !self.eval_node(right)? {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_index_option(&mut self, e: Expression, i: IndexOption) {
|
fn add_index_option(&mut self, e: Expression, i: IndexOption) {
|
||||||
self.indexes.push((e, i));
|
self.indexes.push((e, i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum Plan {
|
pub(super) enum Plan {
|
||||||
TableIterator,
|
TableIterator(Option<String>),
|
||||||
SingleIndex(Expression, IndexOption),
|
SingleIndex(Expression, IndexOption),
|
||||||
MultiIndex(Vec<(Expression, IndexOption)>),
|
MultiIndex(Vec<(Expression, IndexOption)>),
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,20 +71,20 @@ impl<'a> TreeBuilder<'a> {
|
||||||
#[cfg_attr(not(target_arch = "wasm32"), async_recursion)]
|
#[cfg_attr(not(target_arch = "wasm32"), async_recursion)]
|
||||||
#[cfg_attr(target_arch = "wasm32", async_recursion(?Send))]
|
#[cfg_attr(target_arch = "wasm32", async_recursion(?Send))]
|
||||||
async fn eval_value(&mut self, v: &Value) -> Result<Node, Error> {
|
async fn eval_value(&mut self, v: &Value) -> Result<Node, Error> {
|
||||||
Ok(match v {
|
match v {
|
||||||
Value::Expression(e) => self.eval_expression(e).await?,
|
Value::Expression(e) => self.eval_expression(e).await,
|
||||||
Value::Idiom(i) => self.eval_idiom(i).await?,
|
Value::Idiom(i) => self.eval_idiom(i).await,
|
||||||
Value::Strand(_) => Node::Scalar(v.to_owned()),
|
Value::Strand(_) => Ok(Node::Scalar(v.to_owned())),
|
||||||
Value::Number(_) => Node::Scalar(v.to_owned()),
|
Value::Number(_) => Ok(Node::Scalar(v.to_owned())),
|
||||||
Value::Bool(_) => Node::Scalar(v.to_owned()),
|
Value::Bool(_) => Ok(Node::Scalar(v.to_owned())),
|
||||||
Value::Thing(_) => Node::Scalar(v.to_owned()),
|
Value::Thing(_) => Ok(Node::Scalar(v.to_owned())),
|
||||||
Value::Subquery(s) => self.eval_subquery(s).await?,
|
Value::Subquery(s) => self.eval_subquery(s).await,
|
||||||
Value::Param(p) => {
|
Value::Param(p) => {
|
||||||
let v = p.compute(self.ctx, self.opt, self.txn, None).await?;
|
let v = p.compute(self.ctx, self.opt, self.txn, None).await?;
|
||||||
self.eval_value(&v).await?
|
self.eval_value(&v).await
|
||||||
|
}
|
||||||
|
_ => Ok(Node::Unsupported(format!("Unsupported value: {}", v))),
|
||||||
}
|
}
|
||||||
_ => Node::Unsupported,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn eval_idiom(&mut self, i: &Idiom) -> Result<Node, Error> {
|
async fn eval_idiom(&mut self, i: &Idiom) -> Result<Node, Error> {
|
||||||
|
@ -99,9 +99,7 @@ impl<'a> TreeBuilder<'a> {
|
||||||
match e {
|
match e {
|
||||||
Expression::Unary {
|
Expression::Unary {
|
||||||
..
|
..
|
||||||
} => Err(Error::FeatureNotYetImplemented {
|
} => Ok(Node::Unsupported("unary expressions not supported".to_string())),
|
||||||
feature: "unary expressions in index",
|
|
||||||
}),
|
|
||||||
Expression::Binary {
|
Expression::Binary {
|
||||||
l,
|
l,
|
||||||
o,
|
o,
|
||||||
|
@ -173,10 +171,10 @@ impl<'a> TreeBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn eval_subquery(&mut self, s: &Subquery) -> Result<Node, Error> {
|
async fn eval_subquery(&mut self, s: &Subquery) -> Result<Node, Error> {
|
||||||
Ok(match s {
|
match s {
|
||||||
Subquery::Value(v) => self.eval_value(v).await?,
|
Subquery::Value(v) => self.eval_value(v).await,
|
||||||
_ => Node::Unsupported,
|
_ => Ok(Node::Unsupported(format!("Unsupported subquery: {}", s))),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +199,7 @@ pub(super) enum Node {
|
||||||
IndexedField(Idiom, DefineIndexStatement),
|
IndexedField(Idiom, DefineIndexStatement),
|
||||||
NonIndexedField,
|
NonIndexedField,
|
||||||
Scalar(Value),
|
Scalar(Value),
|
||||||
Unsupported,
|
Unsupported(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl AnalyzeStatement {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::FeatureNotYetImplemented {
|
return Err(Error::FeatureNotYetImplemented {
|
||||||
feature: "Statistics on unique and non-unique indexes.",
|
feature: "Statistics on unique and non-unique indexes.".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -110,10 +110,10 @@ async fn select_where_iterate_two_no_index() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&two_multi_index_query("WITH NOINDEX", ""), 9).await?;
|
let mut res = execute_test(&two_multi_index_query("WITH NOINDEX", ""), 9).await?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
check_result(&mut res, &table_explain(2))?;
|
check_result(&mut res, &table_explain_no_index(2))?;
|
||||||
// AND results
|
// AND results
|
||||||
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
check_result(&mut res, &table_explain(1))?;
|
check_result(&mut res, &table_explain_no_index(1))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,6 +185,31 @@ fn table_explain(fetch_count: usize) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn table_explain_no_index(fetch_count: usize) -> String {
|
||||||
|
format!(
|
||||||
|
"[
|
||||||
|
{{
|
||||||
|
detail: {{
|
||||||
|
table: 'person'
|
||||||
|
}},
|
||||||
|
operation: 'Iterate Table'
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
detail: {{
|
||||||
|
reason: 'WITH NOINDEX'
|
||||||
|
}},
|
||||||
|
operation: 'Fallback'
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
detail: {{
|
||||||
|
count: {fetch_count}
|
||||||
|
}},
|
||||||
|
operation: 'Fetch'
|
||||||
|
}}
|
||||||
|
]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const THREE_TABLE_EXPLAIN: &str = "[
|
const THREE_TABLE_EXPLAIN: &str = "[
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
|
@ -332,3 +357,60 @@ const TWO_MULTI_INDEX_EXPLAIN: &str = "[
|
||||||
operation: 'Fetch'
|
operation: 'Fetch'
|
||||||
}
|
}
|
||||||
]";
|
]";
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_with_no_index_unary_operator() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let mut res = dbs
|
||||||
|
.execute("SELECT * FROM table WITH NOINDEX WHERE !param.subparam EXPLAIN", &ses, None)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
r#"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'table'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Table'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
reason: 'WITH NOINDEX'
|
||||||
|
},
|
||||||
|
operation: 'Fallback'
|
||||||
|
}
|
||||||
|
]"#,
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_unsupported_unary_operator() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
|
let mut res =
|
||||||
|
dbs.execute("SELECT * FROM table WHERE !param.subparam EXPLAIN", &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), 1);
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(
|
||||||
|
r#"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'table'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Table'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
reason: 'unary expressions not supported'
|
||||||
|
},
|
||||||
|
operation: 'Fallback'
|
||||||
|
}
|
||||||
|
]"#,
|
||||||
|
);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue