2023-06-19 18:41:13 +00:00
|
|
|
use crate::err::Error;
|
2023-07-20 12:56:32 +00:00
|
|
|
use crate::idx::ft::MatchRef;
|
|
|
|
use crate::idx::planner::tree::Node;
|
2023-06-19 18:41:13 +00:00
|
|
|
use crate::sql::statements::DefineIndexStatement;
|
2023-07-21 18:41:36 +00:00
|
|
|
use crate::sql::with::With;
|
2023-07-20 12:56:32 +00:00
|
|
|
use crate::sql::Object;
|
|
|
|
use crate::sql::{Expression, Idiom, Operator, Value};
|
2023-06-19 18:41:13 +00:00
|
|
|
use std::collections::HashMap;
|
2023-06-23 20:26:19 +00:00
|
|
|
use std::hash::Hash;
|
|
|
|
use std::sync::Arc;
|
2023-06-19 18:41:13 +00:00
|
|
|
|
2023-07-21 18:41:36 +00:00
|
|
|
pub(super) struct PlanBuilder<'a> {
|
2023-06-23 20:26:19 +00:00
|
|
|
indexes: Vec<(Expression, IndexOption)>,
|
2023-07-21 18:41:36 +00:00
|
|
|
with: &'a Option<With>,
|
2023-07-20 12:56:32 +00:00
|
|
|
all_and: bool,
|
|
|
|
all_exp_with_index: bool,
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 18:41:36 +00:00
|
|
|
impl<'a> PlanBuilder<'a> {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2023-07-20 12:56:32 +00:00
|
|
|
let mut b = PlanBuilder {
|
|
|
|
indexes: Vec::new(),
|
2023-07-21 18:41:36 +00:00
|
|
|
with,
|
2023-07-20 12:56:32 +00:00
|
|
|
all_and: true,
|
|
|
|
all_exp_with_index: true,
|
|
|
|
};
|
|
|
|
// Browse the AST and collect information
|
2023-07-21 18:41:36 +00:00
|
|
|
if !b.eval_node(root)? {
|
|
|
|
return Ok(Plan::TableIterator);
|
|
|
|
}
|
2023-07-20 12:56:32 +00:00
|
|
|
// If we didn't found any index, we're done with no index plan
|
|
|
|
if b.indexes.is_empty() {
|
2023-07-21 18:41:36 +00:00
|
|
|
return Ok(Plan::TableIterator);
|
2023-07-20 12:56:32 +00:00
|
|
|
}
|
|
|
|
// If every boolean operator are AND then we can use the single index plan
|
|
|
|
if b.all_and {
|
|
|
|
if let Some((e, i)) = b.indexes.pop() {
|
|
|
|
return Ok(Plan::SingleIndex(e, i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If every expression is backed by an index with can use the MultiIndex plan
|
|
|
|
if b.all_exp_with_index {
|
|
|
|
return Ok(Plan::MultiIndex(b.indexes));
|
|
|
|
}
|
2023-07-21 18:41:36 +00:00
|
|
|
Ok(Plan::TableIterator)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-21 18:41:36 +00:00
|
|
|
fn eval_node(&mut self, node: Node) -> Result<bool, Error> {
|
2023-07-20 12:56:32 +00:00
|
|
|
match node {
|
|
|
|
Node::Expression {
|
|
|
|
io,
|
|
|
|
left,
|
|
|
|
right,
|
|
|
|
exp,
|
|
|
|
} => {
|
|
|
|
if self.all_and && Operator::Or.eq(exp.operator()) {
|
|
|
|
self.all_and = false;
|
|
|
|
}
|
|
|
|
let is_bool = self.check_boolean_operator(exp.operator());
|
2023-07-21 18:41:36 +00:00
|
|
|
if let Some(io) = self.filter_index_option(io) {
|
2023-07-20 12:56:32 +00:00
|
|
|
self.add_index_option(exp, io);
|
|
|
|
} else if self.all_exp_with_index && !is_bool {
|
|
|
|
self.all_exp_with_index = false;
|
|
|
|
}
|
|
|
|
self.eval_expression(*left, *right)
|
|
|
|
}
|
2023-07-21 18:41:36 +00:00
|
|
|
Node::Unsupported => Ok(false),
|
|
|
|
_ => Ok(true),
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-20 12:56:32 +00:00
|
|
|
fn check_boolean_operator(&mut self, op: &Operator) -> bool {
|
|
|
|
match op {
|
|
|
|
Operator::Neg | Operator::Or => {
|
|
|
|
if self.all_and {
|
|
|
|
self.all_and = false;
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
Operator::And => true,
|
|
|
|
_ => false,
|
2023-06-23 20:26:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-21 18:41:36 +00:00
|
|
|
fn eval_expression(&mut self, left: Node, right: Node) -> Result<bool, Error> {
|
|
|
|
if !self.eval_node(left)? {
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
if !self.eval_node(right)? {
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
Ok(true)
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-20 12:56:32 +00:00
|
|
|
fn add_index_option(&mut self, e: Expression, i: IndexOption) {
|
|
|
|
self.indexes.push((e, i));
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-20 12:56:32 +00:00
|
|
|
pub(super) enum Plan {
|
2023-07-21 18:41:36 +00:00
|
|
|
TableIterator,
|
2023-07-20 12:56:32 +00:00
|
|
|
SingleIndex(Expression, IndexOption),
|
|
|
|
MultiIndex(Vec<(Expression, IndexOption)>),
|
|
|
|
}
|
|
|
|
|
2023-06-19 18:41:13 +00:00
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
2023-07-20 12:56:32 +00:00
|
|
|
pub(crate) struct IndexOption(Arc<Inner>);
|
2023-06-23 20:26:19 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
|
|
|
pub(super) struct Inner {
|
|
|
|
ix: DefineIndexStatement,
|
|
|
|
id: Idiom,
|
|
|
|
v: Value,
|
|
|
|
qs: Option<String>,
|
|
|
|
op: Operator,
|
|
|
|
mr: Option<MatchRef>,
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl IndexOption {
|
2023-06-23 20:26:19 +00:00
|
|
|
pub(super) fn new(
|
|
|
|
ix: DefineIndexStatement,
|
|
|
|
id: Idiom,
|
|
|
|
op: Operator,
|
|
|
|
v: Value,
|
|
|
|
qs: Option<String>,
|
|
|
|
mr: Option<MatchRef>,
|
|
|
|
) -> Self {
|
|
|
|
Self(Arc::new(Inner {
|
2023-06-19 18:41:13 +00:00
|
|
|
ix,
|
2023-06-23 20:26:19 +00:00
|
|
|
id,
|
2023-06-19 18:41:13 +00:00
|
|
|
op,
|
|
|
|
v,
|
2023-06-23 20:26:19 +00:00
|
|
|
qs,
|
|
|
|
mr,
|
|
|
|
}))
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
|
2023-06-23 20:26:19 +00:00
|
|
|
pub(super) fn ix(&self) -> &DefineIndexStatement {
|
|
|
|
&self.0.ix
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn op(&self) -> &Operator {
|
|
|
|
&self.0.op
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn value(&self) -> &Value {
|
|
|
|
&self.0.v
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn qs(&self) -> Option<&String> {
|
|
|
|
self.0.qs.as_ref()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn id(&self) -> &Idiom {
|
|
|
|
&self.0.id
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn match_ref(&self) -> Option<&MatchRef> {
|
|
|
|
self.0.mr.as_ref()
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
|
2023-07-20 12:56:32 +00:00
|
|
|
pub(crate) fn explain(&self) -> Value {
|
|
|
|
Value::Object(Object::from(HashMap::from([
|
|
|
|
("index", Value::from(self.ix().name.0.to_owned())),
|
|
|
|
("operator", Value::from(self.op().to_string())),
|
|
|
|
("value", self.value().clone()),
|
|
|
|
])))
|
2023-06-19 18:41:13 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-23 20:26:19 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::idx::planner::plan::IndexOption;
|
|
|
|
use crate::sql::statements::DefineIndexStatement;
|
|
|
|
use crate::sql::{Idiom, Operator, Value};
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_hash_index_option() {
|
|
|
|
let mut set = HashSet::new();
|
|
|
|
let io1 = IndexOption::new(
|
|
|
|
DefineIndexStatement::default(),
|
|
|
|
Idiom::from("a.b".to_string()),
|
|
|
|
Operator::Equal,
|
|
|
|
Value::from("test"),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
|
|
|
|
let io2 = IndexOption::new(
|
|
|
|
DefineIndexStatement::default(),
|
|
|
|
Idiom::from("a.b".to_string()),
|
|
|
|
Operator::Equal,
|
|
|
|
Value::from("test"),
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
|
|
|
|
set.insert(io1);
|
|
|
|
set.insert(io2.clone());
|
|
|
|
set.insert(io2);
|
|
|
|
|
|
|
|
assert_eq!(set.len(), 1);
|
|
|
|
}
|
|
|
|
}
|