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 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)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -266,20 +266,7 @@ 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,
|
||||||
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())),
|
|
||||||
Index::Uniq => Ok(Self::new_unique_index_iterator(opt, ix, io.clone())),
|
|
||||||
Index::Search {
|
|
||||||
..
|
|
||||||
} => self.new_search_index_iterator(it_ref, io.clone()).await,
|
|
||||||
Index::MTree(_) => Ok(self.new_mtree_index_knn_iterator(it_ref)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IteratorEntry::Range(_, ir, from, to) => {
|
IteratorEntry::Range(_, ir, from, to) => {
|
||||||
Ok(self.new_range_iterator(opt, *ir, from, to))
|
Ok(self.new_range_iterator(opt, *ir, from, to))
|
||||||
}
|
}
|
||||||
|
@ -289,6 +276,26 @@ impl QueryExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())),
|
||||||
|
Index::Uniq => Ok(Self::new_unique_index_iterator(opt, ix, io.clone())),
|
||||||
|
Index::Search {
|
||||||
|
..
|
||||||
|
} => self.new_search_index_iterator(it_ref, io.clone()).await,
|
||||||
|
Index::MTree(_) => Ok(self.new_mtree_index_knn_iterator(it_ref)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_index_iterator(
|
fn new_index_iterator(
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
ix: &DefineIndexStatement,
|
ix: &DefineIndexStatement,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,23 +312,79 @@ 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> {
|
||||||
match op {
|
if exp_ios.is_empty() {
|
||||||
Operator::LessThan => self.to.set_to(v),
|
return None;
|
||||||
Operator::LessThanOrEqual => self.to.set_to_inclusive(v),
|
|
||||||
Operator::MoreThan => self.from.set_from(v),
|
|
||||||
Operator::MoreThanOrEqual => self.from.set_from_inclusive(v),
|
|
||||||
_ => return,
|
|
||||||
}
|
}
|
||||||
self.exps.insert(exp);
|
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(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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue