feat: Implements SELECT/EXPLAIN FULL (#2258)
This commit is contained in:
parent
ac213d69bb
commit
1e30eb4aa1
14 changed files with 399 additions and 74 deletions
100
lib/src/dbs/explanation.rs
Normal file
100
lib/src/dbs/explanation.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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?;
|
||||||
|
|
||||||
|
if let Some(e) = &mut explanation {
|
||||||
|
e.add_fetch(self.results.len());
|
||||||
|
self.results.clear();
|
||||||
|
} else {
|
||||||
// Process any FETCH clause
|
// Process any FETCH clause
|
||||||
self.output_fetch(ctx, opt, txn, stm).await?;
|
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(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
52
lib/src/sql/explain.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
1
lib/src/sql/value/serde/ser/explain/mod.rs
Normal file
1
lib/src/sql/value/serde/ser/explain/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub(super) mod opt;
|
74
lib/src/sql/value/serde/ser/explain/opt.rs
Normal file
74
lib/src/sql/value/serde/ser/explain/opt.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue