Bug fix: Improve support of range queries with complex queries (#3786)
This commit is contained in:
parent
c82bbc0820
commit
829fb0baf9
7 changed files with 613 additions and 94 deletions
|
@ -19,18 +19,19 @@ use crate::sql::thing::Thing;
|
|||
use crate::sql::value::Value;
|
||||
use async_recursion::async_recursion;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum Iterable {
|
||||
Value(Value),
|
||||
Table(Table),
|
||||
Table(Arc<Table>),
|
||||
Thing(Thing),
|
||||
Range(Range),
|
||||
Edges(Edges),
|
||||
Defer(Thing),
|
||||
Mergeable(Thing, Value),
|
||||
Relatable(Thing, Thing, Thing),
|
||||
Index(Table, IteratorRef),
|
||||
Index(Arc<Table>, IteratorRef),
|
||||
}
|
||||
|
||||
pub(crate) struct Processed {
|
||||
|
@ -117,7 +118,7 @@ impl Iterator {
|
|||
}
|
||||
_ => {
|
||||
// Ingest the table for scanning
|
||||
self.ingest(Iterable::Table(v))
|
||||
self.ingest(Iterable::Table(Arc::new(v)))
|
||||
}
|
||||
},
|
||||
// There is no data clause so create a record id
|
||||
|
@ -128,7 +129,7 @@ impl Iterator {
|
|||
}
|
||||
_ => {
|
||||
// Ingest the table for scanning
|
||||
self.ingest(Iterable::Table(v))
|
||||
self.ingest(Iterable::Table(Arc::new(v)))
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -127,10 +127,10 @@ impl<'a> Processor<'a> {
|
|||
// Avoiding search in the hashmap of the query planner for each doc
|
||||
let mut ctx = Context::new(ctx);
|
||||
ctx.set_query_executor(exe.clone());
|
||||
return self.process_table(&ctx, opt, txn, stm, v).await;
|
||||
return self.process_table(&ctx, opt, txn, stm, v.as_ref()).await;
|
||||
}
|
||||
}
|
||||
self.process_table(ctx, opt, txn, stm, v).await?
|
||||
self.process_table(ctx, opt, txn, stm, v.as_ref()).await?
|
||||
}
|
||||
Iterable::Range(v) => self.process_range(ctx, opt, txn, stm, v).await?,
|
||||
Iterable::Edges(e) => self.process_edge(ctx, opt, txn, stm, e).await?,
|
||||
|
@ -141,10 +141,10 @@ impl<'a> Processor<'a> {
|
|||
// Avoiding search in the hashmap of the query planner for each doc
|
||||
let mut ctx = Context::new(ctx);
|
||||
ctx.set_query_executor(exe.clone());
|
||||
return self.process_index(&ctx, opt, txn, stm, t, ir).await;
|
||||
return self.process_index(&ctx, opt, txn, stm, t.as_ref(), ir).await;
|
||||
}
|
||||
}
|
||||
self.process_index(ctx, opt, txn, stm, t, ir).await?
|
||||
self.process_index(ctx, opt, txn, stm, t.as_ref(), ir).await?
|
||||
}
|
||||
Iterable::Mergeable(v, o) => {
|
||||
self.process_mergeable(ctx, opt, txn, stm, v, o).await?
|
||||
|
@ -302,13 +302,13 @@ impl<'a> Processor<'a> {
|
|||
opt: &Options,
|
||||
txn: &Transaction,
|
||||
stm: &Statement<'_>,
|
||||
v: Table,
|
||||
v: &Table,
|
||||
) -> Result<(), Error> {
|
||||
// Check that the table exists
|
||||
txn.lock().await.check_ns_db_tb(opt.ns(), opt.db(), &v, opt.strict).await?;
|
||||
txn.lock().await.check_ns_db_tb(opt.ns(), opt.db(), v, opt.strict).await?;
|
||||
// Prepare the start and end keys
|
||||
let beg = thing::prefix(opt.ns(), opt.db(), &v);
|
||||
let end = thing::suffix(opt.ns(), opt.db(), &v);
|
||||
let beg = thing::prefix(opt.ns(), opt.db(), v);
|
||||
let end = thing::suffix(opt.ns(), opt.db(), v);
|
||||
// Loop until no more keys
|
||||
let mut next_page = Some(ScanPage::from(beg..end));
|
||||
while let Some(page) = next_page {
|
||||
|
@ -556,7 +556,7 @@ impl<'a> Processor<'a> {
|
|||
opt: &Options,
|
||||
txn: &Transaction,
|
||||
stm: &Statement<'_>,
|
||||
table: Table,
|
||||
table: &Table,
|
||||
ir: IteratorRef,
|
||||
) -> Result<(), Error> {
|
||||
// Check that the table exists
|
||||
|
|
|
@ -266,7 +266,22 @@ impl QueryExecutor {
|
|||
) -> Result<Option<ThingIterator>, Error> {
|
||||
if let Some(it_entry) = self.0.it_entries.get(it_ref as usize) {
|
||||
match it_entry {
|
||||
IteratorEntry::Single(_, io) => {
|
||||
IteratorEntry::Single(_, io) => self.new_single_iterator(opt, it_ref, io).await,
|
||||
IteratorEntry::Range(_, ir, from, to) => {
|
||||
Ok(self.new_range_iterator(opt, *ir, from, to))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn new_single_iterator(
|
||||
&self,
|
||||
opt: &Options,
|
||||
it_ref: IteratorRef,
|
||||
io: &IndexOption,
|
||||
) -> Result<Option<ThingIterator>, Error> {
|
||||
if let Some(ix) = self.0.index_definitions.get(io.ix_ref() as usize) {
|
||||
match ix.index {
|
||||
Index::Idx => Ok(Self::new_index_iterator(opt, ix, io.clone())),
|
||||
|
@ -280,14 +295,6 @@ impl QueryExecutor {
|
|||
Ok(None)
|
||||
}
|
||||
}
|
||||
IteratorEntry::Range(_, ir, from, to) => {
|
||||
Ok(self.new_range_iterator(opt, *ir, from, to))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_index_iterator(
|
||||
opt: &Options,
|
||||
|
|
|
@ -53,12 +53,20 @@ impl<'a> QueryPlanner<'a> {
|
|||
) -> Result<(), Error> {
|
||||
let mut is_table_iterator = false;
|
||||
let mut is_knn = false;
|
||||
let t = Arc::new(t);
|
||||
match Tree::build(ctx, self.opt, txn, &t, self.cond, self.with).await? {
|
||||
Some((node, im, with_indexes, knn_expressions)) => {
|
||||
is_knn = is_knn || !knn_expressions.is_empty();
|
||||
let mut exe =
|
||||
InnerQueryExecutor::new(ctx, self.opt, txn, &t, im, knn_expressions).await?;
|
||||
match PlanBuilder::build(node, self.with, with_indexes)? {
|
||||
Some(tree) => {
|
||||
is_knn = is_knn || !tree.knn_expressions.is_empty();
|
||||
let mut exe = InnerQueryExecutor::new(
|
||||
ctx,
|
||||
self.opt,
|
||||
txn,
|
||||
&t,
|
||||
tree.index_map,
|
||||
tree.knn_expressions,
|
||||
)
|
||||
.await?;
|
||||
match PlanBuilder::build(tree.root, self.with, tree.with_indexes)? {
|
||||
Plan::SingleIndex(exp, io) => {
|
||||
if io.require_distinct() {
|
||||
self.requires_distinct = true;
|
||||
|
@ -66,15 +74,21 @@ impl<'a> QueryPlanner<'a> {
|
|||
let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
|
||||
self.add(t.clone(), Some(ir), exe, it);
|
||||
}
|
||||
Plan::MultiIndex(v) => {
|
||||
for (exp, io) in v {
|
||||
let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
|
||||
Plan::MultiIndex(non_range_indexes, ranges_indexes) => {
|
||||
for (exp, io) in non_range_indexes {
|
||||
let ie = IteratorEntry::Single(exp, io);
|
||||
let ir = exe.add_iterator(ie);
|
||||
it.ingest(Iterable::Index(t.clone(), ir));
|
||||
self.requires_distinct = true;
|
||||
}
|
||||
for (ixn, rq) in ranges_indexes {
|
||||
let ie = IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to);
|
||||
let ir = exe.add_iterator(ie);
|
||||
it.ingest(Iterable::Index(t.clone(), ir));
|
||||
}
|
||||
self.requires_distinct = true;
|
||||
self.add(t.clone(), None, exe, it);
|
||||
}
|
||||
Plan::SingleIndexMultiExpression(ixn, rq) => {
|
||||
Plan::SingleIndexRange(ixn, rq) => {
|
||||
let ir =
|
||||
exe.add_iterator(IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to));
|
||||
self.add(t.clone(), Some(ir), exe, it);
|
||||
|
@ -103,7 +117,7 @@ impl<'a> QueryPlanner<'a> {
|
|||
|
||||
fn add(
|
||||
&mut self,
|
||||
tb: Table,
|
||||
tb: Arc<Table>,
|
||||
irf: Option<IteratorRef>,
|
||||
exe: InnerQueryExecutor,
|
||||
it: &mut Iterator,
|
||||
|
|
|
@ -1,19 +1,29 @@
|
|||
use crate::err::Error;
|
||||
use crate::idx::ft::MatchRef;
|
||||
use crate::idx::planner::tree::{IndexRef, Node};
|
||||
use crate::idx::planner::tree::{GroupRef, IndexRef, Node};
|
||||
use crate::sql::with::With;
|
||||
use crate::sql::{Array, Idiom, Object};
|
||||
use crate::sql::{Expression, Operator, Value};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The `PlanBuilder` struct represents a builder for constructing query plans.
|
||||
pub(super) struct PlanBuilder {
|
||||
indexes: Vec<(Arc<Expression>, IndexOption)>,
|
||||
range_queries: HashMap<IndexRef, RangeQueryBuilder>,
|
||||
/// Do we have at least one index?
|
||||
has_indexes: bool,
|
||||
/// List of expressions that are not ranges, backed by an index
|
||||
non_range_indexes: Vec<(Arc<Expression>, IndexOption)>,
|
||||
/// List of indexes involved in this plan
|
||||
with_indexes: Vec<IndexRef>,
|
||||
/// Group each possible optimisations local to a SubQuery
|
||||
groups: BTreeMap<GroupRef, Group>, // The order matters because we want the plan to be consistent across repeated queries.
|
||||
/// Does a group contains only AND relations?
|
||||
all_and_groups: HashMap<GroupRef, bool>,
|
||||
/// Does the whole query contains only AND relations?
|
||||
all_and: bool,
|
||||
/// Is every expression backed by an index?
|
||||
all_exp_with_index: bool,
|
||||
}
|
||||
|
||||
|
@ -27,9 +37,11 @@ impl PlanBuilder {
|
|||
return Ok(Plan::TableIterator(Some("WITH NOINDEX".to_string())));
|
||||
}
|
||||
let mut b = PlanBuilder {
|
||||
indexes: Default::default(),
|
||||
range_queries: Default::default(),
|
||||
has_indexes: false,
|
||||
non_range_indexes: Default::default(),
|
||||
groups: Default::default(),
|
||||
with_indexes,
|
||||
all_and_groups: Default::default(),
|
||||
all_and: true,
|
||||
all_exp_with_index: true,
|
||||
};
|
||||
|
@ -37,8 +49,8 @@ impl PlanBuilder {
|
|||
if let Err(e) = b.eval_node(&root) {
|
||||
return Ok(Plan::TableIterator(Some(e.to_string())));
|
||||
}
|
||||
// If we didn't found any index, we're done with no index plan
|
||||
if b.indexes.is_empty() {
|
||||
// If we didn't find any index, we're done with no index plan
|
||||
if !b.has_indexes {
|
||||
return Ok(Plan::TableIterator(Some("NO INDEX FOUND".to_string())));
|
||||
}
|
||||
|
||||
|
@ -46,17 +58,27 @@ impl PlanBuilder {
|
|||
if b.all_and {
|
||||
// TODO: This is currently pretty arbitrary
|
||||
// We take the "first" range query if one is available
|
||||
if let Some((ir, rq)) = b.range_queries.drain().take(1).next() {
|
||||
return Ok(Plan::SingleIndexMultiExpression(ir, rq));
|
||||
if let Some((_, group)) = b.groups.into_iter().next() {
|
||||
if let Some((ir, rq)) = group.take_first_range() {
|
||||
return Ok(Plan::SingleIndexRange(ir, rq));
|
||||
}
|
||||
}
|
||||
// Otherwise we take the first single index option
|
||||
if let Some((e, i)) = b.indexes.pop() {
|
||||
if let Some((e, i)) = b.non_range_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));
|
||||
else if b.all_exp_with_index {
|
||||
let mut ranges = Vec::with_capacity(b.groups.len());
|
||||
for (depth, group) in b.groups {
|
||||
if b.all_and_groups.get(&depth) == Some(&true) {
|
||||
group.take_union_ranges(&mut ranges);
|
||||
} else {
|
||||
group.take_intersect_ranges(&mut ranges);
|
||||
}
|
||||
}
|
||||
return Ok(Plan::MultiIndex(b.non_range_indexes, ranges));
|
||||
}
|
||||
Ok(Plan::TableIterator(None))
|
||||
}
|
||||
|
@ -74,17 +96,15 @@ impl PlanBuilder {
|
|||
fn eval_node(&mut self, node: &Node) -> Result<(), String> {
|
||||
match node {
|
||||
Node::Expression {
|
||||
group,
|
||||
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());
|
||||
let is_bool = self.check_boolean_operator(*group, exp.operator());
|
||||
if let Some(io) = self.filter_index_option(io.as_ref()) {
|
||||
self.add_index_option(exp.clone(), io);
|
||||
self.add_index_option(*group, exp.clone(), io);
|
||||
} else if self.all_exp_with_index && !is_bool {
|
||||
self.all_exp_with_index = false;
|
||||
}
|
||||
|
@ -97,41 +117,49 @@ impl PlanBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_boolean_operator(&mut self, op: &Operator) -> bool {
|
||||
fn check_boolean_operator(&mut self, gr: GroupRef, op: &Operator) -> bool {
|
||||
match op {
|
||||
Operator::Neg | Operator::Or => {
|
||||
if self.all_and {
|
||||
self.all_and = false;
|
||||
}
|
||||
self.all_and_groups.entry(gr).and_modify(|b| *b = false).or_insert(false);
|
||||
true
|
||||
}
|
||||
Operator::And => true,
|
||||
_ => false,
|
||||
Operator::And => {
|
||||
self.all_and_groups.entry(gr).or_insert(true);
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
self.all_and_groups.entry(gr).or_insert(true);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_index_option(&mut self, exp: Arc<Expression>, io: IndexOption) {
|
||||
if let IndexOperator::RangePart(o, v) = io.op() {
|
||||
match self.range_queries.entry(io.ix_ref()) {
|
||||
fn add_index_option(&mut self, group_ref: GroupRef, exp: Arc<Expression>, io: IndexOption) {
|
||||
if let IndexOperator::RangePart(_, _) = io.op() {
|
||||
let level = self.groups.entry(group_ref).or_default();
|
||||
match level.ranges.entry(io.ix_ref()) {
|
||||
Entry::Occupied(mut e) => {
|
||||
e.get_mut().add(exp.clone(), o, v);
|
||||
e.get_mut().push((exp, io));
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
let mut b = RangeQueryBuilder::default();
|
||||
b.add(exp.clone(), o, v);
|
||||
e.insert(b);
|
||||
e.insert(vec![(exp, io)]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.non_range_indexes.push((exp, io));
|
||||
}
|
||||
self.indexes.push((exp, io));
|
||||
self.has_indexes = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum Plan {
|
||||
TableIterator(Option<String>),
|
||||
SingleIndex(Arc<Expression>, IndexOption),
|
||||
MultiIndex(Vec<(Arc<Expression>, IndexOption)>),
|
||||
SingleIndexMultiExpression(IndexRef, RangeQueryBuilder),
|
||||
MultiIndex(Vec<(Arc<Expression>, IndexOption)>, Vec<(IndexRef, UnionRangeQueryBuilder)>),
|
||||
SingleIndexRange(IndexRef, UnionRangeQueryBuilder),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
|
@ -284,24 +312,80 @@ impl From<&RangeValue> for Value {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Group {
|
||||
ranges: HashMap<IndexRef, Vec<(Arc<Expression>, IndexOption)>>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
fn take_first_range(self) -> Option<(IndexRef, UnionRangeQueryBuilder)> {
|
||||
if let Some((ir, ri)) = self.ranges.into_iter().take(1).next() {
|
||||
UnionRangeQueryBuilder::new_aggregate(ri).map(|rb| (ir, rb))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn take_union_ranges(self, r: &mut Vec<(IndexRef, UnionRangeQueryBuilder)>) {
|
||||
for (ir, ri) in self.ranges {
|
||||
if let Some(rb) = UnionRangeQueryBuilder::new_aggregate(ri) {
|
||||
r.push((ir, rb));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn take_intersect_ranges(self, r: &mut Vec<(IndexRef, UnionRangeQueryBuilder)>) {
|
||||
for (ir, ri) in self.ranges {
|
||||
for (exp, io) in ri {
|
||||
if let Some(rb) = UnionRangeQueryBuilder::new(exp, io) {
|
||||
r.push((ir, rb));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(super) struct RangeQueryBuilder {
|
||||
pub(super) struct UnionRangeQueryBuilder {
|
||||
pub(super) exps: HashSet<Arc<Expression>>,
|
||||
pub(super) from: RangeValue,
|
||||
pub(super) to: RangeValue,
|
||||
}
|
||||
|
||||
impl RangeQueryBuilder {
|
||||
fn add(&mut self, exp: Arc<Expression>, op: &Operator, v: &Value) {
|
||||
impl UnionRangeQueryBuilder {
|
||||
fn new_aggregate(exp_ios: Vec<(Arc<Expression>, IndexOption)>) -> Option<Self> {
|
||||
if exp_ios.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut b = Self::default();
|
||||
for (exp, io) in exp_ios {
|
||||
b.add(exp, io);
|
||||
}
|
||||
Some(b)
|
||||
}
|
||||
|
||||
fn new(exp: Arc<Expression>, io: IndexOption) -> Option<Self> {
|
||||
let mut b = Self::default();
|
||||
if b.add(exp, io) {
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, exp: Arc<Expression>, io: IndexOption) -> bool {
|
||||
if let IndexOperator::RangePart(op, val) = io.op() {
|
||||
match op {
|
||||
Operator::LessThan => self.to.set_to(v),
|
||||
Operator::LessThanOrEqual => self.to.set_to_inclusive(v),
|
||||
Operator::MoreThan => self.from.set_from(v),
|
||||
Operator::MoreThanOrEqual => self.from.set_from_inclusive(v),
|
||||
_ => return,
|
||||
Operator::LessThan => self.to.set_to(val),
|
||||
Operator::LessThanOrEqual => self.to.set_to_inclusive(val),
|
||||
Operator::MoreThan => self.from.set_from(val),
|
||||
Operator::MoreThanOrEqual => self.from.set_from_inclusive(val),
|
||||
_ => return false,
|
||||
}
|
||||
self.exps.insert(exp);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -12,7 +12,12 @@ use async_recursion::async_recursion;
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(super) struct Tree {}
|
||||
pub(super) struct Tree {
|
||||
pub(super) root: Node,
|
||||
pub(super) index_map: IndexesMap,
|
||||
pub(super) with_indexes: Vec<IndexRef>,
|
||||
pub(super) knn_expressions: KnnExpressions,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
/// Traverse all the conditions and extract every expression
|
||||
|
@ -24,11 +29,16 @@ impl Tree {
|
|||
table: &'a Table,
|
||||
cond: &'a Option<Cond>,
|
||||
with: &'a Option<With>,
|
||||
) -> Result<Option<(Node, IndexesMap, Vec<IndexRef>, KnnExpressions)>, Error> {
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let mut b = TreeBuilder::new(ctx, opt, txn, table, with);
|
||||
if let Some(cond) = cond {
|
||||
let node = b.eval_value(&cond.0).await?;
|
||||
Ok(Some((node, b.index_map, b.with_indexes, b.knn_expressions)))
|
||||
let root = b.eval_value(0, &cond.0).await?;
|
||||
Ok(Some(Self {
|
||||
root,
|
||||
index_map: b.index_map,
|
||||
with_indexes: b.with_indexes,
|
||||
knn_expressions: b.knn_expressions,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -48,6 +58,7 @@ struct TreeBuilder<'a> {
|
|||
index_map: IndexesMap,
|
||||
with_indexes: Vec<IndexRef>,
|
||||
knn_expressions: KnnExpressions,
|
||||
group_sequence: GroupRef,
|
||||
}
|
||||
|
||||
impl<'a> TreeBuilder<'a> {
|
||||
|
@ -75,8 +86,10 @@ impl<'a> TreeBuilder<'a> {
|
|||
index_map: Default::default(),
|
||||
with_indexes,
|
||||
knn_expressions: Default::default(),
|
||||
group_sequence: 0,
|
||||
}
|
||||
}
|
||||
|
||||
async fn lazy_cache_indexes(&mut self) -> Result<(), Error> {
|
||||
if self.indexes.is_none() {
|
||||
let indexes = self
|
||||
|
@ -93,10 +106,10 @@ impl<'a> TreeBuilder<'a> {
|
|||
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_recursion)]
|
||||
#[cfg_attr(target_arch = "wasm32", async_recursion(?Send))]
|
||||
async fn eval_value(&mut self, v: &Value) -> Result<Node, Error> {
|
||||
async fn eval_value(&mut self, group: GroupRef, v: &Value) -> Result<Node, Error> {
|
||||
match v {
|
||||
Value::Expression(e) => self.eval_expression(e).await,
|
||||
Value::Idiom(i) => self.eval_idiom(i).await,
|
||||
Value::Expression(e) => self.eval_expression(group, e).await,
|
||||
Value::Idiom(i) => self.eval_idiom(group, i).await,
|
||||
Value::Strand(_)
|
||||
| Value::Number(_)
|
||||
| Value::Bool(_)
|
||||
|
@ -110,7 +123,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
Value::Subquery(s) => self.eval_subquery(s).await,
|
||||
Value::Param(p) => {
|
||||
let v = p.compute(self.ctx, self.opt, self.txn, None).await?;
|
||||
self.eval_value(&v).await
|
||||
self.eval_value(group, &v).await
|
||||
}
|
||||
_ => Ok(Node::Unsupported(format!("Unsupported value: {}", v))),
|
||||
}
|
||||
|
@ -124,7 +137,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
Ok(Node::Computed(Arc::new(Value::Array(Array::from(values)))))
|
||||
}
|
||||
|
||||
async fn eval_idiom(&mut self, i: &Idiom) -> Result<Node, Error> {
|
||||
async fn eval_idiom(&mut self, group: GroupRef, i: &Idiom) -> Result<Node, Error> {
|
||||
// Check if the idiom has already been resolved
|
||||
if let Some(i) = self.resolved_idioms.get(i) {
|
||||
if let Some(Some(irs)) = self.idioms_indexes.get(i).cloned() {
|
||||
|
@ -137,7 +150,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
if let Some(Part::Start(x)) = i.0.first() {
|
||||
if x.is_param() {
|
||||
let v = i.compute(self.ctx, self.opt, self.txn, None).await?;
|
||||
return self.eval_value(&v).await;
|
||||
return self.eval_value(group, &v).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +192,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
res
|
||||
}
|
||||
|
||||
async fn eval_expression(&mut self, e: &Expression) -> Result<Node, Error> {
|
||||
async fn eval_expression(&mut self, group: GroupRef, e: &Expression) -> Result<Node, Error> {
|
||||
match e {
|
||||
Expression::Unary {
|
||||
..
|
||||
|
@ -194,8 +207,8 @@ impl<'a> TreeBuilder<'a> {
|
|||
return Ok(re.into());
|
||||
}
|
||||
let exp = Arc::new(e.clone());
|
||||
let left = Arc::new(self.eval_value(l).await?);
|
||||
let right = Arc::new(self.eval_value(r).await?);
|
||||
let left = Arc::new(self.eval_value(group, l).await?);
|
||||
let right = Arc::new(self.eval_value(group, r).await?);
|
||||
let mut io = None;
|
||||
if let Some((id, irs)) = left.is_indexed_field() {
|
||||
io = self.lookup_index_option(
|
||||
|
@ -221,6 +234,7 @@ impl<'a> TreeBuilder<'a> {
|
|||
self.eval_knn(id, &left, &exp)?;
|
||||
}
|
||||
let re = ResolvedExpression {
|
||||
group,
|
||||
exp: exp.clone(),
|
||||
io: io.clone(),
|
||||
left: left.clone(),
|
||||
|
@ -337,8 +351,9 @@ impl<'a> TreeBuilder<'a> {
|
|||
}
|
||||
|
||||
async fn eval_subquery(&mut self, s: &Subquery) -> Result<Node, Error> {
|
||||
self.group_sequence += 1;
|
||||
match s {
|
||||
Subquery::Value(v) => self.eval_value(v).await,
|
||||
Subquery::Value(v) => self.eval_value(self.group_sequence, v).await,
|
||||
_ => Ok(Node::Unsupported(format!("Unsupported subquery: {}", s))),
|
||||
}
|
||||
}
|
||||
|
@ -352,9 +367,12 @@ pub(super) struct IndexesMap {
|
|||
pub(super) definitions: Vec<DefineIndexStatement>,
|
||||
}
|
||||
|
||||
pub(super) type GroupRef = u16;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub(super) enum Node {
|
||||
Expression {
|
||||
group: GroupRef,
|
||||
io: Option<IndexOption>,
|
||||
left: Arc<Node>,
|
||||
right: Arc<Node>,
|
||||
|
@ -398,7 +416,7 @@ enum IdiomPosition {
|
|||
Right,
|
||||
}
|
||||
impl IdiomPosition {
|
||||
// Reverses the operator for non commutative operators
|
||||
// Reverses the operator for non-commutative operators
|
||||
fn transform(&self, op: &Operator) -> Operator {
|
||||
match self {
|
||||
IdiomPosition::Left => op.clone(),
|
||||
|
@ -415,6 +433,7 @@ impl IdiomPosition {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct ResolvedExpression {
|
||||
group: GroupRef,
|
||||
exp: Arc<Expression>,
|
||||
io: Option<IndexOption>,
|
||||
left: Arc<Node>,
|
||||
|
@ -423,6 +442,7 @@ struct ResolvedExpression {
|
|||
impl From<ResolvedExpression> for Node {
|
||||
fn from(re: ResolvedExpression) -> Self {
|
||||
Node::Expression {
|
||||
group: re.group,
|
||||
io: re.io,
|
||||
left: re.left,
|
||||
right: re.right,
|
||||
|
|
|
@ -1528,3 +1528,396 @@ async fn select_with_in_operator_uniq_index() -> Result<(), Error> {
|
|||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn select_with_in_operator_multiple_indexes() -> Result<(), Error> {
|
||||
let dbs = new_ds().await?;
|
||||
let ses = Session::owner().with_ns("test").with_db("test");
|
||||
|
||||
let sql = r#"
|
||||
DEFINE INDEX index_note_id ON TABLE notes COLUMNS id;
|
||||
DEFINE INDEX index_note_kind ON TABLE notes COLUMNS kind;
|
||||
DEFINE INDEX index_note_pubkey ON TABLE notes COLUMNS pubkey;
|
||||
DEFINE INDEX index_note_published ON TABLE notes COLUMNS published;
|
||||
CREATE notes:1 SET kind = 1, pubkey = 123, published=2021;
|
||||
CREATE notes:2 SET kind = 2, pubkey = 123, published=2022;
|
||||
CREATE notes:3 SET kind = 1, pubkey = 123, published=2023;
|
||||
CREATE notes:4 SET kind = 2, pubkey = 123, published=2024;
|
||||
CREATE notes:5 SET kind = 1, pubkey = 123, published=2025;
|
||||
SELECT * FROM notes WHERE (kind IN [1,2] OR pubkey IN [123]) AND published > 2022 EXPLAIN;
|
||||
SELECT * FROM notes WHERE (kind IN [1,2] OR pubkey IN [123]) AND published > 2022;
|
||||
SELECT * FROM notes WHERE published < 2024 AND (kind IN [1,2] OR pubkey IN [123]) AND published > 2022 EXPLAIN;
|
||||
SELECT * FROM notes WHERE published < 2024 AND (kind IN [1,2] OR pubkey IN [123]) AND published > 2022;
|
||||
SELECT * FROM notes WHERE published < 2022 OR (kind IN [1,2] OR pubkey IN [123]) AND published > 2022 EXPLAIN;
|
||||
SELECT * FROM notes WHERE published < 2022 OR (kind IN [1,2] OR pubkey IN [123]) AND published > 2022;
|
||||
SELECT * FROM notes WHERE (kind IN [1,2] AND published < 2022) OR (pubkey IN [123] AND published > 2022) EXPLAIN;
|
||||
SELECT * FROM notes WHERE (kind IN [1,2] AND published < 2022) OR (pubkey IN [123] AND published > 2022);
|
||||
"#;
|
||||
let mut res = dbs.execute(sql, &ses, None).await?;
|
||||
//
|
||||
assert_eq!(res.len(), 17);
|
||||
skip_ok(&mut res, 9)?;
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_kind",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_pubkey",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
123
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
"detail": {
|
||||
plan: {
|
||||
from: {
|
||||
inclusive: false,
|
||||
value: 2022
|
||||
},
|
||||
index: 'index_note_published',
|
||||
to: {
|
||||
inclusive: false,
|
||||
value: None
|
||||
}
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
type: 'Memory'
|
||||
},
|
||||
operation: 'Collector'
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
id: notes:3,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2023
|
||||
},
|
||||
{
|
||||
id: notes:5,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2025
|
||||
},
|
||||
{
|
||||
id: notes:4,
|
||||
kind: 2,
|
||||
pubkey: 123,
|
||||
published: 2024
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_kind",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_pubkey",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
123
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
plan: {
|
||||
from: {
|
||||
inclusive: false,
|
||||
value: 2022
|
||||
},
|
||||
index: 'index_note_published',
|
||||
to: {
|
||||
inclusive: false,
|
||||
value: 2024
|
||||
}
|
||||
},
|
||||
table: 'notes'
|
||||
},
|
||||
operation: 'Iterate Index'
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
type: 'Memory'
|
||||
},
|
||||
operation: 'Collector'
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
id: notes:3,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2023
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_kind",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_pubkey",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
123
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
plan: {
|
||||
from: {
|
||||
inclusive: false,
|
||||
value: None
|
||||
},
|
||||
index: 'index_note_published',
|
||||
to: {
|
||||
inclusive: false,
|
||||
value: 2022
|
||||
}
|
||||
},
|
||||
table: 'notes'
|
||||
},
|
||||
operation: 'Iterate Index'
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
plan: {
|
||||
from: {
|
||||
inclusive: false,
|
||||
value: 2022
|
||||
},
|
||||
index: 'index_note_published',
|
||||
to: {
|
||||
inclusive: false,
|
||||
value: None
|
||||
}
|
||||
},
|
||||
table: 'notes'
|
||||
},
|
||||
operation: 'Iterate Index'
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
type: 'Memory'
|
||||
},
|
||||
operation: 'Collector'
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
id: notes:1,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2021
|
||||
},
|
||||
{
|
||||
id: notes:3,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2023
|
||||
},
|
||||
{
|
||||
id: notes:5,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2025
|
||||
},
|
||||
{
|
||||
id: notes:4,
|
||||
kind: 2,
|
||||
pubkey: 123,
|
||||
published: 2024
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_kind",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
"detail": {
|
||||
"plan": {
|
||||
"index": "index_note_pubkey",
|
||||
"operator": "union",
|
||||
"value": [
|
||||
123
|
||||
]
|
||||
},
|
||||
"table": "notes"
|
||||
},
|
||||
"operation": "Iterate Index"
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
plan: {
|
||||
from: {
|
||||
inclusive: false,
|
||||
value: None
|
||||
},
|
||||
index: 'index_note_published',
|
||||
to: {
|
||||
inclusive: false,
|
||||
value: 2022
|
||||
}
|
||||
},
|
||||
table: 'notes'
|
||||
},
|
||||
operation: 'Iterate Index'
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
plan: {
|
||||
from: {
|
||||
inclusive: false,
|
||||
value: 2022
|
||||
},
|
||||
index: 'index_note_published',
|
||||
to: {
|
||||
inclusive: false,
|
||||
value: None
|
||||
}
|
||||
},
|
||||
table: 'notes'
|
||||
},
|
||||
operation: 'Iterate Index'
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
type: 'Memory'
|
||||
},
|
||||
operation: 'Collector'
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
//
|
||||
let tmp = res.remove(0).result?;
|
||||
let val = Value::parse(
|
||||
r#"[
|
||||
{
|
||||
id: notes:1,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2021
|
||||
},
|
||||
{
|
||||
id: notes:3,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2023
|
||||
},
|
||||
{
|
||||
id: notes:5,
|
||||
kind: 1,
|
||||
pubkey: 123,
|
||||
published: 2025
|
||||
},
|
||||
{
|
||||
id: notes:4,
|
||||
kind: 2,
|
||||
pubkey: 123,
|
||||
published: 2024
|
||||
}
|
||||
]"#,
|
||||
);
|
||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue