Bug fix: Improve support of range queries with complex queries (#3786)

This commit is contained in:
Emmanuel Keller 2024-03-30 23:11:54 +00:00 committed by GitHub
parent c82bbc0820
commit 829fb0baf9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 613 additions and 94 deletions

View file

@ -19,18 +19,19 @@ use crate::sql::thing::Thing;
use crate::sql::value::Value; use crate::sql::value::Value;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use std::mem; use std::mem;
use std::sync::Arc;
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum Iterable { pub(crate) enum Iterable {
Value(Value), Value(Value),
Table(Table), Table(Arc<Table>),
Thing(Thing), Thing(Thing),
Range(Range), Range(Range),
Edges(Edges), Edges(Edges),
Defer(Thing), Defer(Thing),
Mergeable(Thing, Value), Mergeable(Thing, Value),
Relatable(Thing, Thing, Thing), Relatable(Thing, Thing, Thing),
Index(Table, IteratorRef), Index(Arc<Table>, IteratorRef),
} }
pub(crate) struct Processed { pub(crate) struct Processed {
@ -117,7 +118,7 @@ impl Iterator {
} }
_ => { _ => {
// Ingest the table for scanning // 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 // There is no data clause so create a record id
@ -128,7 +129,7 @@ impl Iterator {
} }
_ => { _ => {
// Ingest the table for scanning // Ingest the table for scanning
self.ingest(Iterable::Table(v)) self.ingest(Iterable::Table(Arc::new(v)))
} }
}, },
}, },

View file

@ -127,10 +127,10 @@ impl<'a> Processor<'a> {
// Avoiding search in the hashmap of the query planner for each doc // Avoiding search in the hashmap of the query planner for each doc
let mut ctx = Context::new(ctx); let mut ctx = Context::new(ctx);
ctx.set_query_executor(exe.clone()); 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::Range(v) => self.process_range(ctx, opt, txn, stm, v).await?,
Iterable::Edges(e) => self.process_edge(ctx, opt, txn, stm, e).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 // Avoiding search in the hashmap of the query planner for each doc
let mut ctx = Context::new(ctx); let mut ctx = Context::new(ctx);
ctx.set_query_executor(exe.clone()); 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) => { Iterable::Mergeable(v, o) => {
self.process_mergeable(ctx, opt, txn, stm, v, o).await? self.process_mergeable(ctx, opt, txn, stm, v, o).await?
@ -302,13 +302,13 @@ impl<'a> Processor<'a> {
opt: &Options, opt: &Options,
txn: &Transaction, txn: &Transaction,
stm: &Statement<'_>, stm: &Statement<'_>,
v: Table, v: &Table,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Check that the table exists // 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 // Prepare the start and end keys
let beg = thing::prefix(opt.ns(), opt.db(), &v); let beg = thing::prefix(opt.ns(), opt.db(), v);
let end = thing::suffix(opt.ns(), opt.db(), &v); let end = thing::suffix(opt.ns(), opt.db(), v);
// Loop until no more keys // Loop until no more keys
let mut next_page = Some(ScanPage::from(beg..end)); let mut next_page = Some(ScanPage::from(beg..end));
while let Some(page) = next_page { while let Some(page) = next_page {
@ -556,7 +556,7 @@ impl<'a> Processor<'a> {
opt: &Options, opt: &Options,
txn: &Transaction, txn: &Transaction,
stm: &Statement<'_>, stm: &Statement<'_>,
table: Table, table: &Table,
ir: IteratorRef, ir: IteratorRef,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Check that the table exists // Check that the table exists

View file

@ -266,7 +266,22 @@ impl QueryExecutor {
) -> Result<Option<ThingIterator>, Error> { ) -> Result<Option<ThingIterator>, Error> {
if let Some(it_entry) = self.0.it_entries.get(it_ref as usize) { if let Some(it_entry) = self.0.it_entries.get(it_ref as usize) {
match it_entry { 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) { if let Some(ix) = self.0.index_definitions.get(io.ix_ref() as usize) {
match ix.index { match ix.index {
Index::Idx => Ok(Self::new_index_iterator(opt, ix, io.clone())), Index::Idx => Ok(Self::new_index_iterator(opt, ix, io.clone())),
@ -280,14 +295,6 @@ impl QueryExecutor {
Ok(None) Ok(None)
} }
} }
IteratorEntry::Range(_, ir, from, to) => {
Ok(self.new_range_iterator(opt, *ir, from, to))
}
}
} else {
Ok(None)
}
}
fn new_index_iterator( fn new_index_iterator(
opt: &Options, opt: &Options,

View file

@ -53,12 +53,20 @@ impl<'a> QueryPlanner<'a> {
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut is_table_iterator = false; let mut is_table_iterator = false;
let mut is_knn = false; let mut is_knn = false;
let t = Arc::new(t);
match Tree::build(ctx, self.opt, txn, &t, self.cond, self.with).await? { match Tree::build(ctx, self.opt, txn, &t, self.cond, self.with).await? {
Some((node, im, with_indexes, knn_expressions)) => { Some(tree) => {
is_knn = is_knn || !knn_expressions.is_empty(); is_knn = is_knn || !tree.knn_expressions.is_empty();
let mut exe = let mut exe = InnerQueryExecutor::new(
InnerQueryExecutor::new(ctx, self.opt, txn, &t, im, knn_expressions).await?; ctx,
match PlanBuilder::build(node, self.with, with_indexes)? { 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) => { Plan::SingleIndex(exp, io) => {
if io.require_distinct() { if io.require_distinct() {
self.requires_distinct = true; self.requires_distinct = true;
@ -66,15 +74,21 @@ impl<'a> QueryPlanner<'a> {
let ir = exe.add_iterator(IteratorEntry::Single(exp, io)); let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
self.add(t.clone(), Some(ir), exe, it); self.add(t.clone(), Some(ir), exe, it);
} }
Plan::MultiIndex(v) => { Plan::MultiIndex(non_range_indexes, ranges_indexes) => {
for (exp, io) in v { for (exp, io) in non_range_indexes {
let ir = exe.add_iterator(IteratorEntry::Single(exp, io)); let ie = IteratorEntry::Single(exp, io);
let ir = exe.add_iterator(ie);
it.ingest(Iterable::Index(t.clone(), ir)); 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); self.add(t.clone(), None, exe, it);
} }
Plan::SingleIndexMultiExpression(ixn, rq) => { Plan::SingleIndexRange(ixn, rq) => {
let ir = let ir =
exe.add_iterator(IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to)); exe.add_iterator(IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to));
self.add(t.clone(), Some(ir), exe, it); self.add(t.clone(), Some(ir), exe, it);
@ -103,7 +117,7 @@ impl<'a> QueryPlanner<'a> {
fn add( fn add(
&mut self, &mut self,
tb: Table, tb: Arc<Table>,
irf: Option<IteratorRef>, irf: Option<IteratorRef>,
exe: InnerQueryExecutor, exe: InnerQueryExecutor,
it: &mut Iterator, it: &mut Iterator,

View file

@ -1,19 +1,29 @@
use crate::err::Error; use crate::err::Error;
use crate::idx::ft::MatchRef; 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::with::With;
use crate::sql::{Array, Idiom, Object}; use crate::sql::{Array, Idiom, Object};
use crate::sql::{Expression, Operator, Value}; use crate::sql::{Expression, Operator, Value};
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
/// The `PlanBuilder` struct represents a builder for constructing query plans.
pub(super) struct PlanBuilder { pub(super) struct PlanBuilder {
indexes: Vec<(Arc<Expression>, IndexOption)>, /// Do we have at least one index?
range_queries: HashMap<IndexRef, RangeQueryBuilder>, 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>, 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, all_and: bool,
/// Is every expression backed by an index?
all_exp_with_index: bool, all_exp_with_index: bool,
} }
@ -27,9 +37,11 @@ impl PlanBuilder {
return Ok(Plan::TableIterator(Some("WITH NOINDEX".to_string()))); return Ok(Plan::TableIterator(Some("WITH NOINDEX".to_string())));
} }
let mut b = PlanBuilder { let mut b = PlanBuilder {
indexes: Default::default(), has_indexes: false,
range_queries: Default::default(), non_range_indexes: Default::default(),
groups: Default::default(),
with_indexes, with_indexes,
all_and_groups: Default::default(),
all_and: true, all_and: true,
all_exp_with_index: true, all_exp_with_index: true,
}; };
@ -37,8 +49,8 @@ impl PlanBuilder {
if let Err(e) = b.eval_node(&root) { if let Err(e) = b.eval_node(&root) {
return Ok(Plan::TableIterator(Some(e.to_string()))); return Ok(Plan::TableIterator(Some(e.to_string())));
} }
// If we didn't found any index, we're done with no index plan // If we didn't find any index, we're done with no index plan
if b.indexes.is_empty() { if !b.has_indexes {
return Ok(Plan::TableIterator(Some("NO INDEX FOUND".to_string()))); return Ok(Plan::TableIterator(Some("NO INDEX FOUND".to_string())));
} }
@ -46,17 +58,27 @@ impl PlanBuilder {
if b.all_and { if b.all_and {
// TODO: This is currently pretty arbitrary // TODO: This is currently pretty arbitrary
// We take the "first" range query if one is available // We take the "first" range query if one is available
if let Some((ir, rq)) = b.range_queries.drain().take(1).next() { if let Some((_, group)) = b.groups.into_iter().next() {
return Ok(Plan::SingleIndexMultiExpression(ir, rq)); if let Some((ir, rq)) = group.take_first_range() {
return Ok(Plan::SingleIndexRange(ir, rq));
}
} }
// Otherwise we take the first single index option // 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)); return Ok(Plan::SingleIndex(e, i));
} }
} }
// If every expression is backed by an index with can use the MultiIndex plan // If every expression is backed by an index with can use the MultiIndex plan
if b.all_exp_with_index { else if b.all_exp_with_index {
return Ok(Plan::MultiIndex(b.indexes)); 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)) Ok(Plan::TableIterator(None))
} }
@ -74,17 +96,15 @@ impl PlanBuilder {
fn eval_node(&mut self, node: &Node) -> Result<(), String> { fn eval_node(&mut self, node: &Node) -> Result<(), String> {
match node { match node {
Node::Expression { Node::Expression {
group,
io, io,
left, left,
right, right,
exp, exp,
} => { } => {
if self.all_and && Operator::Or.eq(exp.operator()) { let is_bool = self.check_boolean_operator(*group, exp.operator());
self.all_and = false;
}
let is_bool = self.check_boolean_operator(exp.operator());
if let Some(io) = self.filter_index_option(io.as_ref()) { 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 { } else if self.all_exp_with_index && !is_bool {
self.all_exp_with_index = false; 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 { match op {
Operator::Neg | Operator::Or => { Operator::Neg | Operator::Or => {
if self.all_and { if self.all_and {
self.all_and = false; self.all_and = false;
} }
self.all_and_groups.entry(gr).and_modify(|b| *b = false).or_insert(false);
true true
} }
Operator::And => true, Operator::And => {
_ => false, 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) { fn add_index_option(&mut self, group_ref: GroupRef, exp: Arc<Expression>, io: IndexOption) {
if let IndexOperator::RangePart(o, v) = io.op() { if let IndexOperator::RangePart(_, _) = io.op() {
match self.range_queries.entry(io.ix_ref()) { let level = self.groups.entry(group_ref).or_default();
match level.ranges.entry(io.ix_ref()) {
Entry::Occupied(mut e) => { Entry::Occupied(mut e) => {
e.get_mut().add(exp.clone(), o, v); e.get_mut().push((exp, io));
} }
Entry::Vacant(e) => { Entry::Vacant(e) => {
let mut b = RangeQueryBuilder::default(); e.insert(vec![(exp, io)]);
b.add(exp.clone(), o, v);
e.insert(b);
} }
} }
} else {
self.non_range_indexes.push((exp, io));
} }
self.indexes.push((exp, io)); self.has_indexes = true;
} }
} }
pub(super) enum Plan { pub(super) enum Plan {
TableIterator(Option<String>), TableIterator(Option<String>),
SingleIndex(Arc<Expression>, IndexOption), SingleIndex(Arc<Expression>, IndexOption),
MultiIndex(Vec<(Arc<Expression>, IndexOption)>), MultiIndex(Vec<(Arc<Expression>, IndexOption)>, Vec<(IndexRef, UnionRangeQueryBuilder)>),
SingleIndexMultiExpression(IndexRef, RangeQueryBuilder), SingleIndexRange(IndexRef, UnionRangeQueryBuilder),
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[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)] #[derive(Default, Debug)]
pub(super) struct RangeQueryBuilder { pub(super) struct UnionRangeQueryBuilder {
pub(super) exps: HashSet<Arc<Expression>>, pub(super) exps: HashSet<Arc<Expression>>,
pub(super) from: RangeValue, pub(super) from: RangeValue,
pub(super) to: RangeValue, pub(super) to: RangeValue,
} }
impl RangeQueryBuilder { impl UnionRangeQueryBuilder {
fn add(&mut self, exp: Arc<Expression>, op: &Operator, v: &Value) { 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 { match op {
Operator::LessThan => self.to.set_to(v), Operator::LessThan => self.to.set_to(val),
Operator::LessThanOrEqual => self.to.set_to_inclusive(v), Operator::LessThanOrEqual => self.to.set_to_inclusive(val),
Operator::MoreThan => self.from.set_from(v), Operator::MoreThan => self.from.set_from(val),
Operator::MoreThanOrEqual => self.from.set_from_inclusive(v), Operator::MoreThanOrEqual => self.from.set_from_inclusive(val),
_ => return, _ => return false,
} }
self.exps.insert(exp); self.exps.insert(exp);
} }
true
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -12,7 +12,12 @@ use async_recursion::async_recursion;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; 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 { impl Tree {
/// Traverse all the conditions and extract every expression /// Traverse all the conditions and extract every expression
@ -24,11 +29,16 @@ impl Tree {
table: &'a Table, table: &'a Table,
cond: &'a Option<Cond>, cond: &'a Option<Cond>,
with: &'a Option<With>, 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); let mut b = TreeBuilder::new(ctx, opt, txn, table, with);
if let Some(cond) = cond { if let Some(cond) = cond {
let node = b.eval_value(&cond.0).await?; let root = b.eval_value(0, &cond.0).await?;
Ok(Some((node, b.index_map, b.with_indexes, b.knn_expressions))) Ok(Some(Self {
root,
index_map: b.index_map,
with_indexes: b.with_indexes,
knn_expressions: b.knn_expressions,
}))
} else { } else {
Ok(None) Ok(None)
} }
@ -48,6 +58,7 @@ struct TreeBuilder<'a> {
index_map: IndexesMap, index_map: IndexesMap,
with_indexes: Vec<IndexRef>, with_indexes: Vec<IndexRef>,
knn_expressions: KnnExpressions, knn_expressions: KnnExpressions,
group_sequence: GroupRef,
} }
impl<'a> TreeBuilder<'a> { impl<'a> TreeBuilder<'a> {
@ -75,8 +86,10 @@ impl<'a> TreeBuilder<'a> {
index_map: Default::default(), index_map: Default::default(),
with_indexes, with_indexes,
knn_expressions: Default::default(), knn_expressions: Default::default(),
group_sequence: 0,
} }
} }
async fn lazy_cache_indexes(&mut self) -> Result<(), Error> { async fn lazy_cache_indexes(&mut self) -> Result<(), Error> {
if self.indexes.is_none() { if self.indexes.is_none() {
let indexes = self let indexes = self
@ -93,10 +106,10 @@ impl<'a> TreeBuilder<'a> {
#[cfg_attr(not(target_arch = "wasm32"), async_recursion)] #[cfg_attr(not(target_arch = "wasm32"), async_recursion)]
#[cfg_attr(target_arch = "wasm32", async_recursion(?Send))] #[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 { match v {
Value::Expression(e) => self.eval_expression(e).await, Value::Expression(e) => self.eval_expression(group, e).await,
Value::Idiom(i) => self.eval_idiom(i).await, Value::Idiom(i) => self.eval_idiom(group, i).await,
Value::Strand(_) Value::Strand(_)
| Value::Number(_) | Value::Number(_)
| Value::Bool(_) | Value::Bool(_)
@ -110,7 +123,7 @@ impl<'a> TreeBuilder<'a> {
Value::Subquery(s) => self.eval_subquery(s).await, Value::Subquery(s) => self.eval_subquery(s).await,
Value::Param(p) => { Value::Param(p) => {
let v = p.compute(self.ctx, self.opt, self.txn, None).await?; 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))), _ => 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))))) 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 // Check if the idiom has already been resolved
if let Some(i) = self.resolved_idioms.get(i) { if let Some(i) = self.resolved_idioms.get(i) {
if let Some(Some(irs)) = self.idioms_indexes.get(i).cloned() { 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 let Some(Part::Start(x)) = i.0.first() {
if x.is_param() { if x.is_param() {
let v = i.compute(self.ctx, self.opt, self.txn, None).await?; 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 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 { match e {
Expression::Unary { Expression::Unary {
.. ..
@ -194,8 +207,8 @@ impl<'a> TreeBuilder<'a> {
return Ok(re.into()); return Ok(re.into());
} }
let exp = Arc::new(e.clone()); let exp = Arc::new(e.clone());
let left = Arc::new(self.eval_value(l).await?); let left = Arc::new(self.eval_value(group, l).await?);
let right = Arc::new(self.eval_value(r).await?); let right = Arc::new(self.eval_value(group, r).await?);
let mut io = None; let mut io = None;
if let Some((id, irs)) = left.is_indexed_field() { if let Some((id, irs)) = left.is_indexed_field() {
io = self.lookup_index_option( io = self.lookup_index_option(
@ -221,6 +234,7 @@ impl<'a> TreeBuilder<'a> {
self.eval_knn(id, &left, &exp)?; self.eval_knn(id, &left, &exp)?;
} }
let re = ResolvedExpression { let re = ResolvedExpression {
group,
exp: exp.clone(), exp: exp.clone(),
io: io.clone(), io: io.clone(),
left: left.clone(), left: left.clone(),
@ -337,8 +351,9 @@ impl<'a> TreeBuilder<'a> {
} }
async fn eval_subquery(&mut self, s: &Subquery) -> Result<Node, Error> { async fn eval_subquery(&mut self, s: &Subquery) -> Result<Node, Error> {
self.group_sequence += 1;
match s { 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))), _ => Ok(Node::Unsupported(format!("Unsupported subquery: {}", s))),
} }
} }
@ -352,9 +367,12 @@ pub(super) struct IndexesMap {
pub(super) definitions: Vec<DefineIndexStatement>, pub(super) definitions: Vec<DefineIndexStatement>,
} }
pub(super) type GroupRef = u16;
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub(super) enum Node { pub(super) enum Node {
Expression { Expression {
group: GroupRef,
io: Option<IndexOption>, io: Option<IndexOption>,
left: Arc<Node>, left: Arc<Node>,
right: Arc<Node>, right: Arc<Node>,
@ -398,7 +416,7 @@ enum IdiomPosition {
Right, Right,
} }
impl IdiomPosition { impl IdiomPosition {
// Reverses the operator for non commutative operators // Reverses the operator for non-commutative operators
fn transform(&self, op: &Operator) -> Operator { fn transform(&self, op: &Operator) -> Operator {
match self { match self {
IdiomPosition::Left => op.clone(), IdiomPosition::Left => op.clone(),
@ -415,6 +433,7 @@ impl IdiomPosition {
#[derive(Clone)] #[derive(Clone)]
struct ResolvedExpression { struct ResolvedExpression {
group: GroupRef,
exp: Arc<Expression>, exp: Arc<Expression>,
io: Option<IndexOption>, io: Option<IndexOption>,
left: Arc<Node>, left: Arc<Node>,
@ -423,6 +442,7 @@ struct ResolvedExpression {
impl From<ResolvedExpression> for Node { impl From<ResolvedExpression> for Node {
fn from(re: ResolvedExpression) -> Self { fn from(re: ResolvedExpression) -> Self {
Node::Expression { Node::Expression {
group: re.group,
io: re.io, io: re.io,
left: re.left, left: re.left,
right: re.right, right: re.right,

View file

@ -1528,3 +1528,396 @@ async fn select_with_in_operator_uniq_index() -> Result<(), Error> {
assert_eq!(format!("{:#}", tmp), format!("{:#}", val)); assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
Ok(()) 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(())
}