Feat: Indexes used with the operators CONTAINS[ANY|ALL] (#2879)
This commit is contained in:
parent
08ac7579d1
commit
b8ff68b464
6 changed files with 492 additions and 119 deletions
|
@ -6,8 +6,8 @@ use crate::idx::ft::termdocs::TermsDocs;
|
||||||
use crate::idx::ft::terms::TermId;
|
use crate::idx::ft::terms::TermId;
|
||||||
use crate::idx::ft::{FtIndex, MatchRef};
|
use crate::idx::ft::{FtIndex, MatchRef};
|
||||||
use crate::idx::planner::iterators::{
|
use crate::idx::planner::iterators::{
|
||||||
IndexEqualThingIterator, IndexRangeThingIterator, KnnThingIterator, MatchesThingIterator,
|
IndexEqualThingIterator, IndexRangeThingIterator, IndexUnionThingIterator, KnnThingIterator,
|
||||||
ThingIterator, UniqueEqualThingIterator, UniqueRangeThingIterator,
|
MatchesThingIterator, ThingIterator, UniqueEqualThingIterator, UniqueRangeThingIterator,
|
||||||
};
|
};
|
||||||
use crate::idx::planner::plan::IndexOperator::Matches;
|
use crate::idx::planner::plan::IndexOperator::Matches;
|
||||||
use crate::idx::planner::plan::{IndexOperator, IndexOption, RangeValue};
|
use crate::idx::planner::plan::{IndexOperator, IndexOption, RangeValue};
|
||||||
|
@ -199,8 +199,8 @@ impl QueryExecutor {
|
||||||
IteratorEntry::Single(_, io) => {
|
IteratorEntry::Single(_, io) => {
|
||||||
if let Some(ix) = self.index_definitions.get(&io.ir()) {
|
if let Some(ix) = self.index_definitions.get(&io.ir()) {
|
||||||
match ix.index {
|
match ix.index {
|
||||||
Index::Idx => Self::new_index_iterator(opt, ix, io.clone()),
|
Index::Idx => Ok(Self::new_index_iterator(opt, ix, io.clone())),
|
||||||
Index::Uniq => Self::new_unique_index_iterator(opt, ix, io.clone()),
|
Index::Uniq => Ok(Self::new_unique_index_iterator(opt, ix, io.clone())),
|
||||||
Index::Search {
|
Index::Search {
|
||||||
..
|
..
|
||||||
} => self.new_search_index_iterator(ir, io.clone()).await,
|
} => self.new_search_index_iterator(ir, io.clone()).await,
|
||||||
|
@ -211,7 +211,7 @@ impl QueryExecutor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,13 +223,15 @@ impl QueryExecutor {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
ix: &DefineIndexStatement,
|
ix: &DefineIndexStatement,
|
||||||
io: IndexOption,
|
io: IndexOption,
|
||||||
) -> Result<Option<ThingIterator>, Error> {
|
) -> Option<ThingIterator> {
|
||||||
match io.op() {
|
match io.op() {
|
||||||
IndexOperator::Equality(array) => {
|
IndexOperator::Equality(value) => {
|
||||||
Ok(Some(ThingIterator::IndexEqual(IndexEqualThingIterator::new(opt, ix, array)?)))
|
Some(ThingIterator::IndexEqual(IndexEqualThingIterator::new(opt, ix, value)))
|
||||||
}
|
}
|
||||||
IndexOperator::RangePart(_, _) => Ok(None), // TODO
|
IndexOperator::Union(value) => {
|
||||||
_ => Ok(None),
|
Some(ThingIterator::IndexUnion(IndexUnionThingIterator::new(opt, ix, value)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,38 +241,35 @@ impl QueryExecutor {
|
||||||
ir: IndexRef,
|
ir: IndexRef,
|
||||||
from: &RangeValue,
|
from: &RangeValue,
|
||||||
to: &RangeValue,
|
to: &RangeValue,
|
||||||
) -> Result<Option<ThingIterator>, Error> {
|
) -> Option<ThingIterator> {
|
||||||
if let Some(ix) = self.index_definitions.get(&ir) {
|
if let Some(ix) = self.index_definitions.get(&ir) {
|
||||||
match ix.index {
|
match ix.index {
|
||||||
Index::Idx => {
|
Index::Idx => {
|
||||||
return Ok(Some(ThingIterator::IndexRange(IndexRangeThingIterator::new(
|
return Some(ThingIterator::IndexRange(IndexRangeThingIterator::new(
|
||||||
opt, ix, from, to,
|
opt, ix, from, to,
|
||||||
))))
|
)))
|
||||||
}
|
}
|
||||||
Index::Uniq => {
|
Index::Uniq => {
|
||||||
return Ok(Some(ThingIterator::UniqueRange(UniqueRangeThingIterator::new(
|
return Some(ThingIterator::UniqueRange(UniqueRangeThingIterator::new(
|
||||||
opt, ix, from, to,
|
opt, ix, from, to,
|
||||||
))))
|
)))
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_unique_index_iterator(
|
fn new_unique_index_iterator(
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
ix: &DefineIndexStatement,
|
ix: &DefineIndexStatement,
|
||||||
io: IndexOption,
|
io: IndexOption,
|
||||||
) -> Result<Option<ThingIterator>, Error> {
|
) -> Option<ThingIterator> {
|
||||||
match io.op() {
|
match io.op() {
|
||||||
IndexOperator::Equality(array) => {
|
IndexOperator::Equality(value) => {
|
||||||
Ok(Some(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new(opt, ix, array)?)))
|
Some(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new(opt, ix, value)))
|
||||||
}
|
}
|
||||||
IndexOperator::RangePart(_, _) => {
|
_ => None,
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
_ => Ok(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ use tokio::sync::RwLock;
|
||||||
pub(crate) enum ThingIterator {
|
pub(crate) enum ThingIterator {
|
||||||
IndexEqual(IndexEqualThingIterator),
|
IndexEqual(IndexEqualThingIterator),
|
||||||
IndexRange(IndexRangeThingIterator),
|
IndexRange(IndexRangeThingIterator),
|
||||||
|
IndexUnion(IndexUnionThingIterator),
|
||||||
UniqueEqual(UniqueEqualThingIterator),
|
UniqueEqual(UniqueEqualThingIterator),
|
||||||
UniqueRange(UniqueRangeThingIterator),
|
UniqueRange(UniqueRangeThingIterator),
|
||||||
Matches(MatchesThingIterator),
|
Matches(MatchesThingIterator),
|
||||||
|
@ -33,6 +34,7 @@ impl ThingIterator {
|
||||||
ThingIterator::UniqueEqual(i) => i.next_batch(tx).await,
|
ThingIterator::UniqueEqual(i) => i.next_batch(tx).await,
|
||||||
ThingIterator::IndexRange(i) => i.next_batch(tx, size).await,
|
ThingIterator::IndexRange(i) => i.next_batch(tx, size).await,
|
||||||
ThingIterator::UniqueRange(i) => i.next_batch(tx, size).await,
|
ThingIterator::UniqueRange(i) => i.next_batch(tx, size).await,
|
||||||
|
ThingIterator::IndexUnion(i) => i.next_batch(tx, size).await,
|
||||||
ThingIterator::Matches(i) => i.next_batch(tx, size).await,
|
ThingIterator::Matches(i) => i.next_batch(tx, size).await,
|
||||||
ThingIterator::Knn(i) => i.next_batch(tx, size).await,
|
ThingIterator::Knn(i) => i.next_batch(tx, size).await,
|
||||||
}
|
}
|
||||||
|
@ -45,13 +47,32 @@ pub(crate) struct IndexEqualThingIterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexEqualThingIterator {
|
impl IndexEqualThingIterator {
|
||||||
pub(super) fn new(opt: &Options, ix: &DefineIndexStatement, v: &Array) -> Result<Self, Error> {
|
pub(super) fn new(opt: &Options, ix: &DefineIndexStatement, v: &Value) -> Self {
|
||||||
let beg = Index::prefix_ids_beg(opt.ns(), opt.db(), &ix.what, &ix.name, v);
|
let a = Array::from(v.clone());
|
||||||
let end = Index::prefix_ids_end(opt.ns(), opt.db(), &ix.what, &ix.name, v);
|
let beg = Index::prefix_ids_beg(opt.ns(), opt.db(), &ix.what, &ix.name, &a);
|
||||||
Ok(Self {
|
let end = Index::prefix_ids_end(opt.ns(), opt.db(), &ix.what, &ix.name, &a);
|
||||||
|
Self {
|
||||||
beg,
|
beg,
|
||||||
end,
|
end,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn next_scan(
|
||||||
|
txn: &Transaction,
|
||||||
|
beg: &mut Vec<u8>,
|
||||||
|
end: &[u8],
|
||||||
|
limit: u32,
|
||||||
|
) -> Result<Vec<(Thing, DocId)>, Error> {
|
||||||
|
let min = beg.clone();
|
||||||
|
let max = end.to_owned();
|
||||||
|
let res = txn.lock().await.scan(min..max, limit).await?;
|
||||||
|
if let Some((key, _)) = res.last() {
|
||||||
|
let mut key = key.clone();
|
||||||
|
key.push(0x00);
|
||||||
|
*beg = key;
|
||||||
|
}
|
||||||
|
let res = res.iter().map(|(_, val)| (val.into(), NO_DOC_ID)).collect();
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next_batch(
|
async fn next_batch(
|
||||||
|
@ -59,15 +80,7 @@ impl IndexEqualThingIterator {
|
||||||
txn: &Transaction,
|
txn: &Transaction,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
) -> Result<Vec<(Thing, DocId)>, Error> {
|
) -> Result<Vec<(Thing, DocId)>, Error> {
|
||||||
let min = self.beg.clone();
|
Self::next_scan(txn, &mut self.beg, &self.end, limit).await
|
||||||
let max = self.end.clone();
|
|
||||||
let res = txn.lock().await.scan(min..max, limit).await?;
|
|
||||||
if let Some((key, _)) = res.last() {
|
|
||||||
self.beg = key.clone();
|
|
||||||
self.beg.push(0x00);
|
|
||||||
}
|
|
||||||
let res = res.iter().map(|(_, val)| (val.into(), NO_DOC_ID)).collect();
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,16 +192,57 @@ impl IndexRangeThingIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct IndexUnionThingIterator {
|
||||||
|
values: VecDeque<(Vec<u8>, Vec<u8>)>,
|
||||||
|
current: Option<(Vec<u8>, Vec<u8>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexUnionThingIterator {
|
||||||
|
pub(super) fn new(opt: &Options, ix: &DefineIndexStatement, a: &Array) -> Self {
|
||||||
|
// We create a VecDeque to hold the prefix keys (begin and end) for each value in the array.
|
||||||
|
let mut values: VecDeque<(Vec<u8>, Vec<u8>)> =
|
||||||
|
a.0.iter()
|
||||||
|
.map(|v| {
|
||||||
|
let a = Array::from(v.clone());
|
||||||
|
let beg = Index::prefix_ids_beg(opt.ns(), opt.db(), &ix.what, &ix.name, &a);
|
||||||
|
let end = Index::prefix_ids_end(opt.ns(), opt.db(), &ix.what, &ix.name, &a);
|
||||||
|
(beg, end)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let current = values.pop_front();
|
||||||
|
Self {
|
||||||
|
values,
|
||||||
|
current,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn next_batch(
|
||||||
|
&mut self,
|
||||||
|
txn: &Transaction,
|
||||||
|
limit: u32,
|
||||||
|
) -> Result<Vec<(Thing, DocId)>, Error> {
|
||||||
|
while let Some(r) = &mut self.current {
|
||||||
|
let res = IndexEqualThingIterator::next_scan(txn, &mut r.0, &r.1, limit).await?;
|
||||||
|
if !res.is_empty() {
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
self.current = self.values.pop_front();
|
||||||
|
}
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct UniqueEqualThingIterator {
|
pub(crate) struct UniqueEqualThingIterator {
|
||||||
key: Option<Key>,
|
key: Option<Key>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UniqueEqualThingIterator {
|
impl UniqueEqualThingIterator {
|
||||||
pub(super) fn new(opt: &Options, ix: &DefineIndexStatement, a: &Array) -> Result<Self, Error> {
|
pub(super) fn new(opt: &Options, ix: &DefineIndexStatement, v: &Value) -> Self {
|
||||||
let key = Index::new(opt.ns(), opt.db(), &ix.what, &ix.name, a, None).into();
|
let a = Array::from(v.to_owned());
|
||||||
Ok(Self {
|
let key = Index::new(opt.ns(), opt.db(), &ix.what, &ix.name, &a, None).into();
|
||||||
|
Self {
|
||||||
key: Some(key),
|
key: Some(key),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn next_batch(&mut self, txn: &Transaction) -> Result<Vec<(Thing, DocId)>, Error> {
|
async fn next_batch(&mut self, txn: &Transaction) -> Result<Vec<(Thing, DocId)>, Error> {
|
||||||
|
|
|
@ -47,6 +47,9 @@ impl<'a> QueryPlanner<'a> {
|
||||||
let mut exe = QueryExecutor::new(self.opt, txn, &t, im).await?;
|
let mut exe = QueryExecutor::new(self.opt, txn, &t, im).await?;
|
||||||
match PlanBuilder::build(node, self.with, with_indexes)? {
|
match PlanBuilder::build(node, self.with, with_indexes)? {
|
||||||
Plan::SingleIndex(exp, io) => {
|
Plan::SingleIndex(exp, io) => {
|
||||||
|
if io.require_distinct() {
|
||||||
|
self.requires_distinct = true;
|
||||||
|
}
|
||||||
let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
|
let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
|
||||||
it.ingest(Iterable::Index(t.clone(), ir));
|
it.ingest(Iterable::Index(t.clone(), ir));
|
||||||
self.executors.insert(t.0.clone(), exe);
|
self.executors.insert(t.0.clone(), exe);
|
||||||
|
|
|
@ -146,7 +146,8 @@ pub(super) struct Inner {
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||||
pub(super) enum IndexOperator {
|
pub(super) enum IndexOperator {
|
||||||
Equality(Array),
|
Equality(Value),
|
||||||
|
Union(Array),
|
||||||
RangePart(Operator, Value),
|
RangePart(Operator, Value),
|
||||||
Matches(String, Option<MatchRef>),
|
Matches(String, Option<MatchRef>),
|
||||||
Knn(Array, u32),
|
Knn(Array, u32),
|
||||||
|
@ -161,6 +162,10 @@ impl IndexOption {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn require_distinct(&self) -> bool {
|
||||||
|
matches!(self.0.op, IndexOperator::Union(_))
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn ir(&self) -> IndexRef {
|
pub(super) fn ir(&self) -> IndexRef {
|
||||||
self.0.ir
|
self.0.ir
|
||||||
}
|
}
|
||||||
|
@ -173,16 +178,24 @@ impl IndexOption {
|
||||||
&self.0.id
|
&self.0.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reduce_array(v: &Value) -> Value {
|
||||||
|
if let Value::Array(a) = v {
|
||||||
|
if a.len() == 1 {
|
||||||
|
return a[0].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn explain(&self, e: &mut HashMap<&str, Value>) {
|
pub(crate) fn explain(&self, e: &mut HashMap<&str, Value>) {
|
||||||
match self.op() {
|
match self.op() {
|
||||||
IndexOperator::Equality(a) => {
|
IndexOperator::Equality(v) => {
|
||||||
let v = if a.len() == 1 {
|
|
||||||
a[0].clone()
|
|
||||||
} else {
|
|
||||||
Value::Array(a.clone())
|
|
||||||
};
|
|
||||||
e.insert("operator", Value::from(Operator::Equal.to_string()));
|
e.insert("operator", Value::from(Operator::Equal.to_string()));
|
||||||
e.insert("value", v);
|
e.insert("value", Self::reduce_array(v));
|
||||||
|
}
|
||||||
|
IndexOperator::Union(a) => {
|
||||||
|
e.insert("operator", Value::from("union"));
|
||||||
|
e.insert("value", Value::Array(a.clone()));
|
||||||
}
|
}
|
||||||
IndexOperator::Matches(qs, a) => {
|
IndexOperator::Matches(qs, a) => {
|
||||||
e.insert("operator", Value::from(Operator::Matches(*a).to_string()));
|
e.insert("operator", Value::from(Operator::Matches(*a).to_string()));
|
||||||
|
@ -303,13 +316,13 @@ mod tests {
|
||||||
let io1 = IndexOption::new(
|
let io1 = IndexOption::new(
|
||||||
1,
|
1,
|
||||||
Idiom::from("a.b".to_string()),
|
Idiom::from("a.b".to_string()),
|
||||||
IndexOperator::Equality(Array::from(vec!["test"])),
|
IndexOperator::Equality(Value::Array(Array::from(vec!["test"]))),
|
||||||
);
|
);
|
||||||
|
|
||||||
let io2 = IndexOption::new(
|
let io2 = IndexOption::new(
|
||||||
1,
|
1,
|
||||||
Idiom::from("a.b".to_string()),
|
Idiom::from("a.b".to_string()),
|
||||||
IndexOperator::Equality(Array::from(vec!["test"])),
|
IndexOperator::Equality(Value::Array(Array::from(vec!["test"]))),
|
||||||
);
|
);
|
||||||
|
|
||||||
set.insert(io1);
|
set.insert(io1);
|
||||||
|
|
|
@ -11,6 +11,28 @@ use std::sync::Arc;
|
||||||
|
|
||||||
pub(super) struct Tree {}
|
pub(super) struct Tree {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum IdiomPosition {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IdiomPosition {
|
||||||
|
// Reverses the operator for non commutative operators
|
||||||
|
fn transform(&self, op: &Operator) -> Operator {
|
||||||
|
match self {
|
||||||
|
IdiomPosition::Left => op.clone(),
|
||||||
|
IdiomPosition::Right => match op {
|
||||||
|
Operator::LessThan => Operator::MoreThan,
|
||||||
|
Operator::LessThanOrEqual => Operator::MoreThanOrEqual,
|
||||||
|
Operator::MoreThan => Operator::LessThan,
|
||||||
|
Operator::MoreThanOrEqual => Operator::LessThanOrEqual,
|
||||||
|
_ => op.clone(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Tree {
|
impl Tree {
|
||||||
/// Traverse all the conditions and extract every expression
|
/// Traverse all the conditions and extract every expression
|
||||||
/// that can be resolved by an index.
|
/// that can be resolved by an index.
|
||||||
|
@ -103,9 +125,9 @@ impl<'a> TreeBuilder<'a> {
|
||||||
Value::Expression(e) => self.eval_expression(e).await,
|
Value::Expression(e) => self.eval_expression(e).await,
|
||||||
Value::Idiom(i) => self.eval_idiom(i).await,
|
Value::Idiom(i) => self.eval_idiom(i).await,
|
||||||
Value::Strand(_) | Value::Number(_) | Value::Bool(_) | Value::Thing(_) => {
|
Value::Strand(_) | Value::Number(_) | Value::Bool(_) | Value::Thing(_) => {
|
||||||
Ok(Node::Scalar(v.to_owned()))
|
Ok(Node::Computed(v.to_owned()))
|
||||||
}
|
}
|
||||||
Value::Array(a) => Ok(self.eval_array(a)),
|
Value::Array(a) => self.eval_array(a).await,
|
||||||
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?;
|
||||||
|
@ -115,14 +137,12 @@ impl<'a> TreeBuilder<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_array(&mut self, a: &Array) -> Node {
|
async fn eval_array(&mut self, a: &Array) -> Result<Node, Error> {
|
||||||
// Check if it is a numeric vector
|
let mut values = Vec::with_capacity(a.len());
|
||||||
for v in &a.0 {
|
for v in &a.0 {
|
||||||
if !v.is_number() {
|
values.push(v.compute(self.ctx, self.opt, self.txn, None).await?);
|
||||||
return Node::Unsupported(format!("Unsupported array: {}", a));
|
|
||||||
}
|
}
|
||||||
}
|
Ok(Node::Computed(Value::Array(Array::from(values))))
|
||||||
Node::Vector(a.to_owned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn eval_idiom(&mut self, i: &Idiom) -> Result<Node, Error> {
|
async fn eval_idiom(&mut self, i: &Idiom) -> Result<Node, Error> {
|
||||||
|
@ -163,9 +183,23 @@ impl<'a> TreeBuilder<'a> {
|
||||||
}
|
}
|
||||||
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(irs.as_slice(), o, id, &right, e);
|
io = self.lookup_index_option(
|
||||||
|
irs.as_slice(),
|
||||||
|
o,
|
||||||
|
id,
|
||||||
|
&right,
|
||||||
|
e,
|
||||||
|
IdiomPosition::Left,
|
||||||
|
);
|
||||||
} else if let Some((id, irs)) = right.is_indexed_field() {
|
} else if let Some((id, irs)) = right.is_indexed_field() {
|
||||||
io = self.lookup_index_option(irs.as_slice(), o, id, &left, e);
|
io = self.lookup_index_option(
|
||||||
|
irs.as_slice(),
|
||||||
|
o,
|
||||||
|
id,
|
||||||
|
&left,
|
||||||
|
e,
|
||||||
|
IdiomPosition::Right,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
Ok(Node::Expression {
|
Ok(Node::Expression {
|
||||||
io,
|
io,
|
||||||
|
@ -184,36 +218,17 @@ impl<'a> TreeBuilder<'a> {
|
||||||
id: &Idiom,
|
id: &Idiom,
|
||||||
n: &Node,
|
n: &Node,
|
||||||
e: &Expression,
|
e: &Expression,
|
||||||
|
p: IdiomPosition,
|
||||||
) -> Option<IndexOption> {
|
) -> Option<IndexOption> {
|
||||||
for ir in irs {
|
for ir in irs {
|
||||||
if let Some(ix) = self.index_map.definitions.get(ir) {
|
if let Some(ix) = self.index_map.definitions.get(ir) {
|
||||||
let op = match &ix.index {
|
let op = match &ix.index {
|
||||||
Index::Idx => Self::eval_index_operator(op, n),
|
Index::Idx => Self::eval_index_operator(op, n, p),
|
||||||
Index::Uniq => Self::eval_index_operator(op, n),
|
Index::Uniq => Self::eval_index_operator(op, n, p),
|
||||||
Index::Search {
|
Index::Search {
|
||||||
..
|
..
|
||||||
} => {
|
} => Self::eval_matches_operator(op, n),
|
||||||
if let Some(v) = n.is_scalar() {
|
Index::MTree(_) => Self::eval_knn_operator(op, n),
|
||||||
if let Operator::Matches(mr) = op {
|
|
||||||
Some(IndexOperator::Matches(v.clone().to_raw_string(), *mr))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Index::MTree(_) => {
|
|
||||||
if let Operator::Knn(k) = op {
|
|
||||||
if let Node::Vector(a) = n {
|
|
||||||
Some(IndexOperator::Knn(a.clone(), *k))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if let Some(op) = op {
|
if let Some(op) = op {
|
||||||
let io = IndexOption::new(*ir, id.clone(), op);
|
let io = IndexOption::new(*ir, id.clone(), op);
|
||||||
|
@ -224,15 +239,45 @@ impl<'a> TreeBuilder<'a> {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
fn eval_matches_operator(op: &Operator, n: &Node) -> Option<IndexOperator> {
|
||||||
|
if let Some(v) = n.is_computed() {
|
||||||
|
if let Operator::Matches(mr) = op {
|
||||||
|
return Some(IndexOperator::Matches(v.clone().to_raw_string(), *mr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn eval_index_operator(op: &Operator, n: &Node) -> Option<IndexOperator> {
|
fn eval_knn_operator(op: &Operator, n: &Node) -> Option<IndexOperator> {
|
||||||
if let Some(v) = n.is_scalar() {
|
if let Operator::Knn(k) = op {
|
||||||
match op {
|
if let Node::Computed(Value::Array(a)) = n {
|
||||||
Operator::Equal => Some(IndexOperator::Equality(Array::from(v.clone()))),
|
return Some(IndexOperator::Knn(a.clone(), *k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_index_operator(op: &Operator, n: &Node, p: IdiomPosition) -> Option<IndexOperator> {
|
||||||
|
if let Some(v) = n.is_computed() {
|
||||||
|
match (op, v, p) {
|
||||||
|
(Operator::Equal, v, _) => Some(IndexOperator::Equality(v.clone())),
|
||||||
|
(Operator::Contain, v, IdiomPosition::Left) => {
|
||||||
|
Some(IndexOperator::Equality(v.clone()))
|
||||||
|
}
|
||||||
|
(Operator::ContainAny, Value::Array(a), IdiomPosition::Left) => {
|
||||||
|
Some(IndexOperator::Union(a.clone()))
|
||||||
|
}
|
||||||
|
(Operator::ContainAll, Value::Array(a), IdiomPosition::Left) => {
|
||||||
|
Some(IndexOperator::Union(a.clone()))
|
||||||
|
}
|
||||||
|
(
|
||||||
Operator::LessThan
|
Operator::LessThan
|
||||||
| Operator::LessThanOrEqual
|
| Operator::LessThanOrEqual
|
||||||
| Operator::MoreThan
|
| Operator::MoreThan
|
||||||
| Operator::MoreThanOrEqual => Some(IndexOperator::RangePart(op.clone(), v.clone())),
|
| Operator::MoreThanOrEqual,
|
||||||
|
v,
|
||||||
|
p,
|
||||||
|
) => Some(IndexOperator::RangePart(p.transform(op), v.clone())),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -267,14 +312,13 @@ pub(super) enum Node {
|
||||||
},
|
},
|
||||||
IndexedField(Idiom, Arc<Vec<IndexRef>>),
|
IndexedField(Idiom, Arc<Vec<IndexRef>>),
|
||||||
NonIndexedField,
|
NonIndexedField,
|
||||||
Scalar(Value),
|
Computed(Value),
|
||||||
Vector(Array),
|
|
||||||
Unsupported(String),
|
Unsupported(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
pub(super) fn is_scalar(&self) -> Option<&Value> {
|
pub(super) fn is_computed(&self) -> Option<&Value> {
|
||||||
if let Node::Scalar(v) = self {
|
if let Node::Computed(v) = self {
|
||||||
Some(v)
|
Some(v)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -5,11 +5,14 @@ mod helpers;
|
||||||
use helpers::new_ds;
|
use helpers::new_ds;
|
||||||
use surrealdb::dbs::{Response, Session};
|
use surrealdb::dbs::{Response, Session};
|
||||||
use surrealdb::err::Error;
|
use surrealdb::err::Error;
|
||||||
|
use surrealdb::kvs::Datastore;
|
||||||
use surrealdb::sql::Value;
|
use surrealdb::sql::Value;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_three_multi_index() -> Result<(), Error> {
|
async fn select_where_iterate_three_multi_index() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&three_multi_index_query("", ""), 12, 8).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &three_multi_index_query("", ""), 12).await?;
|
||||||
|
skip_ok(&mut res, 8)?;
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
@ -21,7 +24,9 @@ async fn select_where_iterate_three_multi_index() -> Result<(), Error> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_three_multi_index_parallel() -> Result<(), Error> {
|
async fn select_where_iterate_three_multi_index_parallel() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&three_multi_index_query("", "PARALLEL"), 12, 8).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &three_multi_index_query("", "PARALLEL"), 12).await?;
|
||||||
|
skip_ok(&mut res, 8)?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
||||||
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
@ -33,12 +38,14 @@ async fn select_where_iterate_three_multi_index_parallel() -> Result<(), Error>
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_three_multi_index_with_all_index() -> Result<(), Error> {
|
async fn select_where_iterate_three_multi_index_with_all_index() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
let mut res = execute_test(
|
let mut res = execute_test(
|
||||||
|
&dbs,
|
||||||
&three_multi_index_query("WITH INDEX uniq_name,idx_genre,ft_company", ""),
|
&three_multi_index_query("WITH INDEX uniq_name,idx_genre,ft_company", ""),
|
||||||
12,
|
12,
|
||||||
8,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
skip_ok(&mut res, 8)?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }, { name: 'Lizzie' }]")?;
|
||||||
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
check_result(&mut res, THREE_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
@ -50,8 +57,11 @@ async fn select_where_iterate_three_multi_index_with_all_index() -> Result<(), E
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_three_multi_index_with_one_ft_index() -> Result<(), Error> {
|
async fn select_where_iterate_three_multi_index_with_one_ft_index() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
let mut res =
|
let mut res =
|
||||||
execute_test(&three_multi_index_query("WITH INDEX ft_company", ""), 12, 8).await?;
|
execute_test(&dbs, &three_multi_index_query("WITH INDEX ft_company", ""), 12).await?;
|
||||||
|
skip_ok(&mut res, 8)?;
|
||||||
|
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Lizzie' }, { name: 'Tobie' } ]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Lizzie' }, { name: 'Tobie' } ]")?;
|
||||||
check_result(&mut res, THREE_TABLE_EXPLAIN)?;
|
check_result(&mut res, THREE_TABLE_EXPLAIN)?;
|
||||||
|
@ -63,7 +73,11 @@ async fn select_where_iterate_three_multi_index_with_one_ft_index() -> Result<()
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_three_multi_index_with_one_index() -> Result<(), Error> {
|
async fn select_where_iterate_three_multi_index_with_one_index() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&three_multi_index_query("WITH INDEX uniq_name", ""), 12, 8).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res =
|
||||||
|
execute_test(&dbs, &three_multi_index_query("WITH INDEX uniq_name", ""), 12).await?;
|
||||||
|
skip_ok(&mut res, 8)?;
|
||||||
|
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Lizzie' }, { name: 'Tobie' } ]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Lizzie' }, { name: 'Tobie' } ]")?;
|
||||||
check_result(&mut res, THREE_TABLE_EXPLAIN)?;
|
check_result(&mut res, THREE_TABLE_EXPLAIN)?;
|
||||||
|
@ -75,7 +89,9 @@ async fn select_where_iterate_three_multi_index_with_one_index() -> Result<(), E
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_two_multi_index() -> Result<(), Error> {
|
async fn select_where_iterate_two_multi_index() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&two_multi_index_query("", ""), 9, 5).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &two_multi_index_query("", ""), 9).await?;
|
||||||
|
skip_ok(&mut res, 5)?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
check_result(&mut res, TWO_MULTI_INDEX_EXPLAIN)?;
|
check_result(&mut res, TWO_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
@ -87,7 +103,9 @@ async fn select_where_iterate_two_multi_index() -> Result<(), Error> {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_two_multi_index_with_one_index() -> Result<(), Error> {
|
async fn select_where_iterate_two_multi_index_with_one_index() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&two_multi_index_query("WITH INDEX idx_genre", ""), 9, 5).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &two_multi_index_query("WITH INDEX idx_genre", ""), 9).await?;
|
||||||
|
skip_ok(&mut res, 5)?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
check_result(&mut res, &table_explain(2))?;
|
check_result(&mut res, &table_explain(2))?;
|
||||||
|
@ -99,8 +117,10 @@ async fn select_where_iterate_two_multi_index_with_one_index() -> Result<(), Err
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_two_multi_index_with_two_index() -> Result<(), Error> {
|
async fn select_where_iterate_two_multi_index_with_two_index() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
let mut res =
|
let mut res =
|
||||||
execute_test(&two_multi_index_query("WITH INDEX idx_genre,uniq_name", ""), 9, 5).await?;
|
execute_test(&dbs, &two_multi_index_query("WITH INDEX idx_genre,uniq_name", ""), 9).await?;
|
||||||
|
skip_ok(&mut res, 5)?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
check_result(&mut res, TWO_MULTI_INDEX_EXPLAIN)?;
|
check_result(&mut res, TWO_MULTI_INDEX_EXPLAIN)?;
|
||||||
|
@ -112,7 +132,9 @@ async fn select_where_iterate_two_multi_index_with_two_index() -> Result<(), Err
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn select_where_iterate_two_no_index() -> Result<(), Error> {
|
async fn select_where_iterate_two_no_index() -> Result<(), Error> {
|
||||||
let mut res = execute_test(&two_multi_index_query("WITH NOINDEX", ""), 9, 5).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &two_multi_index_query("WITH NOINDEX", ""), 9).await?;
|
||||||
|
skip_ok(&mut res, 5)?;
|
||||||
// OR results
|
// OR results
|
||||||
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
check_result(&mut res, "[{ name: 'Jaime' }, { name: 'Tobie' }]")?;
|
||||||
check_result(&mut res, &table_explain_no_index(2))?;
|
check_result(&mut res, &table_explain_no_index(2))?;
|
||||||
|
@ -123,19 +145,21 @@ async fn select_where_iterate_two_no_index() -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_test(
|
async fn execute_test(
|
||||||
|
dbs: &Datastore,
|
||||||
sql: &str,
|
sql: &str,
|
||||||
expected_result: usize,
|
expected_result: usize,
|
||||||
check_results: usize,
|
|
||||||
) -> Result<Vec<Response>, Error> {
|
) -> Result<Vec<Response>, Error> {
|
||||||
let dbs = new_ds().await?;
|
|
||||||
let ses = Session::owner().with_ns("test").with_db("test");
|
let ses = Session::owner().with_ns("test").with_db("test");
|
||||||
let mut res = dbs.execute(sql, &ses, None).await?;
|
let res = dbs.execute(sql, &ses, None).await?;
|
||||||
assert_eq!(res.len(), expected_result);
|
assert_eq!(res.len(), expected_result);
|
||||||
// Check that the setup is ok
|
Ok(res)
|
||||||
for _ in 0..check_results {
|
}
|
||||||
|
|
||||||
|
fn skip_ok(res: &mut Vec<Response>, skip: usize) -> Result<(), Error> {
|
||||||
|
for _ in 0..skip {
|
||||||
let _ = res.remove(0).result?;
|
let _ = res.remove(0).result?;
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_result(res: &mut Vec<Response>, expected: &str) -> Result<(), Error> {
|
fn check_result(res: &mut Vec<Response>, expected: &str) -> Result<(), Error> {
|
||||||
|
@ -468,7 +492,9 @@ async fn select_range(
|
||||||
explain: &str,
|
explain: &str,
|
||||||
result: &str,
|
result: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut res = execute_test(&range_test(unique, from_incl, to_incl), 8, 6).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &range_test(unique, from_incl, to_incl), 8).await?;
|
||||||
|
skip_ok(&mut res, 6)?;
|
||||||
{
|
{
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(explain);
|
let val = Value::parse(explain);
|
||||||
|
@ -685,7 +711,9 @@ async fn select_single_range_operator(
|
||||||
explain: &str,
|
explain: &str,
|
||||||
result: &str,
|
result: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut res = execute_test(&single_range_operator_test(unique, op), 6, 4).await?;
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, &single_range_operator_test(unique, op), 6).await?;
|
||||||
|
skip_ok(&mut res, 4)?;
|
||||||
{
|
{
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(explain);
|
let val = Value::parse(explain);
|
||||||
|
@ -862,11 +890,7 @@ async fn select_with_idiom_param_value() -> Result<(), Error> {
|
||||||
);
|
);
|
||||||
let mut res = dbs.execute(&sql, &ses, None).await?;
|
let mut res = dbs.execute(&sql, &ses, None).await?;
|
||||||
assert_eq!(res.len(), 6);
|
assert_eq!(res.len(), 6);
|
||||||
res.remove(0).result?;
|
skip_ok(&mut res, 5)?;
|
||||||
res.remove(0).result?;
|
|
||||||
res.remove(0).result?;
|
|
||||||
res.remove(0).result?;
|
|
||||||
res.remove(0).result?;
|
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse(
|
let val = Value::parse(
|
||||||
r#"[
|
r#"[
|
||||||
|
@ -886,3 +910,239 @@ async fn select_with_idiom_param_value() -> Result<(), Error> {
|
||||||
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CONTAINS_CONTENT: &str = r#"
|
||||||
|
CREATE student:1 CONTENT {
|
||||||
|
marks: [
|
||||||
|
{ subject: "maths", mark: 50 },
|
||||||
|
{ subject: "english", mark: 40 },
|
||||||
|
{ subject: "tamil", mark: 45 }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
CREATE student:2 CONTENT {
|
||||||
|
marks: [
|
||||||
|
{ subject: "maths", mark: 50 },
|
||||||
|
{ subject: "english", mark: 35 },
|
||||||
|
{ subject: "hindi", mark: 45 }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
CREATE student:3 CONTENT {
|
||||||
|
marks: [
|
||||||
|
{ subject: "maths", mark: 50 },
|
||||||
|
{ subject: "hindi", mark: 30 },
|
||||||
|
{ subject: "tamil", mark: 45 }
|
||||||
|
]
|
||||||
|
};"#;
|
||||||
|
|
||||||
|
const CONTAINS_TABLE_EXPLAIN: &str = r"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'student'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Table'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
reason: 'NO INDEX FOUND'
|
||||||
|
},
|
||||||
|
operation: 'Fallback'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
async fn test_contains(
|
||||||
|
dbs: &Datastore,
|
||||||
|
sql: &str,
|
||||||
|
index_explain: &str,
|
||||||
|
result: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut res = execute_test(&dbs, sql, 5).await?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(CONTAINS_TABLE_EXPLAIN);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(result);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
}
|
||||||
|
skip_ok(&mut res, 1)?;
|
||||||
|
{
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(index_explain);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let tmp = res.remove(0).result?;
|
||||||
|
let val = Value::parse(result);
|
||||||
|
assert_eq!(format!("{:#}", tmp), format!("{:#}", val));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_contains() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, CONTAINS_CONTENT, 3).await?;
|
||||||
|
skip_ok(&mut res, 3)?;
|
||||||
|
|
||||||
|
const SQL: &str = r#"
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINS "english" EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINS "english";
|
||||||
|
DEFINE INDEX subject_idx ON student COLUMNS marks.*.subject;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINS "english" EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINS "english";
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const INDEX_EXPLAIN: &str = r"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'student'
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'subject_idx',
|
||||||
|
operator: '=',
|
||||||
|
value: 'english'
|
||||||
|
},
|
||||||
|
table: 'student',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
const RESULT: &str = r"[
|
||||||
|
{
|
||||||
|
id: student:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: student:2
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
test_contains(&dbs, SQL, INDEX_EXPLAIN, RESULT).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_contains_all() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, CONTAINS_CONTENT, 3).await?;
|
||||||
|
skip_ok(&mut res, 3)?;
|
||||||
|
const SQL: &str = r#"
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSALL ["hindi", "maths"] EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSALL ["hindi", "maths"];
|
||||||
|
DEFINE INDEX subject_idx ON student COLUMNS marks.*.subject;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSALL ["hindi", "maths"] EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSALL ["hindi", "maths"];
|
||||||
|
"#;
|
||||||
|
const INDEX_EXPLAIN: &str = r"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'student'
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'subject_idx',
|
||||||
|
operator: 'union',
|
||||||
|
value: ['hindi', 'maths']
|
||||||
|
},
|
||||||
|
table: 'student',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
const RESULT: &str = r"[
|
||||||
|
{
|
||||||
|
id: student:2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: student:3
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
test_contains(&dbs, SQL, INDEX_EXPLAIN, RESULT).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_contains_any() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, CONTAINS_CONTENT, 3).await?;
|
||||||
|
skip_ok(&mut res, 3)?;
|
||||||
|
const SQL: &str = r#"
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSANY ["tamil", "french"] EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSANY ["tamil", "french"];
|
||||||
|
DEFINE INDEX subject_idx ON student COLUMNS marks.*.subject;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSANY ["tamil", "french"] EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE marks.*.subject CONTAINSANY ["tamil", "french"];
|
||||||
|
"#;
|
||||||
|
const INDEX_EXPLAIN: &str = r"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'student'
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'subject_idx',
|
||||||
|
operator: 'union',
|
||||||
|
value: ['tamil', 'french']
|
||||||
|
},
|
||||||
|
table: 'student',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
const RESULT: &str = r"[
|
||||||
|
{
|
||||||
|
id: student:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: student:3
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
test_contains(&dbs, SQL, INDEX_EXPLAIN, RESULT).await
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONTAINS_UNIQUE_CONTENT: &str = r#"
|
||||||
|
CREATE student:1 CONTENT { subject: "maths", mark: 50 };
|
||||||
|
CREATE student:2 CONTENT { subject: "english", mark: 35 };
|
||||||
|
CREATE student:3 CONTENT { subject: "hindi", mark: 30 };"#;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_unique_contains() -> Result<(), Error> {
|
||||||
|
let dbs = new_ds().await?;
|
||||||
|
let mut res = execute_test(&dbs, CONTAINS_UNIQUE_CONTENT, 3).await?;
|
||||||
|
skip_ok(&mut res, 3)?;
|
||||||
|
|
||||||
|
const SQL: &str = r#"
|
||||||
|
SELECT id FROM student WHERE subject CONTAINS "english" EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE subject CONTAINS "english";
|
||||||
|
DEFINE INDEX subject_idx ON student COLUMNS subject UNIQUE;
|
||||||
|
SELECT id FROM student WHERE subject CONTAINS "english" EXPLAIN;
|
||||||
|
SELECT id FROM student WHERE subject CONTAINS "english";
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const INDEX_EXPLAIN: &str = r"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
table: 'student'
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
index: 'subject_idx',
|
||||||
|
operator: '=',
|
||||||
|
value: 'english'
|
||||||
|
},
|
||||||
|
table: 'student',
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
const RESULT: &str = r"[
|
||||||
|
{
|
||||||
|
id: student:2
|
||||||
|
}
|
||||||
|
]";
|
||||||
|
|
||||||
|
test_contains(&dbs, SQL, INDEX_EXPLAIN, RESULT).await
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue