feat: WITH clause on SELECT statement (#2304)
This commit is contained in:
parent
b66e537f98
commit
0b56d5c6c6
14 changed files with 638 additions and 135 deletions
|
@ -550,7 +550,7 @@ impl<'a> Processor<'a> {
|
||||||
txn.lock().await.check_ns_db_tb(opt.ns(), opt.db(), &table.0, opt.strict).await?;
|
txn.lock().await.check_ns_db_tb(opt.ns(), opt.db(), &table.0, opt.strict).await?;
|
||||||
if let Some(pla) = ctx.get_query_planner() {
|
if let Some(pla) = ctx.get_query_planner() {
|
||||||
if let Some(exe) = pla.get_query_executor(&table.0) {
|
if let Some(exe) = pla.get_query_executor(&table.0) {
|
||||||
let mut iterator = exe.new_iterator(opt, ir, io).await?;
|
if let Some(mut iterator) = exe.new_iterator(opt, ir, io).await? {
|
||||||
let mut things = iterator.next_batch(txn, 1000).await?;
|
let mut things = iterator.next_batch(txn, 1000).await?;
|
||||||
while !things.is_empty() {
|
while !things.is_empty() {
|
||||||
// Check if the context is finished
|
// Check if the context is finished
|
||||||
|
@ -595,6 +595,7 @@ impl<'a> Processor<'a> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(Error::QueryNotExecutedDetail {
|
Err(Error::QueryNotExecutedDetail {
|
||||||
message: "No QueryExecutor has not been found.".to_string(),
|
message: "No QueryExecutor has not been found.".to_string(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -507,10 +507,6 @@ pub enum Error {
|
||||||
feature: &'static str,
|
feature: &'static str,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[error("Bypass the query planner")]
|
|
||||||
BypassQueryPlanner,
|
|
||||||
|
|
||||||
/// Duplicated match references are not allowed
|
/// Duplicated match references are not allowed
|
||||||
#[error("Duplicated Match reference: {mr}")]
|
#[error("Duplicated Match reference: {mr}")]
|
||||||
DuplicatedMatchRef {
|
DuplicatedMatchRef {
|
||||||
|
|
|
@ -121,7 +121,7 @@ impl QueryExecutor {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
ir: IteratorRef,
|
ir: IteratorRef,
|
||||||
io: IndexOption,
|
io: IndexOption,
|
||||||
) -> Result<ThingIterator, Error> {
|
) -> Result<Option<ThingIterator>, Error> {
|
||||||
match &io.ix().index {
|
match &io.ix().index {
|
||||||
Index::Idx => Self::new_index_iterator(opt, io),
|
Index::Idx => Self::new_index_iterator(opt, io),
|
||||||
Index::Uniq => Self::new_unique_index_iterator(opt, io),
|
Index::Uniq => Self::new_unique_index_iterator(opt, io),
|
||||||
|
@ -131,45 +131,48 @@ impl QueryExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_index_iterator(opt: &Options, io: IndexOption) -> Result<ThingIterator, Error> {
|
fn new_index_iterator(opt: &Options, io: IndexOption) -> Result<Option<ThingIterator>, Error> {
|
||||||
if io.op() == &Operator::Equal {
|
if io.op() == &Operator::Equal {
|
||||||
return Ok(ThingIterator::NonUniqueEqual(NonUniqueEqualThingIterator::new(
|
return Ok(Some(ThingIterator::NonUniqueEqual(NonUniqueEqualThingIterator::new(
|
||||||
opt,
|
opt,
|
||||||
io.ix(),
|
io.ix(),
|
||||||
io.value(),
|
io.value(),
|
||||||
)?));
|
)?)));
|
||||||
}
|
}
|
||||||
Err(Error::BypassQueryPlanner)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_unique_index_iterator(opt: &Options, io: IndexOption) -> Result<ThingIterator, Error> {
|
fn new_unique_index_iterator(
|
||||||
|
opt: &Options,
|
||||||
|
io: IndexOption,
|
||||||
|
) -> Result<Option<ThingIterator>, Error> {
|
||||||
if io.op() == &Operator::Equal {
|
if io.op() == &Operator::Equal {
|
||||||
return Ok(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new(
|
return Ok(Some(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new(
|
||||||
opt,
|
opt,
|
||||||
io.ix(),
|
io.ix(),
|
||||||
io.value(),
|
io.value(),
|
||||||
)?));
|
)?)));
|
||||||
}
|
}
|
||||||
Err(Error::BypassQueryPlanner)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_search_index_iterator(
|
async fn new_search_index_iterator(
|
||||||
&self,
|
&self,
|
||||||
ir: IteratorRef,
|
ir: IteratorRef,
|
||||||
io: IndexOption,
|
io: IndexOption,
|
||||||
) -> Result<ThingIterator, Error> {
|
) -> Result<Option<ThingIterator>, Error> {
|
||||||
if let Some(exp) = self.iterators.get(ir as usize) {
|
if let Some(exp) = self.iterators.get(ir as usize) {
|
||||||
if let Operator::Matches(_) = io.op() {
|
if let Operator::Matches(_) = io.op() {
|
||||||
let ixn = &io.ix().name.0;
|
let ixn = &io.ix().name.0;
|
||||||
if let Some(fti) = self.ft_map.get(ixn) {
|
if let Some(fti) = self.ft_map.get(ixn) {
|
||||||
if let Some(fte) = self.exp_entries.get(exp) {
|
if let Some(fte) = self.exp_entries.get(exp) {
|
||||||
let it = MatchesThingIterator::new(fti, fte.0.terms_docs.clone()).await?;
|
let it = MatchesThingIterator::new(fti, fte.0.terms_docs.clone()).await?;
|
||||||
return Ok(ThingIterator::Matches(it));
|
return Ok(Some(ThingIterator::Matches(it)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(Error::BypassQueryPlanner)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn matches(
|
pub(crate) async fn matches(
|
||||||
|
|
|
@ -9,11 +9,13 @@ use crate::err::Error;
|
||||||
use crate::idx::planner::executor::QueryExecutor;
|
use crate::idx::planner::executor::QueryExecutor;
|
||||||
use crate::idx::planner::plan::{Plan, PlanBuilder};
|
use crate::idx::planner::plan::{Plan, PlanBuilder};
|
||||||
use crate::idx::planner::tree::Tree;
|
use crate::idx::planner::tree::Tree;
|
||||||
|
use crate::sql::with::With;
|
||||||
use crate::sql::{Cond, Table};
|
use crate::sql::{Cond, Table};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub(crate) struct QueryPlanner<'a> {
|
pub(crate) struct QueryPlanner<'a> {
|
||||||
opt: &'a Options,
|
opt: &'a Options,
|
||||||
|
with: &'a Option<With>,
|
||||||
cond: &'a Option<Cond>,
|
cond: &'a Option<Cond>,
|
||||||
/// There is one executor per table
|
/// There is one executor per table
|
||||||
executors: HashMap<String, QueryExecutor>,
|
executors: HashMap<String, QueryExecutor>,
|
||||||
|
@ -21,9 +23,10 @@ pub(crate) struct QueryPlanner<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> QueryPlanner<'a> {
|
impl<'a> QueryPlanner<'a> {
|
||||||
pub(crate) fn new(opt: &'a Options, cond: &'a Option<Cond>) -> Self {
|
pub(crate) fn new(opt: &'a Options, with: &'a Option<With>, cond: &'a Option<Cond>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
opt,
|
opt,
|
||||||
|
with,
|
||||||
cond,
|
cond,
|
||||||
executors: HashMap::default(),
|
executors: HashMap::default(),
|
||||||
requires_distinct: false,
|
requires_distinct: false,
|
||||||
|
@ -40,8 +43,7 @@ impl<'a> QueryPlanner<'a> {
|
||||||
let res = Tree::build(ctx, self.opt, txn, &t, self.cond).await?;
|
let res = Tree::build(ctx, self.opt, txn, &t, self.cond).await?;
|
||||||
if let Some((node, im)) = res {
|
if let Some((node, im)) = res {
|
||||||
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) {
|
let ok = match PlanBuilder::build(node, self.with)? {
|
||||||
Ok(plan) => match plan {
|
|
||||||
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));
|
||||||
|
@ -55,9 +57,7 @@ impl<'a> QueryPlanner<'a> {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
},
|
Plan::TableIterator => false,
|
||||||
Err(Error::BypassQueryPlanner) => false,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
};
|
||||||
self.executors.insert(t.0.clone(), exe);
|
self.executors.insert(t.0.clone(), exe);
|
||||||
if ok {
|
if ok {
|
||||||
|
|
|
@ -2,30 +2,40 @@ use crate::err::Error;
|
||||||
use crate::idx::ft::MatchRef;
|
use crate::idx::ft::MatchRef;
|
||||||
use crate::idx::planner::tree::Node;
|
use crate::idx::planner::tree::Node;
|
||||||
use crate::sql::statements::DefineIndexStatement;
|
use crate::sql::statements::DefineIndexStatement;
|
||||||
|
use crate::sql::with::With;
|
||||||
use crate::sql::Object;
|
use crate::sql::Object;
|
||||||
use crate::sql::{Expression, Idiom, Operator, Value};
|
use crate::sql::{Expression, Idiom, Operator, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub(super) struct PlanBuilder {
|
pub(super) struct PlanBuilder<'a> {
|
||||||
indexes: Vec<(Expression, IndexOption)>,
|
indexes: Vec<(Expression, IndexOption)>,
|
||||||
|
with: &'a Option<With>,
|
||||||
all_and: bool,
|
all_and: bool,
|
||||||
all_exp_with_index: bool,
|
all_exp_with_index: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlanBuilder {
|
impl<'a> PlanBuilder<'a> {
|
||||||
pub(super) fn build(root: Node) -> Result<Plan, Error> {
|
pub(super) fn build(root: Node, with: &'a Option<With>) -> Result<Plan, Error> {
|
||||||
|
if let Some(with) = with {
|
||||||
|
if matches!(with, With::NoIndex) {
|
||||||
|
return Ok(Plan::TableIterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut b = PlanBuilder {
|
let mut b = PlanBuilder {
|
||||||
indexes: Vec::new(),
|
indexes: Vec::new(),
|
||||||
|
with,
|
||||||
all_and: true,
|
all_and: true,
|
||||||
all_exp_with_index: true,
|
all_exp_with_index: true,
|
||||||
};
|
};
|
||||||
// Browse the AST and collect information
|
// Browse the AST and collect information
|
||||||
b.eval_node(root)?;
|
if !b.eval_node(root)? {
|
||||||
|
return Ok(Plan::TableIterator);
|
||||||
|
}
|
||||||
// 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 Err(Error::BypassQueryPlanner);
|
return Ok(Plan::TableIterator);
|
||||||
}
|
}
|
||||||
// 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 {
|
||||||
|
@ -37,10 +47,22 @@ impl PlanBuilder {
|
||||||
if b.all_exp_with_index {
|
if b.all_exp_with_index {
|
||||||
return Ok(Plan::MultiIndex(b.indexes));
|
return Ok(Plan::MultiIndex(b.indexes));
|
||||||
}
|
}
|
||||||
Err(Error::BypassQueryPlanner)
|
Ok(Plan::TableIterator)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_node(&mut self, node: Node) -> Result<(), Error> {
|
// Check if we have an explicit list of index we can use
|
||||||
|
fn filter_index_option(&self, io: Option<IndexOption>) -> Option<IndexOption> {
|
||||||
|
if let Some(io) = &io {
|
||||||
|
if let Some(With::Index(ixs)) = self.with {
|
||||||
|
if !ixs.contains(&io.ix().name.0) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_node(&mut self, node: Node) -> Result<bool, Error> {
|
||||||
match node {
|
match node {
|
||||||
Node::Expression {
|
Node::Expression {
|
||||||
io,
|
io,
|
||||||
|
@ -52,15 +74,15 @@ impl PlanBuilder {
|
||||||
self.all_and = false;
|
self.all_and = false;
|
||||||
}
|
}
|
||||||
let is_bool = self.check_boolean_operator(exp.operator());
|
let is_bool = self.check_boolean_operator(exp.operator());
|
||||||
if let Some(io) = io {
|
if let Some(io) = self.filter_index_option(io) {
|
||||||
self.add_index_option(exp, io);
|
self.add_index_option(exp, io);
|
||||||
} 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_expression(*left, *right)
|
||||||
}
|
}
|
||||||
Node::Unsupported => Err(Error::BypassQueryPlanner),
|
Node::Unsupported => Ok(false),
|
||||||
_ => Ok(()),
|
_ => Ok(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,10 +99,14 @@ impl PlanBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_expression(&mut self, left: Node, right: Node) -> Result<(), Error> {
|
fn eval_expression(&mut self, left: Node, right: Node) -> Result<bool, Error> {
|
||||||
self.eval_node(left)?;
|
if !self.eval_node(left)? {
|
||||||
self.eval_node(right)?;
|
return Ok(false);
|
||||||
Ok(())
|
}
|
||||||
|
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) {
|
||||||
|
@ -89,6 +115,7 @@ impl PlanBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum Plan {
|
pub(super) enum Plan {
|
||||||
|
TableIterator,
|
||||||
SingleIndex(Expression, IndexOption),
|
SingleIndex(Expression, IndexOption),
|
||||||
MultiIndex(Vec<(Expression, IndexOption)>),
|
MultiIndex(Vec<(Expression, IndexOption)>),
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl Tree {
|
||||||
opt: &'a Options,
|
opt: &'a Options,
|
||||||
txn: &'a Transaction,
|
txn: &'a Transaction,
|
||||||
table: &'a Table,
|
table: &'a Table,
|
||||||
cond: &Option<Cond>,
|
cond: &'a Option<Cond>,
|
||||||
) -> Result<Option<(Node, IndexMap)>, Error> {
|
) -> Result<Option<(Node, IndexMap)>, Error> {
|
||||||
let mut b = TreeBuilder {
|
let mut b = TreeBuilder {
|
||||||
ctx,
|
ctx,
|
||||||
|
|
|
@ -68,6 +68,7 @@ pub(crate) mod uuid;
|
||||||
pub(crate) mod value;
|
pub(crate) mod value;
|
||||||
pub(crate) mod version;
|
pub(crate) mod version;
|
||||||
pub(crate) mod view;
|
pub(crate) mod view;
|
||||||
|
pub(crate) mod with;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test;
|
pub(crate) mod test;
|
||||||
|
|
|
@ -24,6 +24,7 @@ use crate::sql::start::{start, Start};
|
||||||
use crate::sql::timeout::{timeout, Timeout};
|
use crate::sql::timeout::{timeout, Timeout};
|
||||||
use crate::sql::value::{selects, Value, Values};
|
use crate::sql::value::{selects, Value, Values};
|
||||||
use crate::sql::version::{version, Version};
|
use crate::sql::version::{version, Version};
|
||||||
|
use crate::sql::with::{with, With};
|
||||||
use derive::Store;
|
use derive::Store;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
|
@ -35,6 +36,7 @@ use std::fmt;
|
||||||
pub struct SelectStatement {
|
pub struct SelectStatement {
|
||||||
pub expr: Fields,
|
pub expr: Fields,
|
||||||
pub what: Values,
|
pub what: Values,
|
||||||
|
pub with: Option<With>,
|
||||||
pub cond: Option<Cond>,
|
pub cond: Option<Cond>,
|
||||||
pub split: Option<Splits>,
|
pub split: Option<Splits>,
|
||||||
pub group: Option<Groups>,
|
pub group: Option<Groups>,
|
||||||
|
@ -91,7 +93,7 @@ impl SelectStatement {
|
||||||
let opt = &opt.new_with_futures(false);
|
let opt = &opt.new_with_futures(false);
|
||||||
|
|
||||||
// Get a query planner
|
// Get a query planner
|
||||||
let mut planner = QueryPlanner::new(opt, &self.cond);
|
let mut planner = QueryPlanner::new(opt, &self.with, &self.cond);
|
||||||
// Loop over the select targets
|
// Loop over the select targets
|
||||||
for w in self.what.0.iter() {
|
for w in self.what.0.iter() {
|
||||||
let v = w.compute(ctx, opt, txn, doc).await?;
|
let v = w.compute(ctx, opt, txn, doc).await?;
|
||||||
|
@ -143,6 +145,9 @@ impl SelectStatement {
|
||||||
impl fmt::Display for SelectStatement {
|
impl fmt::Display for SelectStatement {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "SELECT {} FROM {}", self.expr, self.what)?;
|
write!(f, "SELECT {} FROM {}", self.expr, self.what)?;
|
||||||
|
if let Some(ref v) = self.with {
|
||||||
|
write!(f, " {v}")?
|
||||||
|
}
|
||||||
if let Some(ref v) = self.cond {
|
if let Some(ref v) = self.cond {
|
||||||
write!(f, " {v}")?
|
write!(f, " {v}")?
|
||||||
}
|
}
|
||||||
|
@ -188,6 +193,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> {
|
||||||
let (i, _) = tag_no_case("FROM")(i)?;
|
let (i, _) = tag_no_case("FROM")(i)?;
|
||||||
let (i, _) = shouldbespace(i)?;
|
let (i, _) = shouldbespace(i)?;
|
||||||
let (i, what) = selects(i)?;
|
let (i, what) = selects(i)?;
|
||||||
|
let (i, with) = opt(preceded(shouldbespace, with))(i)?;
|
||||||
let (i, cond) = opt(preceded(shouldbespace, cond))(i)?;
|
let (i, cond) = opt(preceded(shouldbespace, cond))(i)?;
|
||||||
let (i, split) = opt(preceded(shouldbespace, split))(i)?;
|
let (i, split) = opt(preceded(shouldbespace, split))(i)?;
|
||||||
check_split_on_fields(i, &expr, &split)?;
|
check_split_on_fields(i, &expr, &split)?;
|
||||||
|
@ -207,6 +213,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> {
|
||||||
SelectStatement {
|
SelectStatement {
|
||||||
expr,
|
expr,
|
||||||
what,
|
what,
|
||||||
|
with,
|
||||||
cond,
|
cond,
|
||||||
split,
|
split,
|
||||||
group,
|
group,
|
||||||
|
|
|
@ -39,6 +39,7 @@ mod timeout;
|
||||||
mod uuid;
|
mod uuid;
|
||||||
mod value;
|
mod value;
|
||||||
mod version;
|
mod version;
|
||||||
|
mod with;
|
||||||
|
|
||||||
use serde::ser::Error;
|
use serde::ser::Error;
|
||||||
use serde::ser::Serialize;
|
use serde::ser::Serialize;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::err::Error;
|
||||||
use crate::sql::explain::Explain;
|
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::with::With;
|
||||||
use crate::sql::Cond;
|
use crate::sql::Cond;
|
||||||
use crate::sql::Fetchs;
|
use crate::sql::Fetchs;
|
||||||
use crate::sql::Fields;
|
use crate::sql::Fields;
|
||||||
|
@ -48,6 +49,7 @@ impl ser::Serializer for Serializer {
|
||||||
pub struct SerializeSelectStatement {
|
pub struct SerializeSelectStatement {
|
||||||
expr: Option<Fields>,
|
expr: Option<Fields>,
|
||||||
what: Option<Values>,
|
what: Option<Values>,
|
||||||
|
with: Option<With>,
|
||||||
cond: Option<Cond>,
|
cond: Option<Cond>,
|
||||||
split: Option<Splits>,
|
split: Option<Splits>,
|
||||||
group: Option<Groups>,
|
group: Option<Groups>,
|
||||||
|
@ -76,6 +78,9 @@ impl serde::ser::SerializeStruct for SerializeSelectStatement {
|
||||||
"what" => {
|
"what" => {
|
||||||
self.what = Some(Values(value.serialize(ser::value::vec::Serializer.wrap())?));
|
self.what = Some(Values(value.serialize(ser::value::vec::Serializer.wrap())?));
|
||||||
}
|
}
|
||||||
|
"with" => {
|
||||||
|
self.with = value.serialize(ser::with::opt::Serializer.wrap())?;
|
||||||
|
}
|
||||||
"cond" => {
|
"cond" => {
|
||||||
self.cond = value.serialize(ser::cond::opt::Serializer.wrap())?;
|
self.cond = value.serialize(ser::cond::opt::Serializer.wrap())?;
|
||||||
}
|
}
|
||||||
|
@ -121,6 +126,7 @@ impl serde::ser::SerializeStruct for SerializeSelectStatement {
|
||||||
(Some(expr), Some(what), Some(parallel)) => Ok(SelectStatement {
|
(Some(expr), Some(what), Some(parallel)) => Ok(SelectStatement {
|
||||||
expr,
|
expr,
|
||||||
what,
|
what,
|
||||||
|
with: self.with,
|
||||||
parallel,
|
parallel,
|
||||||
explain: self.explain,
|
explain: self.explain,
|
||||||
cond: self.cond,
|
cond: self.cond,
|
||||||
|
@ -248,4 +254,34 @@ 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_full() {
|
||||||
|
let stmt = SelectStatement {
|
||||||
|
explain: Some(Explain(true)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let value: SelectStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(value, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_with_noindex() {
|
||||||
|
let stmt = SelectStatement {
|
||||||
|
with: Some(With::NoIndex),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let value: SelectStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(value, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_with_index() {
|
||||||
|
let stmt = SelectStatement {
|
||||||
|
with: Some(With::Index(vec!["uniq".to_string(), "ft".to_string(), "idx".to_string()])),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let value: SelectStatement = stmt.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(value, stmt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
78
lib/src/sql/value/serde/ser/with/mod.rs
Normal file
78
lib/src/sql/value/serde/ser/with/mod.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
pub(super) mod opt;
|
||||||
|
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::with::With;
|
||||||
|
use serde::ser::Error as _;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
pub(super) struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = With;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<With, Error>;
|
||||||
|
type SerializeTuple = Impossible<With, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<With, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<With, Error>;
|
||||||
|
type SerializeMap = Impossible<With, Error>;
|
||||||
|
type SerializeStruct = Impossible<With, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<With, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "an enum `With`";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_unit_variant(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
) -> Result<Self::Ok, Error> {
|
||||||
|
match variant {
|
||||||
|
"NoIndex" => Ok(With::NoIndex),
|
||||||
|
variant => Err(Error::custom(format!("unexpected unit variant `{name}::{variant}`"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn serialize_newtype_variant<T>(
|
||||||
|
self,
|
||||||
|
name: &'static str,
|
||||||
|
_variant_index: u32,
|
||||||
|
variant: &'static str,
|
||||||
|
value: &T,
|
||||||
|
) -> Result<Self::Ok, Error>
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
match variant {
|
||||||
|
"Index" => Ok(With::Index(value.serialize(ser::string::vec::Serializer.wrap())?)),
|
||||||
|
variant => {
|
||||||
|
Err(Error::custom(format!("unexpected newtype variant `{name}::{variant}`")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_noindex() {
|
||||||
|
let with = With::NoIndex;
|
||||||
|
let serialized = with.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(with, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_index() {
|
||||||
|
let with = With::Index(vec!["idx".to_string(), "uniq".to_string()]);
|
||||||
|
let serialized = with.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(with, serialized);
|
||||||
|
}
|
||||||
|
}
|
55
lib/src/sql/value/serde/ser/with/opt.rs
Normal file
55
lib/src/sql/value/serde/ser/with/opt.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::sql::value::serde::ser;
|
||||||
|
use crate::sql::with::With;
|
||||||
|
use serde::ser::Impossible;
|
||||||
|
use serde::ser::Serialize;
|
||||||
|
|
||||||
|
pub struct Serializer;
|
||||||
|
|
||||||
|
impl ser::Serializer for Serializer {
|
||||||
|
type Ok = Option<With>;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
type SerializeSeq = Impossible<Option<With>, Error>;
|
||||||
|
type SerializeTuple = Impossible<Option<With>, Error>;
|
||||||
|
type SerializeTupleStruct = Impossible<Option<With>, Error>;
|
||||||
|
type SerializeTupleVariant = Impossible<Option<With>, Error>;
|
||||||
|
type SerializeMap = Impossible<Option<With>, Error>;
|
||||||
|
type SerializeStruct = Impossible<Option<With>, Error>;
|
||||||
|
type SerializeStructVariant = Impossible<Option<With>, Error>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static str = "an `Option<With>`";
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
{
|
||||||
|
Ok(Some(value.serialize(ser::with::Serializer.wrap())?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ser::Serializer as _;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn none() {
|
||||||
|
let option: Option<With> = None;
|
||||||
|
let serialized = option.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(option, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn some() {
|
||||||
|
let option = Some(With::NoIndex);
|
||||||
|
let serialized = option.serialize(Serializer.wrap()).unwrap();
|
||||||
|
assert_eq!(option, serialized);
|
||||||
|
}
|
||||||
|
}
|
73
lib/src/sql/with.rs
Normal file
73
lib/src/sql/with.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use crate::sql::comment::shouldbespace;
|
||||||
|
use crate::sql::common::commas;
|
||||||
|
use crate::sql::error::IResult;
|
||||||
|
use crate::sql::ident::ident_raw;
|
||||||
|
use derive::Store;
|
||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Store, Hash)]
|
||||||
|
pub enum With {
|
||||||
|
NoIndex,
|
||||||
|
Index(Vec<String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for With {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
f.write_str("WITH")?;
|
||||||
|
match self {
|
||||||
|
With::NoIndex => f.write_str(" NOINDEX"),
|
||||||
|
With::Index(i) => {
|
||||||
|
f.write_str(" INDEX ")?;
|
||||||
|
f.write_str(&i.join(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn no_index(i: &str) -> IResult<&str, With> {
|
||||||
|
let (i, _) = tag_no_case("NOINDEX")(i)?;
|
||||||
|
Ok((i, With::NoIndex))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(i: &str) -> IResult<&str, With> {
|
||||||
|
let (i, _) = tag_no_case("INDEX")(i)?;
|
||||||
|
let (i, _) = shouldbespace(i)?;
|
||||||
|
let (i, v) = separated_list1(commas, ident_raw)(i)?;
|
||||||
|
Ok((i, With::Index(v)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with(i: &str) -> IResult<&str, With> {
|
||||||
|
let (i, _) = tag_no_case("WITH")(i)?;
|
||||||
|
let (i, _) = shouldbespace(i)?;
|
||||||
|
alt((no_index, index))(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_no_index() {
|
||||||
|
let sql = "WITH NOINDEX";
|
||||||
|
let res = with(sql);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let out = res.unwrap().1;
|
||||||
|
assert_eq!(out, With::NoIndex);
|
||||||
|
assert_eq!("WITH NOINDEX", format!("{}", out));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_index() {
|
||||||
|
let sql = "WITH INDEX idx,uniq";
|
||||||
|
let res = with(sql);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
let out = res.unwrap().1;
|
||||||
|
assert_eq!(out, With::Index(vec!["idx".to_string(), "uniq".to_string()]));
|
||||||
|
assert_eq!("WITH INDEX idx,uniq", format!("{}", out));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,157 @@
|
||||||
mod parse;
|
mod parse;
|
||||||
|
|
||||||
use parse::Parse;
|
use parse::Parse;
|
||||||
use surrealdb::dbs::Session;
|
use surrealdb::dbs::{Response, Session};
|
||||||
use surrealdb::err::Error;
|
use surrealdb::err::Error;
|
||||||
use surrealdb::kvs::Datastore;
|
use surrealdb::kvs::Datastore;
|
||||||
use surrealdb::sql::Value;
|
use surrealdb::sql::Value;
|
||||||
|
|
||||||
async fn test_select_where_iterate_multi_index(parallel: bool) -> Result<(), Error> {
|
#[tokio::test]
|
||||||
let parallel = if parallel {
|
async fn select_where_iterate_three_multi_index() -> Result<(), Error> {
|
||||||
"PARALLEL"
|
let mut res = execute_test(&three_multi_index_query("", ""), 12).await?;
|
||||||
} else {
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
||||||
""
|
// OR results
|
||||||
};
|
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
||||||
let sql = format!(
|
// AND results
|
||||||
"
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_FT_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_three_multi_index_parallel() -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&three_multi_index_query("", "PARALLEL"), 12).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
||||||
|
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_FT_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_three_multi_index_with_all_index() -> Result<(), Error> {
|
||||||
|
let mut res =
|
||||||
|
execute_test(&three_multi_index_query("WITH INDEX uniq_name,idx_genre,ft_company", ""), 12)
|
||||||
|
.await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
||||||
|
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_FT_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_three_multi_index_with_one_ft_index() -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&three_multi_index_query("WITH INDEX ft_company", ""), 12).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Lizzie' }, { name: 'Tobie' } ]")?;
|
||||||
|
check_result(&mut res, THREE_TABLE_EXPLAIN)?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_FT_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_three_multi_index_with_one_index() -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&three_multi_index_query("WITH INDEX uniq_name", ""), 12).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Lizzie' }, { name: 'Tobie' } ]")?;
|
||||||
|
check_result(&mut res, THREE_TABLE_EXPLAIN)?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_UNIQ_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_two_multi_index() -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&two_multi_index_query("", ""), 9).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
|
check_result(&mut res, TWO_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_IDX_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_two_multi_index_with_one_index() -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&two_multi_index_query("WITH INDEX idx_genre", ""), 9).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
|
check_result(&mut res, &table_explain(2))?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_IDX_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_two_multi_index_with_two_index() -> Result<(), Error> {
|
||||||
|
let mut res =
|
||||||
|
execute_test(&two_multi_index_query("WITH INDEX idx_genre,uniq_name", ""), 9).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
|
check_result(&mut res, TWO_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, SINGLE_INDEX_IDX_EXPLAIN)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_where_iterate_two_no_index() -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&two_multi_index_query("WITH NOINDEX", ""), 9).await?;
|
||||||
|
// OR results
|
||||||
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
|
check_result(&mut res, &table_explain(2))?;
|
||||||
|
// AND results
|
||||||
|
check_result(&mut res, "[{name: 'Jaime'}]")?;
|
||||||
|
check_result(&mut res, &table_explain(1))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute_test(sql: &str, expected_result: usize) -> Result<Vec<Response>, Error> {
|
||||||
|
let dbs = Datastore::new("memory").await?;
|
||||||
|
let ses = Session::for_kv().with_ns("test").with_db("test");
|
||||||
|
let mut res = dbs.execute(sql, &ses, None).await?;
|
||||||
|
assert_eq!(res.len(), expected_result);
|
||||||
|
// Check that the setup is ok
|
||||||
|
for _ in 0..(expected_result - 4) {
|
||||||
|
let _ = res.remove(0).result?;
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_result(res: &mut Vec<Response>, expected: &str) -> Result<(), Error> {
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(expected);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn two_multi_index_query(with: &str, parallel: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"CREATE person:tobie SET name = 'Tobie', genre='m', company='SurrealDB';
|
||||||
|
CREATE person:jaime SET name = 'Jaime', genre='m', company='SurrealDB';
|
||||||
|
CREATE person:lizzie SET name = 'Lizzie', genre='f', company='SurrealDB';
|
||||||
|
DEFINE INDEX uniq_name ON TABLE person COLUMNS name UNIQUE;
|
||||||
|
DEFINE INDEX idx_genre ON TABLE person COLUMNS genre;
|
||||||
|
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' {parallel};
|
||||||
|
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' {parallel} EXPLAIN FULL;
|
||||||
|
SELECT name FROM person {with} WHERE name = 'Jaime' AND genre = 'm' {parallel};
|
||||||
|
SELECT name FROM person {with} WHERE name = 'Jaime' AND genre = 'm' {parallel} EXPLAIN FULL;"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn three_multi_index_query(with: &str, parallel: &str) -> String {
|
||||||
|
format!("
|
||||||
CREATE person:tobie SET name = 'Tobie', genre='m', company='SurrealDB';
|
CREATE person:tobie SET name = 'Tobie', genre='m', company='SurrealDB';
|
||||||
CREATE person:jaime SET name = 'Jaime', genre='m', company='SurrealDB';
|
CREATE person:jaime SET name = 'Jaime', genre='m', company='SurrealDB';
|
||||||
CREATE person:lizzie SET name = 'Lizzie', genre='f', company='SurrealDB';
|
CREATE person:lizzie SET name = 'Lizzie', genre='f', company='SurrealDB';
|
||||||
|
@ -22,37 +160,47 @@ async fn test_select_where_iterate_multi_index(parallel: bool) -> Result<(), Err
|
||||||
DEFINE INDEX ft_company ON person FIELDS company SEARCH ANALYZER simple BM25;
|
DEFINE INDEX ft_company ON person FIELDS company SEARCH ANALYZER simple BM25;
|
||||||
DEFINE INDEX uniq_name ON TABLE person COLUMNS name UNIQUE;
|
DEFINE INDEX uniq_name ON TABLE person COLUMNS name UNIQUE;
|
||||||
DEFINE INDEX idx_genre ON TABLE person COLUMNS genre;
|
DEFINE INDEX idx_genre ON TABLE person COLUMNS genre;
|
||||||
SELECT name FROM person WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel};
|
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel};
|
||||||
SELECT name FROM person WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel} EXPLAIN FULL;"
|
SELECT name FROM person {with} WHERE name = 'Jaime' OR genre = 'm' OR company @@ 'surrealdb' {parallel} EXPLAIN FULL;
|
||||||
);
|
SELECT name FROM person {with} WHERE name = 'Jaime' AND genre = 'm' AND company @@ 'surrealdb' {parallel};
|
||||||
let dbs = Datastore::new("memory").await?;
|
SELECT name FROM person {with} WHERE name = 'Jaime' AND genre = 'm' AND company @@ 'surrealdb' {parallel} EXPLAIN FULL;")
|
||||||
let ses = Session::for_kv().with_ns("test").with_db("test");
|
|
||||||
let res = &mut dbs.execute(&sql, &ses, None).await?;
|
|
||||||
assert_eq!(res.len(), 10);
|
|
||||||
//
|
|
||||||
for _ in 0..8 {
|
|
||||||
let _ = res.remove(0).result?;
|
|
||||||
}
|
}
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result?;
|
fn table_explain(fetch_count: usize) -> String {
|
||||||
let val = Value::parse(
|
format!(
|
||||||
"[
|
"[
|
||||||
|
{{
|
||||||
|
detail: {{
|
||||||
|
table: 'person'
|
||||||
|
}},
|
||||||
|
operation: 'Iterate Table'
|
||||||
|
}},
|
||||||
|
{{
|
||||||
|
detail: {{
|
||||||
|
count: {fetch_count}
|
||||||
|
}},
|
||||||
|
operation: 'Fetch'
|
||||||
|
}}
|
||||||
|
]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const THREE_TABLE_EXPLAIN: &str = "[
|
||||||
{
|
{
|
||||||
name: 'Jaime'
|
detail: {
|
||||||
|
table: 'person'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Tobie'
|
detail: {
|
||||||
|
count: 3
|
||||||
},
|
},
|
||||||
{
|
operation: 'Fetch'
|
||||||
name: 'Lizzie'
|
|
||||||
}
|
}
|
||||||
]",
|
]";
|
||||||
);
|
|
||||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
const THREE_MULTI_INDEX_EXPLAIN: &str = "[
|
||||||
//
|
|
||||||
let tmp = res.remove(0).result?;
|
|
||||||
let val = Value::parse(
|
|
||||||
"[
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
plan: {
|
plan: {
|
||||||
|
@ -92,18 +240,95 @@ async fn test_select_where_iterate_multi_index(parallel: bool) -> Result<(), Err
|
||||||
},
|
},
|
||||||
operation: 'Fetch'
|
operation: 'Fetch'
|
||||||
}
|
}
|
||||||
]",
|
]";
|
||||||
);
|
|
||||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
const SINGLE_INDEX_FT_EXPLAIN: &str = "[
|
||||||
async fn select_where_iterate_multi_index() -> Result<(), Error> {
|
{
|
||||||
test_select_where_iterate_multi_index(false).await
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'ft_company',
|
||||||
|
operator: '@@',
|
||||||
|
value: 'surrealdb'
|
||||||
|
},
|
||||||
|
table: 'person',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
count: 1
|
||||||
|
},
|
||||||
|
operation: 'Fetch'
|
||||||
}
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
#[tokio::test]
|
const SINGLE_INDEX_UNIQ_EXPLAIN: &str = "[
|
||||||
async fn select_where_iterate_multi_index_parallel() -> Result<(), Error> {
|
{
|
||||||
test_select_where_iterate_multi_index(true).await
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'uniq_name',
|
||||||
|
operator: '=',
|
||||||
|
value: 'Jaime'
|
||||||
|
},
|
||||||
|
table: 'person',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
count: 1
|
||||||
|
},
|
||||||
|
operation: 'Fetch'
|
||||||
}
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
const SINGLE_INDEX_IDX_EXPLAIN: &str = "[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'idx_genre',
|
||||||
|
operator: '=',
|
||||||
|
value: 'm'
|
||||||
|
},
|
||||||
|
table: 'person'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
count: 1
|
||||||
|
},
|
||||||
|
operation: 'Fetch'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
const TWO_MULTI_INDEX_EXPLAIN: &str = "[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'uniq_name',
|
||||||
|
operator: '=',
|
||||||
|
value: 'Jaime'
|
||||||
|
},
|
||||||
|
table: 'person',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'idx_genre',
|
||||||
|
operator: '=',
|
||||||
|
value: 'm'
|
||||||
|
},
|
||||||
|
table: 'person',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
count: 2
|
||||||
|
},
|
||||||
|
operation: 'Fetch'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
Loading…
Reference in a new issue