feat: Implements SELECT/EXPLAIN FULL (#2258)

This commit is contained in:
Emmanuel Keller 2023-07-14 13:22:37 +01:00 committed by GitHub
parent ac213d69bb
commit 1e30eb4aa1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 399 additions and 74 deletions

100
lib/src/dbs/explanation.rs Normal file
View file

@ -0,0 +1,100 @@
use crate::dbs::Iterable;
use crate::sql::{Explain, Object, Value};
use std::collections::HashMap;
#[derive(Default)]
pub(super) struct Explanation(Vec<ExplainItem>);
impl Explanation {
pub(super) fn new(e: Option<&Explain>, iterables: &Vec<Iterable>) -> (bool, Option<Self>) {
match e {
None => (true, None),
Some(e) => {
let mut exp = Self::default();
for i in iterables {
exp.add_iter(i);
}
(e.0, Some(exp))
}
}
}
fn add_iter(&mut self, iter: &Iterable) {
self.0.push(ExplainItem::new_iter(iter));
}
pub(super) fn add_fetch(&mut self, count: usize) {
self.0.push(ExplainItem::new_fetch(count));
}
pub(super) fn output(self, results: &mut Vec<Value>) {
for e in self.0 {
results.push(e.into());
}
}
}
struct ExplainItem {
name: Value,
details: Vec<(&'static str, Value)>,
}
impl ExplainItem {
fn new_fetch(count: usize) -> Self {
Self {
name: "Fetch".into(),
details: vec![("count", count.into())],
}
}
fn new_iter(iter: &Iterable) -> Self {
match iter {
Iterable::Value(v) => Self {
name: "Iterate Value".into(),
details: vec![("value", v.to_owned())],
},
Iterable::Table(t) => Self {
name: "Iterate Table".into(),
details: vec![("table", Value::from(t.0.to_owned()))],
},
Iterable::Thing(t) => Self {
name: "Iterate Thing".into(),
details: vec![("thing", Value::Thing(t.to_owned()))],
},
Iterable::Range(r) => Self {
name: "Iterate Range".into(),
details: vec![("table", Value::from(r.tb.to_owned()))],
},
Iterable::Edges(e) => Self {
name: "Iterate Edges".into(),
details: vec![("from", Value::Thing(e.from.to_owned()))],
},
Iterable::Mergeable(t, v) => Self {
name: "Iterate Mergeable".into(),
details: vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())],
},
Iterable::Relatable(t1, t2, t3) => Self {
name: "Iterate Relatable".into(),
details: vec![
("thing-1", Value::Thing(t1.to_owned())),
("thing-2", Value::Thing(t2.to_owned())),
("thing-3", Value::Thing(t3.to_owned())),
],
},
Iterable::Index(t, p) => Self {
name: "Iterate Index".into(),
details: vec![("table", Value::from(t.0.to_owned())), ("plan", p.explain())],
},
}
}
}
impl From<ExplainItem> for Value {
fn from(i: ExplainItem) -> Self {
let explain = Object::from(HashMap::from([
("operation", i.name),
("detail", Value::Object(Object::from(HashMap::from_iter(i.details)))),
]));
Value::from(explain)
}
}

View file

@ -1,5 +1,6 @@
use crate::ctx::Canceller; use crate::ctx::Canceller;
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::explanation::Explanation;
use crate::dbs::Statement; use crate::dbs::Statement;
use crate::dbs::{Options, Transaction}; use crate::dbs::{Options, Transaction};
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
@ -14,11 +15,10 @@ use crate::sql::range::Range;
use crate::sql::table::Table; use crate::sql::table::Table;
use crate::sql::thing::Thing; use crate::sql::thing::Thing;
use crate::sql::value::Value; use crate::sql::value::Value;
use crate::sql::Object;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap}; use std::collections::BTreeMap;
use std::mem; use std::mem;
pub(crate) enum Iterable { pub(crate) enum Iterable {
@ -88,8 +88,11 @@ impl Iterator {
self.setup_limit(&cancel_ctx, opt, txn, stm).await?; self.setup_limit(&cancel_ctx, opt, txn, stm).await?;
// 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?;
// Process any EXPLAIN clause
if !self.output_explain(&cancel_ctx, opt, txn, stm)? { // 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);
if do_iterate {
// Process prepared values // Process prepared values
self.iterate(&cancel_ctx, opt, txn, stm).await?; self.iterate(&cancel_ctx, opt, txn, stm).await?;
// Return any document errors // Return any document errors
@ -106,9 +109,21 @@ impl Iterator {
self.output_start(ctx, opt, txn, stm).await?; self.output_start(ctx, opt, txn, stm).await?;
// Process any LIMIT clause // Process any LIMIT clause
self.output_limit(ctx, opt, txn, stm).await?; self.output_limit(ctx, opt, txn, stm).await?;
// Process any FETCH clause
self.output_fetch(ctx, opt, txn, stm).await?; if let Some(e) = &mut explanation {
e.add_fetch(self.results.len());
self.results.clear();
} else {
// Process any FETCH clause
self.output_fetch(ctx, opt, txn, stm).await?;
}
} }
// Output the explanation if any
if let Some(e) = explanation {
e.output(&mut self.results);
}
// Output the results // Output the results
Ok(mem::take(&mut self.results).into()) Ok(mem::take(&mut self.results).into())
} }
@ -352,58 +367,6 @@ impl Iterator {
Ok(()) Ok(())
} }
#[inline]
fn output_explain(
&mut self,
_ctx: &Context<'_>,
_opt: &Options,
_txn: &Transaction,
stm: &Statement<'_>,
) -> Result<bool, Error> {
if !stm.explain() {
return Ok(false);
}
for iter in &self.entries {
let (operation, detail) = match iter {
Iterable::Value(v) => ("Iterate Value", vec![("value", v.to_owned())]),
Iterable::Table(t) => {
("Iterate Table", vec![("table", Value::from(t.0.to_owned()))])
}
Iterable::Thing(t) => {
("Iterate Thing", vec![("thing", Value::Thing(t.to_owned()))])
}
Iterable::Range(r) => {
("Iterate Range", vec![("table", Value::from(r.tb.to_owned()))])
}
Iterable::Edges(e) => {
("Iterate Edges", vec![("from", Value::Thing(e.from.to_owned()))])
}
Iterable::Mergeable(t, v) => (
"Iterate Mergeable",
vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())],
),
Iterable::Relatable(t1, t2, t3) => (
"Iterate Relatable",
vec![
("thing-1", Value::Thing(t1.to_owned())),
("thing-2", Value::Thing(t2.to_owned())),
("thing-3", Value::Thing(t3.to_owned())),
],
),
Iterable::Index(t, p) => (
"Iterate Index",
vec![("table", Value::from(t.0.to_owned())), ("plan", p.explain())],
),
};
let explain = Object::from(HashMap::from([
("operation", Value::from(operation)),
("detail", Value::Object(Object::from(HashMap::from_iter(detail)))),
]));
self.results.push(Value::Object(explain));
}
Ok(true)
}
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
#[async_recursion(?Send)] #[async_recursion(?Send)]
async fn iterate( async fn iterate(

View file

@ -4,6 +4,7 @@
//! and executors to process the operations. This module also gives a `context` to the transaction. //! and executors to process the operations. This module also gives a `context` to the transaction.
mod auth; mod auth;
mod executor; mod executor;
mod explanation;
mod iterator; mod iterator;
mod notification; mod notification;
mod options; mod options;

View file

@ -16,6 +16,7 @@ use crate::sql::statements::relate::RelateStatement;
use crate::sql::statements::select::SelectStatement; use crate::sql::statements::select::SelectStatement;
use crate::sql::statements::show::ShowStatement; use crate::sql::statements::show::ShowStatement;
use crate::sql::statements::update::UpdateStatement; use crate::sql::statements::update::UpdateStatement;
use crate::sql::Explain;
use std::fmt; use std::fmt;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -211,10 +212,10 @@ impl<'a> Statement<'a> {
} }
/// Returns any EXPLAIN clause if specified /// Returns any EXPLAIN clause if specified
#[inline] #[inline]
pub fn explain(&self) -> bool { pub fn explain(&self) -> Option<&Explain> {
match self { match self {
Statement::Select(v) => v.explain, Statement::Select(v) => v.explain.as_ref(),
_ => false, _ => None,
} }
} }
} }

52
lib/src/sql/explain.rs Normal file
View file

@ -0,0 +1,52 @@
use crate::sql::comment::shouldbespace;
use crate::sql::error::IResult;
use nom::bytes::complete::tag_no_case;
use nom::combinator::opt;
use nom::sequence::tuple;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
pub struct Explain(pub bool);
impl fmt::Display for Explain {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("EXPLAIN")?;
if self.0 {
f.write_str(" FULL")?;
}
Ok(())
}
}
pub fn explain(i: &str) -> IResult<&str, Explain> {
let (i, _) = tag_no_case("EXPLAIN")(i)?;
let (i, full) = opt(tuple((shouldbespace, tag_no_case("FULL"))))(i)?;
Ok((i, Explain(full.is_some())))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn explain_statement() {
let sql = "EXPLAIN";
let res = explain(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!(out, Explain(false));
assert_eq!("EXPLAIN", format!("{}", out));
}
#[test]
fn explain_full_statement() {
let sql = "EXPLAIN FULL";
let res = explain(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!(out, Explain(true));
assert_eq!("EXPLAIN FULL", format!("{}", out));
}
}

View file

@ -19,6 +19,7 @@ pub(crate) mod edges;
pub(crate) mod ending; pub(crate) mod ending;
pub(crate) mod error; pub(crate) mod error;
pub(crate) mod escape; pub(crate) mod escape;
pub(crate) mod explain;
pub(crate) mod expression; pub(crate) mod expression;
pub(crate) mod fetch; pub(crate) mod fetch;
pub(crate) mod field; pub(crate) mod field;
@ -89,6 +90,7 @@ pub use self::dir::Dir;
pub use self::duration::Duration; pub use self::duration::Duration;
pub use self::edges::Edges; pub use self::edges::Edges;
pub use self::error::Error; pub use self::error::Error;
pub use self::explain::Explain;
pub use self::expression::Expression; pub use self::expression::Expression;
pub use self::fetch::Fetch; pub use self::fetch::Fetch;
pub use self::fetch::Fetchs; pub use self::fetch::Fetchs;

View file

@ -10,6 +10,7 @@ use crate::idx::planner::QueryPlanner;
use crate::sql::comment::shouldbespace; use crate::sql::comment::shouldbespace;
use crate::sql::cond::{cond, Cond}; use crate::sql::cond::{cond, Cond};
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::explain::{explain, Explain};
use crate::sql::fetch::{fetch, Fetchs}; use crate::sql::fetch::{fetch, Fetchs};
use crate::sql::field::{fields, Field, Fields}; use crate::sql::field::{fields, Field, Fields};
use crate::sql::group::{group, Groups}; use crate::sql::group::{group, Groups};
@ -44,7 +45,7 @@ pub struct SelectStatement {
pub version: Option<Version>, pub version: Option<Version>,
pub timeout: Option<Timeout>, pub timeout: Option<Timeout>,
pub parallel: bool, pub parallel: bool,
pub explain: bool, pub explain: Option<Explain>,
} }
impl SelectStatement { impl SelectStatement {
@ -172,6 +173,9 @@ impl fmt::Display for SelectStatement {
if self.parallel { if self.parallel {
f.write_str(" PARALLEL")? f.write_str(" PARALLEL")?
} }
if let Some(ref v) = self.explain {
write!(f, " {v}")?
}
Ok(()) Ok(())
} }
} }
@ -197,7 +201,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> {
let (i, version) = opt(preceded(shouldbespace, version))(i)?; let (i, version) = opt(preceded(shouldbespace, version))(i)?;
let (i, timeout) = opt(preceded(shouldbespace, timeout))(i)?; let (i, timeout) = opt(preceded(shouldbespace, timeout))(i)?;
let (i, parallel) = opt(preceded(shouldbespace, tag_no_case("PARALLEL")))(i)?; let (i, parallel) = opt(preceded(shouldbespace, tag_no_case("PARALLEL")))(i)?;
let (i, explain) = opt(preceded(shouldbespace, tag_no_case("EXPLAIN")))(i)?; let (i, explain) = opt(preceded(shouldbespace, explain))(i)?;
Ok(( Ok((
i, i,
SelectStatement { SelectStatement {
@ -213,7 +217,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> {
version, version,
timeout, timeout,
parallel: parallel.is_some(), parallel: parallel.is_some(),
explain: explain.is_some(), explain,
}, },
)) ))
} }

View file

@ -0,0 +1 @@
pub(super) mod opt;

View file

@ -0,0 +1,74 @@
use crate::err::Error;
use crate::sql::value::serde::ser;
use crate::sql::Explain;
use serde::ser::Impossible;
use serde::ser::Serialize;
pub struct Serializer;
impl ser::Serializer for Serializer {
type Ok = Option<Explain>;
type Error = Error;
type SerializeSeq = Impossible<Option<Explain>, Error>;
type SerializeTuple = Impossible<Option<Explain>, Error>;
type SerializeTupleStruct = Impossible<Option<Explain>, Error>;
type SerializeTupleVariant = Impossible<Option<Explain>, Error>;
type SerializeMap = Impossible<Option<Explain>, Error>;
type SerializeStruct = Impossible<Option<Explain>, Error>;
type SerializeStructVariant = Impossible<Option<Explain>, Error>;
const EXPECTED: &'static str = "an `Option<Explain>`";
#[inline]
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
Ok(None)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
value.serialize(self.wrap())
}
#[inline]
fn serialize_newtype_struct<T>(
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Self::Error>
where
T: ?Sized + Serialize,
{
Ok(Some(Explain(value.serialize(ser::primitive::bool::Serializer.wrap())?)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use ser::Serializer as _;
#[test]
fn none() {
let option: Option<Explain> = None;
let serialized = option.serialize(Serializer.wrap()).unwrap();
assert_eq!(option, serialized);
}
#[test]
fn some_default() {
let option = Some(Explain::default());
let serialized = option.serialize(Serializer.wrap()).unwrap();
assert_eq!(option, serialized);
}
#[test]
fn some_full() {
let option = Some(Explain(true));
let serialized = option.serialize(Serializer.wrap()).unwrap();
assert_eq!(option, serialized);
}
}

View file

@ -8,6 +8,7 @@ mod decimal;
mod dir; mod dir;
mod duration; mod duration;
mod edges; mod edges;
mod explain;
mod expression; mod expression;
mod fetch; mod fetch;
mod field; mod field;

View file

@ -1,4 +1,5 @@
use crate::err::Error; use crate::err::Error;
use crate::sql::explain::Explain;
use crate::sql::statements::SelectStatement; use crate::sql::statements::SelectStatement;
use crate::sql::value::serde::ser; use crate::sql::value::serde::ser;
use crate::sql::Cond; use crate::sql::Cond;
@ -57,7 +58,7 @@ pub struct SerializeSelectStatement {
version: Option<Version>, version: Option<Version>,
timeout: Option<Timeout>, timeout: Option<Timeout>,
parallel: Option<bool>, parallel: Option<bool>,
explain: Option<bool>, explain: Option<Explain>,
} }
impl serde::ser::SerializeStruct for SerializeSelectStatement { impl serde::ser::SerializeStruct for SerializeSelectStatement {
@ -106,7 +107,7 @@ impl serde::ser::SerializeStruct for SerializeSelectStatement {
self.parallel = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?); self.parallel = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
} }
"explain" => { "explain" => {
self.explain = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?); self.explain = value.serialize(ser::explain::opt::Serializer.wrap())?;
} }
key => { key => {
return Err(Error::custom(format!("unexpected field `SelectStatement::{key}`"))); return Err(Error::custom(format!("unexpected field `SelectStatement::{key}`")));
@ -116,12 +117,12 @@ impl serde::ser::SerializeStruct for SerializeSelectStatement {
} }
fn end(self) -> Result<Self::Ok, Error> { fn end(self) -> Result<Self::Ok, Error> {
match (self.expr, self.what, self.parallel, self.explain) { match (self.expr, self.what, self.parallel) {
(Some(expr), Some(what), Some(parallel), Some(explain)) => Ok(SelectStatement { (Some(expr), Some(what), Some(parallel)) => Ok(SelectStatement {
expr, expr,
what, what,
parallel, parallel,
explain, explain: self.explain,
cond: self.cond, cond: self.cond,
split: self.split, split: self.split,
group: self.group, group: self.group,
@ -237,4 +238,14 @@ mod tests {
let value: SelectStatement = stmt.serialize(Serializer.wrap()).unwrap(); let value: SelectStatement = stmt.serialize(Serializer.wrap()).unwrap();
assert_eq!(value, stmt); assert_eq!(value, stmt);
} }
#[test]
fn with_explain() {
let stmt = SelectStatement {
explain: Some(Default::default()),
..Default::default()
};
let value: SelectStatement = stmt.serialize(Serializer.wrap()).unwrap();
assert_eq!(value, stmt);
}
} }

View file

@ -2937,7 +2937,7 @@ mod tests {
assert_eq!(24, std::mem::size_of::<crate::sql::idiom::Idiom>()); assert_eq!(24, std::mem::size_of::<crate::sql::idiom::Idiom>());
assert_eq!(24, std::mem::size_of::<crate::sql::table::Table>()); assert_eq!(24, std::mem::size_of::<crate::sql::table::Table>());
assert_eq!(56, std::mem::size_of::<crate::sql::thing::Thing>()); assert_eq!(56, std::mem::size_of::<crate::sql::thing::Thing>());
assert_eq!(48, std::mem::size_of::<crate::sql::model::Model>()); assert_eq!(40, std::mem::size_of::<crate::sql::model::Model>());
assert_eq!(16, std::mem::size_of::<crate::sql::regex::Regex>()); assert_eq!(16, std::mem::size_of::<crate::sql::regex::Regex>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::range::Range>>()); assert_eq!(8, std::mem::size_of::<Box<crate::sql::range::Range>>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::edges::Edges>>()); assert_eq!(8, std::mem::size_of::<Box<crate::sql::edges::Edges>>());

View file

@ -59,7 +59,7 @@ async fn select_where_matches_without_using_index_iterator() -> Result<(), Error
CREATE blog:2 SET title = 'Foo Bar!'; CREATE blog:2 SET title = 'Foo Bar!';
DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase; DEFINE ANALYZER simple TOKENIZERS blank,class FILTERS lowercase;
DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) HIGHLIGHTS; DEFINE INDEX blog_title ON blog FIELDS title SEARCH ANALYZER simple BM25(1.2,0.75) HIGHLIGHTS;
SELECT id FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99) EXPLAIN; SELECT id FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99) EXPLAIN FULL;
SELECT id,search::highlight('<em>', '</em>', 1) AS title FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99); SELECT id,search::highlight('<em>', '</em>', 1) AS title FROM blog WHERE (title @0@ 'hello' AND identifier > 0) OR (title @1@ 'world' AND identifier < 99);
"; ";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
@ -79,7 +79,13 @@ async fn select_where_matches_without_using_index_iterator() -> Result<(), Error
table: 'blog', table: 'blog',
}, },
operation: 'Iterate Table' operation: 'Iterate Table'
} },
{
detail: {
count: 1,
},
operation: 'Fetch'
},
]", ]",
); );
assert_eq!(tmp, val); assert_eq!(tmp, val);

View file

@ -72,11 +72,12 @@ async fn select_expression_value() -> Result<(), Error> {
CREATE thing:b SET number = -5, boolean = false; CREATE thing:b SET number = -5, boolean = false;
SELECT VALUE -number FROM thing; SELECT VALUE -number FROM thing;
SELECT VALUE !boolean FROM thing; SELECT VALUE !boolean FROM thing;
SELECT VALUE !boolean FROM thing EXPLAIN FULL;
"; ";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(&sql, &ses, None).await?; let res = &mut dbs.execute(&sql, &ses, None).await?;
assert_eq!(res.len(), 4); assert_eq!(res.len(), 5);
// //
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
@ -120,6 +121,25 @@ async fn select_expression_value() -> Result<(), Error> {
); );
assert_eq!(tmp, val); assert_eq!(tmp, val);
// //
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
table: 'thing',
},
operation: 'Iterate Table'
},
{
detail: {
count: 2,
},
operation: 'Fetch'
}
]",
);
assert_eq!(tmp, val);
//
Ok(()) Ok(())
} }
@ -267,11 +287,12 @@ async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
CREATE post:1 SET author = person:tobie; CREATE post:1 SET author = person:tobie;
CREATE post:2 SET author = person:tobie; CREATE post:2 SET author = person:tobie;
SELECT * FROM post WHERE author = person:tobie EXPLAIN; SELECT * FROM post WHERE author = person:tobie EXPLAIN;
SELECT * FROM post WHERE author = person:tobie EXPLAIN FULL;
SELECT * FROM post WHERE author = person:tobie;"; SELECT * FROM post WHERE author = person:tobie;";
let dbs = Datastore::new("memory").await?; let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test"); let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?; let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 6); assert_eq!(res.len(), 7);
// //
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
let _ = res.remove(0).result?; let _ = res.remove(0).result?;
@ -297,6 +318,30 @@ async fn select_where_field_is_thing_and_with_index() -> Result<(), Error> {
assert_eq!(tmp, val); assert_eq!(tmp, val);
// //
let tmp = res.remove(0).result?; let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
plan: {
index: 'author',
operator: '=',
value: person:tobie
},
table: 'post',
},
operation: 'Iterate Index'
},
{
detail: {
count: 2,
},
operation: 'Fetch'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse( let val = Value::parse(
"[ "[
{ {
@ -455,3 +500,67 @@ async fn select_where_and_with_fulltext_index() -> Result<(), Error> {
assert_eq!(tmp, val); assert_eq!(tmp, val);
Ok(()) Ok(())
} }
#[tokio::test]
async fn select_where_explain() -> Result<(), Error> {
let sql = "
CREATE person:tobie SET name = 'Tobie';
CREATE person:jaime SET name = 'Jaime';
CREATE software:surreal SET name = 'SurrealDB';
SELECT * FROM person,software EXPLAIN;
SELECT * FROM person,software EXPLAIN FULL;";
let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None).await?;
assert_eq!(res.len(), 5);
//
let _ = res.remove(0).result?;
let _ = res.remove(0).result?;
let _ = res.remove(0).result?;
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
table: 'person',
},
operation: 'Iterate Table'
},
{
detail: {
table: 'software',
},
operation: 'Iterate Table'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
detail: {
table: 'person',
},
operation: 'Iterate Table'
},
{
detail: {
table: 'software',
},
operation: 'Iterate Table'
},
{
detail: {
count: 3,
},
operation: 'Fetch'
},
]",
);
assert_eq!(tmp, val);
//
Ok(())
}