[Feat] SELECT ORDER should use the index iterator when available (#4525)
This commit is contained in:
parent
a16f850c4f
commit
0b5d79cae0
14 changed files with 606 additions and 243 deletions
|
@ -58,15 +58,17 @@ pub(crate) struct Iterator {
|
||||||
// Iterator status
|
// Iterator status
|
||||||
run: Canceller,
|
run: Canceller,
|
||||||
// Iterator limit value
|
// Iterator limit value
|
||||||
limit: Option<usize>,
|
limit: Option<u32>,
|
||||||
// Iterator start value
|
// Iterator start value
|
||||||
start: Option<usize>,
|
start: Option<u32>,
|
||||||
// Iterator runtime error
|
// Iterator runtime error
|
||||||
error: Option<Error>,
|
error: Option<Error>,
|
||||||
// Iterator output results
|
// Iterator output results
|
||||||
results: Results,
|
results: Results,
|
||||||
// Iterator input values
|
// Iterator input values
|
||||||
entries: Vec<Iterable>,
|
entries: Vec<Iterable>,
|
||||||
|
// Set if the iterator can be cancelled once it reaches start/limit
|
||||||
|
cancel_on_limit: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Iterator {
|
impl Clone for Iterator {
|
||||||
|
@ -78,6 +80,7 @@ impl Clone for Iterator {
|
||||||
error: None,
|
error: None,
|
||||||
results: Results::default(),
|
results: Results::default(),
|
||||||
entries: self.entries.clone(),
|
entries: self.entries.clone(),
|
||||||
|
cancel_on_limit: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +341,7 @@ impl Iterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process any START & LIMIT clause
|
// Process any START & LIMIT clause
|
||||||
self.results.start_limit(self.start.as_ref(), self.limit.as_ref());
|
self.results.start_limit(self.start, self.limit);
|
||||||
|
|
||||||
if let Some(e) = &mut plan.explanation {
|
if let Some(e) = &mut plan.explanation {
|
||||||
e.add_fetch(self.results.len());
|
e.add_fetch(self.results.len());
|
||||||
|
@ -364,17 +367,19 @@ impl Iterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn setup_limit(
|
pub(crate) async fn setup_limit(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<Option<u32>, Error> {
|
||||||
if let Some(v) = stm.limit() {
|
if self.limit.is_none() {
|
||||||
self.limit = Some(v.process(stk, ctx, opt, None).await?);
|
if let Some(v) = stm.limit() {
|
||||||
|
self.limit = Some(v.process(stk, ctx, opt, None).await?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(self.limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -391,6 +396,44 @@ impl Iterator {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the iteration can be limited per iterator
|
||||||
|
fn check_set_start_limit(&mut self, ctx: &Context, stm: &Statement<'_>) -> bool {
|
||||||
|
// If there are groups we can't
|
||||||
|
if stm.group().is_some() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If there is no specified order, we can
|
||||||
|
if stm.order().is_none() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If there is more than 1 iterator, we can't
|
||||||
|
if self.entries.len() != 1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If the iterator is backed by a sorted index
|
||||||
|
// and the sorting matches the first ORDER entry, we can
|
||||||
|
if let Some(Iterable::Index(_, irf)) = self.entries.first() {
|
||||||
|
if let Some(qp) = ctx.get_query_planner() {
|
||||||
|
if qp.is_order(irf) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_start_limit(&mut self, ctx: &Context, stm: &Statement<'_>) {
|
||||||
|
if self.check_set_start_limit(ctx, stm) {
|
||||||
|
if let Some(l) = self.limit {
|
||||||
|
if let Some(s) = self.start {
|
||||||
|
self.cancel_on_limit = Some(l + s);
|
||||||
|
} else {
|
||||||
|
self.cancel_on_limit = Some(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn output_split(
|
async fn output_split(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -489,13 +532,15 @@ impl Iterator {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
// Compute iteration limits
|
||||||
|
self.compute_start_limit(ctx, stm);
|
||||||
// Prevent deep recursion
|
// Prevent deep recursion
|
||||||
let opt = &opt.dive(4)?;
|
let opt = &opt.dive(4)?;
|
||||||
// Check if iterating in parallel
|
// Check if iterating in parallel
|
||||||
match stm.parallel() {
|
match stm.parallel() {
|
||||||
// Run statements sequentially
|
// Run statements sequentially
|
||||||
false => {
|
false => {
|
||||||
// If any iterator requires distinct, we new to create a global distinct instance
|
// If any iterator requires distinct, we need to create a global distinct instance
|
||||||
let mut distinct = SyncDistinct::new(ctx);
|
let mut distinct = SyncDistinct::new(ctx);
|
||||||
// Process all prepared values
|
// Process all prepared values
|
||||||
for v in mem::take(&mut self.entries) {
|
for v in mem::take(&mut self.entries) {
|
||||||
|
@ -621,16 +666,10 @@ impl Iterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check if we can exit
|
// Check if we have enough results
|
||||||
if stm.group().is_none() && stm.order().is_none() {
|
if let Some(l) = self.cancel_on_limit {
|
||||||
if let Some(l) = self.limit {
|
if self.results.len() == l as usize {
|
||||||
if let Some(s) = self.start {
|
self.run.cancel()
|
||||||
if self.results.len() == l + s {
|
|
||||||
self.run.cancel()
|
|
||||||
}
|
|
||||||
} else if self.results.len() == l {
|
|
||||||
self.run.cancel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ impl Results {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn start_limit(&mut self, start: Option<&usize>, limit: Option<&usize>) {
|
pub(super) fn start_limit(&mut self, start: Option<u32>, limit: Option<u32>) {
|
||||||
match self {
|
match self {
|
||||||
Self::None => {}
|
Self::None => {}
|
||||||
Self::Memory(m) => m.start_limit(start, limit),
|
Self::Memory(m) => m.start_limit(start, limit),
|
||||||
|
|
|
@ -19,16 +19,20 @@ impl MemoryCollector {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn start_limit(&mut self, start: Option<&usize>, limit: Option<&usize>) {
|
pub(super) fn start_limit(&mut self, start: Option<u32>, limit: Option<u32>) {
|
||||||
match (start, limit) {
|
match (start, limit) {
|
||||||
(Some(&start), Some(&limit)) => {
|
(Some(start), Some(limit)) => {
|
||||||
self.0 = mem::take(&mut self.0).into_iter().skip(start).take(limit).collect()
|
self.0 = mem::take(&mut self.0)
|
||||||
|
.into_iter()
|
||||||
|
.skip(start as usize)
|
||||||
|
.take(limit as usize)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
(Some(&start), None) => {
|
(Some(start), None) => {
|
||||||
self.0 = mem::take(&mut self.0).into_iter().skip(start).collect()
|
self.0 = mem::take(&mut self.0).into_iter().skip(start as usize).collect()
|
||||||
}
|
}
|
||||||
(None, Some(&limit)) => {
|
(None, Some(limit)) => {
|
||||||
self.0 = mem::take(&mut self.0).into_iter().take(limit).collect()
|
self.0 = mem::take(&mut self.0).into_iter().take(limit as usize).collect()
|
||||||
}
|
}
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
}
|
}
|
||||||
|
@ -124,15 +128,15 @@ pub(super) mod file_store {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::dbs) fn start_limit(&mut self, start: Option<&usize>, limit: Option<&usize>) {
|
pub(in crate::dbs) fn start_limit(&mut self, start: Option<u32>, limit: Option<u32>) {
|
||||||
self.paging.start = start.cloned();
|
self.paging.start = start;
|
||||||
self.paging.limit = limit.cloned();
|
self.paging.limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(in crate::dbs) fn take_vec(&mut self) -> Result<Vec<Value>, Error> {
|
pub(in crate::dbs) fn take_vec(&mut self) -> Result<Vec<Value>, Error> {
|
||||||
self.check_reader()?;
|
self.check_reader()?;
|
||||||
if let Some(mut reader) = self.reader.take() {
|
if let Some(mut reader) = self.reader.take() {
|
||||||
if let Some((start, num)) = self.paging.get_start_num(reader.len) {
|
if let Some((start, num)) = self.paging.get_start_num(reader.len as u32) {
|
||||||
if let Some(orders) = self.orders.take() {
|
if let Some(orders) = self.orders.take() {
|
||||||
return self.sort_and_take_vec(reader, orders, start, num);
|
return self.sort_and_take_vec(reader, orders, start, num);
|
||||||
}
|
}
|
||||||
|
@ -146,8 +150,8 @@ pub(super) mod file_store {
|
||||||
&mut self,
|
&mut self,
|
||||||
reader: FileReader,
|
reader: FileReader,
|
||||||
orders: Orders,
|
orders: Orders,
|
||||||
start: usize,
|
start: u32,
|
||||||
num: usize,
|
num: u32,
|
||||||
) -> Result<Vec<Value>, Error> {
|
) -> Result<Vec<Value>, Error> {
|
||||||
let sort_dir = self.dir.path().join(Self::SORT_DIRECTORY_NAME);
|
let sort_dir = self.dir.path().join(Self::SORT_DIRECTORY_NAME);
|
||||||
fs::create_dir(&sort_dir)?;
|
fs::create_dir(&sort_dir)?;
|
||||||
|
@ -160,7 +164,7 @@ pub(super) mod file_store {
|
||||||
|
|
||||||
let sorted = sorter.sort_by(reader, |a, b| orders.compare(a, b))?;
|
let sorted = sorter.sort_by(reader, |a, b| orders.compare(a, b))?;
|
||||||
let iter = sorted.map(Result::unwrap);
|
let iter = sorted.map(Result::unwrap);
|
||||||
let r: Vec<Value> = iter.skip(start).take(num).collect();
|
let r: Vec<Value> = iter.skip(start as usize).take(num as usize).collect();
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
pub(in crate::dbs) fn explain(&self, exp: &mut Explanation) {
|
pub(in crate::dbs) fn explain(&self, exp: &mut Explanation) {
|
||||||
|
@ -259,20 +263,22 @@ pub(super) mod file_store {
|
||||||
Ok(u)
|
Ok(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_vec(&mut self, start: usize, num: usize) -> Result<Vec<Value>, Error> {
|
fn take_vec(&mut self, start: u32, num: u32) -> Result<Vec<Value>, Error> {
|
||||||
let mut iter = FileRecordsIterator::new(self.records.clone(), self.len);
|
let mut iter = FileRecordsIterator::new(self.records.clone(), self.len);
|
||||||
if start > 0 {
|
if start > 0 {
|
||||||
// Get the start offset of the first record
|
// Get the start offset of the first record
|
||||||
let mut index = OpenOptions::new().read(true).open(&self.index)?;
|
let mut index = OpenOptions::new().read(true).open(&self.index)?;
|
||||||
index.seek(SeekFrom::Start(((start - 1) * FileCollector::USIZE_SIZE) as u64))?;
|
index.seek(SeekFrom::Start(
|
||||||
|
((start as usize - 1) * FileCollector::USIZE_SIZE) as u64,
|
||||||
|
))?;
|
||||||
let start_offset = Self::read_usize(&mut index)?;
|
let start_offset = Self::read_usize(&mut index)?;
|
||||||
|
|
||||||
// Set records to the position of the first record
|
// Set records to the position of the first record
|
||||||
iter.seek(start_offset, start)?;
|
iter.seek(start_offset, start as usize)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect the records
|
// Collect the records
|
||||||
let mut res = Vec::with_capacity(num);
|
let mut res = Vec::with_capacity(num as usize);
|
||||||
for _ in 0..num {
|
for _ in 0..num {
|
||||||
debug!("READ");
|
debug!("READ");
|
||||||
if let Some(val) = iter.next() {
|
if let Some(val) = iter.next() {
|
||||||
|
@ -356,12 +362,12 @@ pub(super) mod file_store {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FilePaging {
|
struct FilePaging {
|
||||||
start: Option<usize>,
|
start: Option<u32>,
|
||||||
limit: Option<usize>,
|
limit: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilePaging {
|
impl FilePaging {
|
||||||
fn get_start_num(&self, len: usize) -> Option<(usize, usize)> {
|
fn get_start_num(&self, len: u32) -> Option<(u32, u32)> {
|
||||||
let start = self.start.unwrap_or(0);
|
let start = self.start.unwrap_or(0);
|
||||||
if start >= len {
|
if start >= len {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl From<InnerQueryExecutor> for QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum IteratorEntry {
|
pub(super) enum IteratorEntry {
|
||||||
Single(Arc<Expression>, IndexOption),
|
Single(Option<Arc<Expression>>, IndexOption),
|
||||||
Range(HashSet<Arc<Expression>>, IndexRef, RangeValue, RangeValue),
|
Range(HashSet<Arc<Expression>>, IndexRef, RangeValue, RangeValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ impl QueryExecutor {
|
||||||
/// Returns `true` if the expression is matching the current iterator.
|
/// Returns `true` if the expression is matching the current iterator.
|
||||||
pub(crate) fn is_iterator_expression(&self, irf: IteratorRef, exp: &Expression) -> bool {
|
pub(crate) fn is_iterator_expression(&self, irf: IteratorRef, exp: &Expression) -> bool {
|
||||||
match self.0.it_entries.get(irf as usize) {
|
match self.0.it_entries.get(irf as usize) {
|
||||||
Some(IteratorEntry::Single(e, ..)) => exp.eq(e.as_ref()),
|
Some(IteratorEntry::Single(Some(e), ..)) => exp.eq(e.as_ref()),
|
||||||
Some(IteratorEntry::Range(es, ..)) => es.contains(exp),
|
Some(IteratorEntry::Range(es, ..)) => es.contains(exp),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -405,6 +405,19 @@ impl QueryExecutor {
|
||||||
let index_join = Box::new(IndexJoinThingIterator::new(irf, opt, ix, iterators)?);
|
let index_join = Box::new(IndexJoinThingIterator::new(irf, opt, ix, iterators)?);
|
||||||
Some(ThingIterator::IndexJoin(index_join))
|
Some(ThingIterator::IndexJoin(index_join))
|
||||||
}
|
}
|
||||||
|
IndexOperator::Order(asc) => {
|
||||||
|
if *asc {
|
||||||
|
Some(ThingIterator::IndexRange(IndexRangeThingIterator::full_range(
|
||||||
|
irf,
|
||||||
|
opt.ns()?,
|
||||||
|
opt.db()?,
|
||||||
|
&ix.what,
|
||||||
|
&ix.name,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -472,6 +485,19 @@ impl QueryExecutor {
|
||||||
let unique_join = Box::new(UniqueJoinThingIterator::new(irf, opt, ix, iterators)?);
|
let unique_join = Box::new(UniqueJoinThingIterator::new(irf, opt, ix, iterators)?);
|
||||||
Some(ThingIterator::UniqueJoin(unique_join))
|
Some(ThingIterator::UniqueJoin(unique_join))
|
||||||
}
|
}
|
||||||
|
IndexOperator::Order(asc) => {
|
||||||
|
if *asc {
|
||||||
|
Some(ThingIterator::UniqueRange(UniqueRangeThingIterator::full_range(
|
||||||
|
irf,
|
||||||
|
opt.ns()?,
|
||||||
|
opt.db()?,
|
||||||
|
&ix.what,
|
||||||
|
&ix.name,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -481,7 +507,7 @@ impl QueryExecutor {
|
||||||
irf: IteratorRef,
|
irf: IteratorRef,
|
||||||
io: IndexOption,
|
io: IndexOption,
|
||||||
) -> Result<Option<ThingIterator>, Error> {
|
) -> Result<Option<ThingIterator>, Error> {
|
||||||
if let Some(IteratorEntry::Single(exp, ..)) = self.0.it_entries.get(irf as usize) {
|
if let Some(IteratorEntry::Single(Some(exp), ..)) = self.0.it_entries.get(irf as usize) {
|
||||||
if let Matches(_, _) = io.op() {
|
if let Matches(_, _) = io.op() {
|
||||||
if let Some(fti) = self.0.ft_map.get(&io.ix_ref()) {
|
if let Some(fti) = self.0.ft_map.get(&io.ix_ref()) {
|
||||||
if let Some(fte) = self.0.exp_entries.get(exp) {
|
if let Some(fte) = self.0.exp_entries.get(exp) {
|
||||||
|
@ -496,7 +522,7 @@ impl QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_mtree_index_knn_iterator(&self, irf: IteratorRef) -> Option<ThingIterator> {
|
fn new_mtree_index_knn_iterator(&self, irf: IteratorRef) -> Option<ThingIterator> {
|
||||||
if let Some(IteratorEntry::Single(exp, ..)) = self.0.it_entries.get(irf as usize) {
|
if let Some(IteratorEntry::Single(Some(exp), ..)) = self.0.it_entries.get(irf as usize) {
|
||||||
if let Some(mte) = self.0.mt_entries.get(exp) {
|
if let Some(mte) = self.0.mt_entries.get(exp) {
|
||||||
let it = KnnIterator::new(irf, mte.res.clone());
|
let it = KnnIterator::new(irf, mte.res.clone());
|
||||||
return Some(ThingIterator::Knn(it));
|
return Some(ThingIterator::Knn(it));
|
||||||
|
@ -506,7 +532,7 @@ impl QueryExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_hnsw_index_ann_iterator(&self, irf: IteratorRef) -> Option<ThingIterator> {
|
fn new_hnsw_index_ann_iterator(&self, irf: IteratorRef) -> Option<ThingIterator> {
|
||||||
if let Some(IteratorEntry::Single(exp, ..)) = self.0.it_entries.get(irf as usize) {
|
if let Some(IteratorEntry::Single(Some(exp), ..)) = self.0.it_entries.get(irf as usize) {
|
||||||
if let Some(he) = self.0.hnsw_entries.get(exp) {
|
if let Some(he) = self.0.hnsw_entries.get(exp) {
|
||||||
let it = KnnIterator::new(irf, he.res.clone());
|
let it = KnnIterator::new(irf, he.res.clone());
|
||||||
return Some(ThingIterator::Knn(it));
|
return Some(ThingIterator::Knn(it));
|
||||||
|
@ -602,7 +628,7 @@ impl QueryExecutor {
|
||||||
r: Value,
|
r: Value,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
// If the query terms contains terms that are unknown in the index
|
// If the query terms contains terms that are unknown in the index
|
||||||
// of if there is not terms in the query
|
// of if there are no terms in the query
|
||||||
// we are sure that it does not match any document
|
// we are sure that it does not match any document
|
||||||
if !ft.0.query_terms_set.is_matchable() {
|
if !ft.0.query_terms_set.is_matchable() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
|
@ -610,6 +636,7 @@ impl QueryExecutor {
|
||||||
let v = match ft.0.index_option.id_pos() {
|
let v = match ft.0.index_option.id_pos() {
|
||||||
IdiomPosition::Left => r,
|
IdiomPosition::Left => r,
|
||||||
IdiomPosition::Right => l,
|
IdiomPosition::Right => l,
|
||||||
|
IdiomPosition::None => return Ok(false),
|
||||||
};
|
};
|
||||||
let terms = ft.0.terms.read().await;
|
let terms = ft.0.terms.read().await;
|
||||||
// Extract the terms set from the record
|
// Extract the terms set from the record
|
||||||
|
|
|
@ -260,6 +260,20 @@ impl IndexRangeThingIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn full_range(
|
||||||
|
irf: IteratorRef,
|
||||||
|
ns: &str,
|
||||||
|
db: &str,
|
||||||
|
ix_what: &Ident,
|
||||||
|
ix_name: &Ident,
|
||||||
|
) -> Self {
|
||||||
|
let full_range = RangeValue {
|
||||||
|
value: Value::None,
|
||||||
|
inclusive: true,
|
||||||
|
};
|
||||||
|
Self::new(irf, ns, db, ix_what, ix_name, &full_range, &full_range)
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_beg(
|
fn compute_beg(
|
||||||
ns: &str,
|
ns: &str,
|
||||||
db: &str,
|
db: &str,
|
||||||
|
@ -329,10 +343,10 @@ impl IndexUnionThingIterator {
|
||||||
db: &str,
|
db: &str,
|
||||||
ix_what: &Ident,
|
ix_what: &Ident,
|
||||||
ix_name: &Ident,
|
ix_name: &Ident,
|
||||||
a: &Array,
|
a: &Value,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// We create a VecDeque to hold the prefix keys (begin and end) for each value in the array.
|
// 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>)> =
|
let mut values: VecDeque<(Vec<u8>, Vec<u8>)> = if let Value::Array(a) = a {
|
||||||
a.0.iter()
|
a.0.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
let a = Array::from(v.clone());
|
let a = Array::from(v.clone());
|
||||||
|
@ -340,7 +354,10 @@ impl IndexUnionThingIterator {
|
||||||
let end = Index::prefix_ids_end(ns, db, ix_what, ix_name, &a);
|
let end = Index::prefix_ids_end(ns, db, ix_what, ix_name, &a);
|
||||||
(beg, end)
|
(beg, end)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
} else {
|
||||||
|
VecDeque::with_capacity(0)
|
||||||
|
};
|
||||||
let current = values.pop_front();
|
let current = values.pop_front();
|
||||||
Self {
|
Self {
|
||||||
irf,
|
irf,
|
||||||
|
@ -561,6 +578,20 @@ impl UniqueRangeThingIterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn full_range(
|
||||||
|
irf: IteratorRef,
|
||||||
|
ns: &str,
|
||||||
|
db: &str,
|
||||||
|
ix_what: &Ident,
|
||||||
|
ix_name: &Ident,
|
||||||
|
) -> Self {
|
||||||
|
let full_range = RangeValue {
|
||||||
|
value: Value::None,
|
||||||
|
inclusive: true,
|
||||||
|
};
|
||||||
|
Self::new(irf, ns, db, ix_what, ix_name, &full_range, &full_range)
|
||||||
|
}
|
||||||
|
|
||||||
fn compute_beg(
|
fn compute_beg(
|
||||||
ns: &str,
|
ns: &str,
|
||||||
db: &str,
|
db: &str,
|
||||||
|
@ -637,17 +668,20 @@ impl UniqueUnionThingIterator {
|
||||||
irf: IteratorRef,
|
irf: IteratorRef,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
ix: &DefineIndexStatement,
|
ix: &DefineIndexStatement,
|
||||||
a: &Array,
|
a: &Value,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
// We create a VecDeque to hold the key for each value in the array.
|
// We create a VecDeque to hold the key for each value in the array.
|
||||||
let keys: VecDeque<Key> =
|
let keys: VecDeque<Key> = if let Value::Array(a) = a {
|
||||||
a.0.iter()
|
a.0.iter()
|
||||||
.map(|v| -> Result<Key, Error> {
|
.map(|v| -> Result<Key, Error> {
|
||||||
let a = Array::from(v.clone());
|
let a = Array::from(v.clone());
|
||||||
let key = Index::new(opt.ns()?, opt.db()?, &ix.what, &ix.name, &a, None).into();
|
let key = Index::new(opt.ns()?, opt.db()?, &ix.what, &ix.name, &a, None).into();
|
||||||
Ok(key)
|
Ok(key)
|
||||||
})
|
})
|
||||||
.collect::<Result<VecDeque<Key>, Error>>()?;
|
.collect::<Result<VecDeque<Key>, Error>>()?
|
||||||
|
} else {
|
||||||
|
VecDeque::with_capacity(0)
|
||||||
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
irf,
|
irf,
|
||||||
keys,
|
keys,
|
||||||
|
|
|
@ -15,7 +15,7 @@ use crate::idx::planner::knn::KnnBruteForceResults;
|
||||||
use crate::idx::planner::plan::{Plan, PlanBuilder};
|
use crate::idx::planner::plan::{Plan, PlanBuilder};
|
||||||
use crate::idx::planner::tree::Tree;
|
use crate::idx::planner::tree::Tree;
|
||||||
use crate::sql::with::With;
|
use crate::sql::with::With;
|
||||||
use crate::sql::{Cond, Table};
|
use crate::sql::{Cond, Orders, Table};
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
@ -25,25 +25,34 @@ pub(crate) struct QueryPlanner {
|
||||||
opt: Arc<Options>,
|
opt: Arc<Options>,
|
||||||
with: Option<Arc<With>>,
|
with: Option<Arc<With>>,
|
||||||
cond: Option<Arc<Cond>>,
|
cond: Option<Arc<Cond>>,
|
||||||
|
order: Option<Arc<Orders>>,
|
||||||
/// There is one executor per table
|
/// There is one executor per table
|
||||||
executors: HashMap<String, QueryExecutor>,
|
executors: HashMap<String, QueryExecutor>,
|
||||||
requires_distinct: bool,
|
requires_distinct: bool,
|
||||||
fallbacks: Vec<String>,
|
fallbacks: Vec<String>,
|
||||||
iteration_workflow: Vec<IterationStage>,
|
iteration_workflow: Vec<IterationStage>,
|
||||||
iteration_index: AtomicU8,
|
iteration_index: AtomicU8,
|
||||||
|
orders: Vec<IteratorRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryPlanner {
|
impl QueryPlanner {
|
||||||
pub(crate) fn new(opt: Arc<Options>, with: Option<Arc<With>>, cond: Option<Arc<Cond>>) -> Self {
|
pub(crate) fn new(
|
||||||
|
opt: Arc<Options>,
|
||||||
|
with: Option<Arc<With>>,
|
||||||
|
cond: Option<Arc<Cond>>,
|
||||||
|
order: Option<Arc<Orders>>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
opt,
|
opt,
|
||||||
with,
|
with,
|
||||||
cond,
|
cond,
|
||||||
|
order,
|
||||||
executors: HashMap::default(),
|
executors: HashMap::default(),
|
||||||
requires_distinct: false,
|
requires_distinct: false,
|
||||||
fallbacks: vec![],
|
fallbacks: vec![],
|
||||||
iteration_workflow: Vec::default(),
|
iteration_workflow: Vec::default(),
|
||||||
iteration_index: AtomicU8::new(0),
|
iteration_index: AtomicU8::new(0),
|
||||||
|
orders: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,73 +64,72 @@ impl QueryPlanner {
|
||||||
it: &mut Iterator,
|
it: &mut Iterator,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut is_table_iterator = false;
|
let mut is_table_iterator = false;
|
||||||
let mut is_knn = false;
|
let mut tree = Tree::build(
|
||||||
match Tree::build(
|
|
||||||
stk,
|
stk,
|
||||||
ctx,
|
ctx,
|
||||||
&self.opt,
|
&self.opt,
|
||||||
&t,
|
&t,
|
||||||
self.cond.as_ref().map(|w| w.as_ref()),
|
self.cond.as_ref().map(|w| w.as_ref()),
|
||||||
self.with.as_ref().map(|c| c.as_ref()),
|
self.with.as_ref().map(|c| c.as_ref()),
|
||||||
|
self.order.as_ref().map(|o| o.as_ref()),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
{
|
|
||||||
Some(tree) => {
|
let is_knn = !tree.knn_expressions.is_empty();
|
||||||
is_knn = is_knn || !tree.knn_expressions.is_empty();
|
let order = tree.index_map.order_limit.take();
|
||||||
let mut exe = InnerQueryExecutor::new(
|
let mut exe = InnerQueryExecutor::new(
|
||||||
stk,
|
stk,
|
||||||
ctx,
|
ctx,
|
||||||
&self.opt,
|
&self.opt,
|
||||||
&t,
|
&t,
|
||||||
tree.index_map,
|
tree.index_map,
|
||||||
tree.knn_expressions,
|
tree.knn_expressions,
|
||||||
tree.knn_brute_force_expressions,
|
tree.knn_brute_force_expressions,
|
||||||
tree.knn_condition,
|
tree.knn_condition,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
match PlanBuilder::build(
|
match PlanBuilder::build(
|
||||||
tree.root,
|
tree.root,
|
||||||
self.with.as_ref().map(|w| w.as_ref()),
|
self.with.as_ref().map(|w| w.as_ref()),
|
||||||
tree.with_indexes,
|
tree.with_indexes,
|
||||||
)? {
|
order,
|
||||||
Plan::SingleIndex(exp, io) => {
|
)? {
|
||||||
if io.require_distinct() {
|
Plan::SingleIndex(exp, io) => {
|
||||||
self.requires_distinct = true;
|
if io.require_distinct() {
|
||||||
}
|
self.requires_distinct = true;
|
||||||
let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
|
}
|
||||||
self.add(t.clone(), Some(ir), exe, it);
|
let is_order = exp.is_none();
|
||||||
}
|
let ir = exe.add_iterator(IteratorEntry::Single(exp, io));
|
||||||
Plan::MultiIndex(non_range_indexes, ranges_indexes) => {
|
self.add(t.clone(), Some(ir), exe, it);
|
||||||
for (exp, io) in non_range_indexes {
|
if is_order {
|
||||||
let ie = IteratorEntry::Single(exp, io);
|
self.orders.push(ir);
|
||||||
let ir = exe.add_iterator(ie);
|
|
||||||
it.ingest(Iterable::Index(t.clone(), ir));
|
|
||||||
}
|
|
||||||
for (ixn, rq) in ranges_indexes {
|
|
||||||
let ie = IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to);
|
|
||||||
let ir = exe.add_iterator(ie);
|
|
||||||
it.ingest(Iterable::Index(t.clone(), ir));
|
|
||||||
}
|
|
||||||
self.requires_distinct = true;
|
|
||||||
self.add(t.clone(), None, exe, it);
|
|
||||||
}
|
|
||||||
Plan::SingleIndexRange(ixn, rq) => {
|
|
||||||
let ir =
|
|
||||||
exe.add_iterator(IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to));
|
|
||||||
self.add(t.clone(), Some(ir), exe, it);
|
|
||||||
}
|
|
||||||
Plan::TableIterator(fallback) => {
|
|
||||||
if let Some(fallback) = fallback {
|
|
||||||
self.fallbacks.push(fallback);
|
|
||||||
}
|
|
||||||
self.add(t.clone(), None, exe, it);
|
|
||||||
it.ingest(Iterable::Table(t));
|
|
||||||
is_table_iterator = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
Plan::MultiIndex(non_range_indexes, ranges_indexes) => {
|
||||||
|
for (exp, io) in non_range_indexes {
|
||||||
|
let ie = IteratorEntry::Single(Some(exp), io);
|
||||||
|
let ir = exe.add_iterator(ie);
|
||||||
|
it.ingest(Iterable::Index(t.clone(), ir));
|
||||||
|
}
|
||||||
|
for (ixr, rq) in ranges_indexes {
|
||||||
|
let ie = IteratorEntry::Range(rq.exps, ixr, rq.from, rq.to);
|
||||||
|
let ir = exe.add_iterator(ie);
|
||||||
|
it.ingest(Iterable::Index(t.clone(), ir));
|
||||||
|
}
|
||||||
|
self.requires_distinct = true;
|
||||||
|
self.add(t.clone(), None, exe, it);
|
||||||
|
}
|
||||||
|
Plan::SingleIndexRange(ixn, rq) => {
|
||||||
|
let ir = exe.add_iterator(IteratorEntry::Range(rq.exps, ixn, rq.from, rq.to));
|
||||||
|
self.add(t.clone(), Some(ir), exe, it);
|
||||||
|
}
|
||||||
|
Plan::TableIterator(fallback) => {
|
||||||
|
if let Some(fallback) = fallback {
|
||||||
|
self.fallbacks.push(fallback);
|
||||||
|
}
|
||||||
|
self.add(t.clone(), None, exe, it);
|
||||||
it.ingest(Iterable::Table(t));
|
it.ingest(Iterable::Table(t));
|
||||||
|
is_table_iterator = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_knn && is_table_iterator {
|
if is_knn && is_table_iterator {
|
||||||
|
@ -160,6 +168,10 @@ impl QueryPlanner {
|
||||||
&self.fallbacks
|
&self.fallbacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_order(&self, irf: &IteratorRef) -> bool {
|
||||||
|
self.orders.contains(irf)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn next_iteration_stage(&self) -> Option<IterationStage> {
|
pub(crate) async fn next_iteration_stage(&self) -> Option<IterationStage> {
|
||||||
let pos = self.iteration_index.fetch_add(1, Ordering::Relaxed);
|
let pos = self.iteration_index.fetch_add(1, Ordering::Relaxed);
|
||||||
match self.iteration_workflow.get(pos as usize) {
|
match self.iteration_workflow.get(pos as usize) {
|
||||||
|
|
|
@ -30,9 +30,10 @@ pub(super) struct PlanBuilder {
|
||||||
|
|
||||||
impl PlanBuilder {
|
impl PlanBuilder {
|
||||||
pub(super) fn build(
|
pub(super) fn build(
|
||||||
root: Node,
|
root: Option<Node>,
|
||||||
with: Option<&With>,
|
with: Option<&With>,
|
||||||
with_indexes: Vec<IndexRef>,
|
with_indexes: Vec<IndexRef>,
|
||||||
|
order: Option<IndexOption>,
|
||||||
) -> Result<Plan, Error> {
|
) -> Result<Plan, Error> {
|
||||||
if let Some(With::NoIndex) = with {
|
if let Some(With::NoIndex) = with {
|
||||||
return Ok(Plan::TableIterator(Some("WITH NOINDEX".to_string())));
|
return Ok(Plan::TableIterator(Some("WITH NOINDEX".to_string())));
|
||||||
|
@ -47,12 +48,10 @@ impl PlanBuilder {
|
||||||
all_exp_with_index: true,
|
all_exp_with_index: true,
|
||||||
};
|
};
|
||||||
// Browse the AST and collect information
|
// Browse the AST and collect information
|
||||||
if let Err(e) = b.eval_node(&root) {
|
if let Some(root) = &root {
|
||||||
return Ok(Plan::TableIterator(Some(e.to_string())));
|
if let Err(e) = b.eval_node(root) {
|
||||||
}
|
return Ok(Plan::TableIterator(Some(e.to_string())));
|
||||||
// If we didn't find any index, we're done with no index plan
|
}
|
||||||
if !b.has_indexes {
|
|
||||||
return Ok(Plan::TableIterator(Some("NO INDEX FOUND".to_string())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If every boolean operator are AND then we can use the single index plan
|
// If every boolean operator are AND then we can use the single index plan
|
||||||
|
@ -66,7 +65,11 @@ impl PlanBuilder {
|
||||||
}
|
}
|
||||||
// Otherwise we take the first single index option
|
// Otherwise we take the first single index option
|
||||||
if let Some((e, i)) = b.non_range_indexes.pop() {
|
if let Some((e, i)) = b.non_range_indexes.pop() {
|
||||||
return Ok(Plan::SingleIndex(e, i));
|
return Ok(Plan::SingleIndex(Some(e), i));
|
||||||
|
}
|
||||||
|
// If there is an order option
|
||||||
|
if let Some(o) = order {
|
||||||
|
return Ok(Plan::SingleIndex(None, o.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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
|
||||||
|
@ -157,15 +160,19 @@ impl PlanBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) enum Plan {
|
pub(super) enum Plan {
|
||||||
|
/// Table full scan
|
||||||
TableIterator(Option<String>),
|
TableIterator(Option<String>),
|
||||||
SingleIndex(Arc<Expression>, IndexOption),
|
/// Index scan filtered on records matching a given expression
|
||||||
|
SingleIndex(Option<Arc<Expression>>, IndexOption),
|
||||||
|
/// Union of filtered index scans
|
||||||
MultiIndex(Vec<(Arc<Expression>, IndexOption)>, Vec<(IndexRef, UnionRangeQueryBuilder)>),
|
MultiIndex(Vec<(Arc<Expression>, IndexOption)>, Vec<(IndexRef, UnionRangeQueryBuilder)>),
|
||||||
|
/// Index scan for record matching a given range
|
||||||
SingleIndexRange(IndexRef, UnionRangeQueryBuilder),
|
SingleIndexRange(IndexRef, UnionRangeQueryBuilder),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||||
pub(super) struct IndexOption {
|
pub(super) struct IndexOption {
|
||||||
/// A reference o the index definition
|
/// A reference to the index definition
|
||||||
ix_ref: IndexRef,
|
ix_ref: IndexRef,
|
||||||
id: Idiom,
|
id: Idiom,
|
||||||
id_pos: IdiomPosition,
|
id_pos: IdiomPosition,
|
||||||
|
@ -174,14 +181,15 @@ pub(super) struct IndexOption {
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||||
pub(super) enum IndexOperator {
|
pub(super) enum IndexOperator {
|
||||||
Equality(Value),
|
Equality(Arc<Value>),
|
||||||
Exactness(Value),
|
Exactness(Arc<Value>),
|
||||||
Union(Array),
|
Union(Arc<Value>),
|
||||||
Join(Vec<IndexOption>),
|
Join(Vec<IndexOption>),
|
||||||
RangePart(Operator, Value),
|
RangePart(Operator, Arc<Value>),
|
||||||
Matches(String, Option<MatchRef>),
|
Matches(String, Option<MatchRef>),
|
||||||
Knn(Arc<Vec<Number>>, u32),
|
Knn(Arc<Vec<Number>>, u32),
|
||||||
Ann(Arc<Vec<Number>>, u32, u32),
|
Ann(Arc<Vec<Number>>, u32, u32),
|
||||||
|
Order(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexOption {
|
impl IndexOption {
|
||||||
|
@ -242,9 +250,9 @@ impl IndexOption {
|
||||||
e.insert("operator", Value::from(Operator::Exact.to_string()));
|
e.insert("operator", Value::from(Operator::Exact.to_string()));
|
||||||
e.insert("value", Self::reduce_array(v));
|
e.insert("value", Self::reduce_array(v));
|
||||||
}
|
}
|
||||||
IndexOperator::Union(a) => {
|
IndexOperator::Union(v) => {
|
||||||
e.insert("operator", Value::from("union"));
|
e.insert("operator", Value::from("union"));
|
||||||
e.insert("value", Value::Array(a.clone()));
|
e.insert("value", v.as_ref().clone());
|
||||||
}
|
}
|
||||||
IndexOperator::Join(ios) => {
|
IndexOperator::Join(ios) => {
|
||||||
e.insert("operator", Value::from("join"));
|
e.insert("operator", Value::from("join"));
|
||||||
|
@ -261,7 +269,7 @@ impl IndexOption {
|
||||||
}
|
}
|
||||||
IndexOperator::RangePart(op, v) => {
|
IndexOperator::RangePart(op, v) => {
|
||||||
e.insert("operator", Value::from(op.to_string()));
|
e.insert("operator", Value::from(op.to_string()));
|
||||||
e.insert("value", v.to_owned());
|
e.insert("value", v.as_ref().to_owned());
|
||||||
}
|
}
|
||||||
IndexOperator::Knn(a, k) => {
|
IndexOperator::Knn(a, k) => {
|
||||||
let op = Value::from(Operator::Knn(*k, None).to_string());
|
let op = Value::from(Operator::Knn(*k, None).to_string());
|
||||||
|
@ -275,6 +283,10 @@ impl IndexOption {
|
||||||
e.insert("operator", op);
|
e.insert("operator", op);
|
||||||
e.insert("value", val);
|
e.insert("value", val);
|
||||||
}
|
}
|
||||||
|
IndexOperator::Order(asc) => {
|
||||||
|
e.insert("operator", Value::from("Order"));
|
||||||
|
e.insert("ascending", Value::from(*asc));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Value::from(e)
|
Value::from(e)
|
||||||
}
|
}
|
||||||
|
@ -443,14 +455,14 @@ mod tests {
|
||||||
1,
|
1,
|
||||||
Idiom::parse("test"),
|
Idiom::parse("test"),
|
||||||
IdiomPosition::Right,
|
IdiomPosition::Right,
|
||||||
IndexOperator::Equality(Value::Array(Array::from(vec!["test"]))),
|
IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let io2 = IndexOption::new(
|
let io2 = IndexOption::new(
|
||||||
1,
|
1,
|
||||||
Idiom::parse("test"),
|
Idiom::parse("test"),
|
||||||
IdiomPosition::Right,
|
IdiomPosition::Right,
|
||||||
IndexOperator::Equality(Value::Array(Array::from(vec!["test"]))),
|
IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()),
|
||||||
);
|
);
|
||||||
|
|
||||||
set.insert(io1);
|
set.insert(io1);
|
||||||
|
|
|
@ -10,14 +10,15 @@ use crate::kvs::Transaction;
|
||||||
use crate::sql::index::Index;
|
use crate::sql::index::Index;
|
||||||
use crate::sql::statements::{DefineFieldStatement, DefineIndexStatement};
|
use crate::sql::statements::{DefineFieldStatement, DefineIndexStatement};
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
Array, Cond, Expression, Idiom, Kind, Number, Operator, Part, Subquery, Table, Value, With,
|
Array, Cond, Expression, Idiom, Kind, Number, Operator, Order, Orders, Part, Subquery, Table,
|
||||||
|
Value, With,
|
||||||
};
|
};
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
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) root: Option<Node>,
|
||||||
pub(super) index_map: IndexesMap,
|
pub(super) index_map: IndexesMap,
|
||||||
pub(super) with_indexes: Vec<IndexRef>,
|
pub(super) with_indexes: Vec<IndexRef>,
|
||||||
pub(super) knn_expressions: KnnExpressions,
|
pub(super) knn_expressions: KnnExpressions,
|
||||||
|
@ -35,26 +36,21 @@ impl Tree {
|
||||||
table: &'a Table,
|
table: &'a Table,
|
||||||
cond: Option<&Cond>,
|
cond: Option<&Cond>,
|
||||||
with: Option<&With>,
|
with: Option<&With>,
|
||||||
) -> Result<Option<Self>, Error> {
|
order: Option<&Orders>,
|
||||||
let mut b = TreeBuilder::new(ctx, opt, table, with);
|
) -> Result<Self, Error> {
|
||||||
|
let mut b = TreeBuilder::new(ctx, opt, table, with, order);
|
||||||
if let Some(cond) = cond {
|
if let Some(cond) = cond {
|
||||||
let root = b.eval_value(stk, 0, &cond.0).await?;
|
b.eval_cond(stk, cond).await?;
|
||||||
let knn_condition = if b.knn_expressions.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
KnnConditionRewriter::build(&b.knn_expressions, cond)
|
|
||||||
};
|
|
||||||
Ok(Some(Self {
|
|
||||||
root,
|
|
||||||
index_map: b.index_map,
|
|
||||||
with_indexes: b.with_indexes,
|
|
||||||
knn_expressions: b.knn_expressions,
|
|
||||||
knn_brute_force_expressions: b.knn_brute_force_expressions,
|
|
||||||
knn_condition,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
b.eval_order().await?;
|
||||||
|
Ok(Self {
|
||||||
|
root: b.root,
|
||||||
|
index_map: b.index_map,
|
||||||
|
with_indexes: b.with_indexes,
|
||||||
|
knn_expressions: b.knn_expressions,
|
||||||
|
knn_brute_force_expressions: b.knn_brute_force_expressions,
|
||||||
|
knn_condition: b.knn_condition,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +59,7 @@ struct TreeBuilder<'a> {
|
||||||
opt: &'a Options,
|
opt: &'a Options,
|
||||||
table: &'a Table,
|
table: &'a Table,
|
||||||
with: Option<&'a With>,
|
with: Option<&'a With>,
|
||||||
|
first_order: Option<&'a Order>,
|
||||||
schemas: HashMap<Table, SchemaCache>,
|
schemas: HashMap<Table, SchemaCache>,
|
||||||
idioms_indexes: HashMap<Table, HashMap<Idiom, LocalIndexRefs>>,
|
idioms_indexes: HashMap<Table, HashMap<Idiom, LocalIndexRefs>>,
|
||||||
resolved_expressions: HashMap<Arc<Expression>, ResolvedExpression>,
|
resolved_expressions: HashMap<Arc<Expression>, ResolvedExpression>,
|
||||||
|
@ -73,6 +70,8 @@ struct TreeBuilder<'a> {
|
||||||
knn_expressions: KnnExpressions,
|
knn_expressions: KnnExpressions,
|
||||||
idioms_record_options: HashMap<Idiom, RecordOptions>,
|
idioms_record_options: HashMap<Idiom, RecordOptions>,
|
||||||
group_sequence: GroupRef,
|
group_sequence: GroupRef,
|
||||||
|
root: Option<Node>,
|
||||||
|
knn_condition: Option<Cond>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
@ -85,16 +84,28 @@ pub(super) type LocalIndexRefs = Vec<IndexRef>;
|
||||||
pub(super) type RemoteIndexRefs = Arc<Vec<(Idiom, LocalIndexRefs)>>;
|
pub(super) type RemoteIndexRefs = Arc<Vec<(Idiom, LocalIndexRefs)>>;
|
||||||
|
|
||||||
impl<'a> TreeBuilder<'a> {
|
impl<'a> TreeBuilder<'a> {
|
||||||
fn new(ctx: &'a Context, opt: &'a Options, table: &'a Table, with: Option<&'a With>) -> Self {
|
fn new(
|
||||||
|
ctx: &'a Context,
|
||||||
|
opt: &'a Options,
|
||||||
|
table: &'a Table,
|
||||||
|
with: Option<&'a With>,
|
||||||
|
orders: Option<&'a Orders>,
|
||||||
|
) -> Self {
|
||||||
let with_indexes = match with {
|
let with_indexes = match with {
|
||||||
Some(With::Index(ixs)) => Vec::with_capacity(ixs.len()),
|
Some(With::Index(ixs)) => Vec::with_capacity(ixs.len()),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
|
let first_order = if let Some(o) = orders {
|
||||||
|
o.0.first()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
ctx,
|
ctx,
|
||||||
opt,
|
opt,
|
||||||
table,
|
table,
|
||||||
with,
|
with,
|
||||||
|
first_order,
|
||||||
schemas: Default::default(),
|
schemas: Default::default(),
|
||||||
idioms_indexes: Default::default(),
|
idioms_indexes: Default::default(),
|
||||||
resolved_expressions: Default::default(),
|
resolved_expressions: Default::default(),
|
||||||
|
@ -105,6 +116,8 @@ impl<'a> TreeBuilder<'a> {
|
||||||
knn_expressions: Default::default(),
|
knn_expressions: Default::default(),
|
||||||
idioms_record_options: Default::default(),
|
idioms_record_options: Default::default(),
|
||||||
group_sequence: 0,
|
group_sequence: 0,
|
||||||
|
root: None,
|
||||||
|
knn_condition: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +134,34 @@ impl<'a> TreeBuilder<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Was marked recursive
|
async fn eval_order(&mut self) -> Result<(), Error> {
|
||||||
|
if let Some(o) = self.first_order {
|
||||||
|
if !o.random {
|
||||||
|
if let Node::IndexedField(id, irf) = self.resolve_idiom(&o.order).await? {
|
||||||
|
if let Some(ix_ref) = irf.first().cloned() {
|
||||||
|
self.index_map.order_limit = Some(IndexOption::new(
|
||||||
|
ix_ref,
|
||||||
|
id,
|
||||||
|
IdiomPosition::None,
|
||||||
|
IndexOperator::Order(o.direction),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn eval_cond(&mut self, stk: &mut Stk, cond: &Cond) -> Result<(), Error> {
|
||||||
|
self.root = Some(self.eval_value(stk, 0, &cond.0).await?);
|
||||||
|
self.knn_condition = if self.knn_expressions.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
KnnConditionRewriter::build(&self.knn_expressions, cond)
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn eval_value(
|
async fn eval_value(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -141,6 +181,8 @@ impl<'a> TreeBuilder<'a> {
|
||||||
| Value::Geometry(_)
|
| Value::Geometry(_)
|
||||||
| Value::Datetime(_)
|
| Value::Datetime(_)
|
||||||
| Value::Param(_)
|
| Value::Param(_)
|
||||||
|
| Value::Null
|
||||||
|
| Value::None
|
||||||
| Value::Function(_) => Ok(Node::Computable),
|
| Value::Function(_) => Ok(Node::Computable),
|
||||||
Value::Array(a) => self.eval_array(stk, a).await,
|
Value::Array(a) => self.eval_array(stk, a).await,
|
||||||
Value::Subquery(s) => self.eval_subquery(stk, s).await,
|
Value::Subquery(s) => self.eval_subquery(stk, s).await,
|
||||||
|
@ -151,7 +193,7 @@ impl<'a> TreeBuilder<'a> {
|
||||||
async fn compute(&self, stk: &mut Stk, v: &Value, n: Node) -> Result<Node, Error> {
|
async fn compute(&self, stk: &mut Stk, v: &Value, n: Node) -> Result<Node, Error> {
|
||||||
Ok(if n == Node::Computable {
|
Ok(if n == Node::Computable {
|
||||||
match v.compute(stk, self.ctx, self.opt, None).await {
|
match v.compute(stk, self.ctx, self.opt, None).await {
|
||||||
Ok(v) => Node::Computed(Arc::new(v)),
|
Ok(v) => Node::Computed(v.into()),
|
||||||
Err(_) => Node::Unsupported(format!("Unsupported value: {}", v)),
|
Err(_) => Node::Unsupported(format!("Unsupported value: {}", v)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -187,8 +229,6 @@ impl<'a> TreeBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = self.resolve_idiom(i).await?;
|
let n = self.resolve_idiom(i).await?;
|
||||||
self.resolved_idioms.insert(i.clone(), n.clone());
|
|
||||||
|
|
||||||
Ok(n)
|
Ok(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,17 +237,23 @@ impl<'a> TreeBuilder<'a> {
|
||||||
self.lazy_load_schema_resolver(&tx, self.table).await?;
|
self.lazy_load_schema_resolver(&tx, self.table).await?;
|
||||||
|
|
||||||
// Try to detect if it matches an index
|
// Try to detect if it matches an index
|
||||||
if let Some(schema) = self.schemas.get(self.table).cloned() {
|
let n = if let Some(schema) = self.schemas.get(self.table).cloned() {
|
||||||
let irs = self.resolve_indexes(self.table, i, &schema);
|
let irs = self.resolve_indexes(self.table, i, &schema);
|
||||||
if !irs.is_empty() {
|
if !irs.is_empty() {
|
||||||
return Ok(Node::IndexedField(i.clone(), irs));
|
Node::IndexedField(i.clone(), irs)
|
||||||
|
} else if let Some(ro) =
|
||||||
|
self.resolve_record_field(&tx, schema.fields.as_ref(), i).await?
|
||||||
|
{
|
||||||
|
// Try to detect an indexed record field
|
||||||
|
Node::RecordField(i.clone(), ro)
|
||||||
|
} else {
|
||||||
|
Node::NonIndexedField(i.clone())
|
||||||
}
|
}
|
||||||
// Try to detect an indexed record field
|
} else {
|
||||||
if let Some(ro) = self.resolve_record_field(&tx, schema.fields.as_ref(), i).await? {
|
Node::NonIndexedField(i.clone())
|
||||||
return Ok(Node::RecordField(i.clone(), ro));
|
};
|
||||||
}
|
self.resolved_idioms.insert(i.clone(), n.clone());
|
||||||
}
|
Ok(n)
|
||||||
Ok(Node::NonIndexedField(i.clone()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_indexes(&mut self, t: &Table, i: &Idiom, schema: &SchemaCache) -> Vec<IndexRef> {
|
fn resolve_indexes(&mut self, t: &Table, i: &Idiom, schema: &SchemaCache) -> Vec<IndexRef> {
|
||||||
|
@ -392,8 +438,8 @@ impl<'a> TreeBuilder<'a> {
|
||||||
for ir in irs {
|
for ir in irs {
|
||||||
if let Some(ix) = self.index_map.definitions.get(*ir as usize) {
|
if let Some(ix) = self.index_map.definitions.get(*ir as usize) {
|
||||||
let op = match &ix.index {
|
let op = match &ix.index {
|
||||||
Index::Idx => Self::eval_index_operator(op, n, p),
|
Index::Idx => self.eval_index_operator(op, n, p),
|
||||||
Index::Uniq => Self::eval_index_operator(op, n, p),
|
Index::Uniq => self.eval_index_operator(op, n, p),
|
||||||
Index::Search {
|
Index::Search {
|
||||||
..
|
..
|
||||||
} => Self::eval_matches_operator(op, n),
|
} => Self::eval_matches_operator(op, n),
|
||||||
|
@ -425,7 +471,7 @@ impl<'a> TreeBuilder<'a> {
|
||||||
fn eval_matches_operator(op: &Operator, n: &Node) -> Option<IndexOperator> {
|
fn eval_matches_operator(op: &Operator, n: &Node) -> Option<IndexOperator> {
|
||||||
if let Some(v) = n.is_computed() {
|
if let Some(v) = n.is_computed() {
|
||||||
if let Operator::Matches(mr) = op {
|
if let Operator::Matches(mr) = op {
|
||||||
return Some(IndexOperator::Matches(v.clone().to_raw_string(), *mr));
|
return Some(IndexOperator::Matches(v.to_raw_string(), *mr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
@ -482,22 +528,31 @@ impl<'a> TreeBuilder<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_index_operator(op: &Operator, n: &Node, p: IdiomPosition) -> Option<IndexOperator> {
|
fn eval_index_operator(
|
||||||
|
&self,
|
||||||
|
op: &Operator,
|
||||||
|
n: &Node,
|
||||||
|
p: IdiomPosition,
|
||||||
|
) -> Option<IndexOperator> {
|
||||||
if let Some(v) = n.is_computed() {
|
if let Some(v) = n.is_computed() {
|
||||||
match (op, v, p) {
|
match (op, v, p) {
|
||||||
(Operator::Equal, v, _) => Some(IndexOperator::Equality(v.clone())),
|
(Operator::Equal, v, _) => return Some(IndexOperator::Equality(v)),
|
||||||
(Operator::Exact, v, _) => Some(IndexOperator::Exactness(v.clone())),
|
(Operator::Exact, v, _) => return Some(IndexOperator::Exactness(v)),
|
||||||
(Operator::Contain, v, IdiomPosition::Left) => {
|
(Operator::Contain, v, IdiomPosition::Left) => {
|
||||||
Some(IndexOperator::Equality(v.clone()))
|
return Some(IndexOperator::Equality(v))
|
||||||
}
|
}
|
||||||
(Operator::Inside, v, IdiomPosition::Right) => {
|
(Operator::Inside, v, IdiomPosition::Right) => {
|
||||||
Some(IndexOperator::Equality(v.clone()))
|
return Some(IndexOperator::Equality(v))
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
Operator::ContainAny | Operator::ContainAll | Operator::Inside,
|
Operator::ContainAny | Operator::ContainAll | Operator::Inside,
|
||||||
Value::Array(a),
|
v,
|
||||||
IdiomPosition::Left,
|
IdiomPosition::Left,
|
||||||
) => Some(IndexOperator::Union(a.clone())),
|
) => {
|
||||||
|
if let Value::Array(_) = v.as_ref() {
|
||||||
|
return Some(IndexOperator::Union(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
(
|
(
|
||||||
Operator::LessThan
|
Operator::LessThan
|
||||||
| Operator::LessThanOrEqual
|
| Operator::LessThanOrEqual
|
||||||
|
@ -505,12 +560,11 @@ impl<'a> TreeBuilder<'a> {
|
||||||
| Operator::MoreThanOrEqual,
|
| Operator::MoreThanOrEqual,
|
||||||
v,
|
v,
|
||||||
p,
|
p,
|
||||||
) => Some(IndexOperator::RangePart(p.transform(op), v.clone())),
|
) => return Some(IndexOperator::RangePart(p.transform(op), v)),
|
||||||
_ => None,
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn eval_subquery(&mut self, stk: &mut Stk, s: &Subquery) -> Result<Node, Error> {
|
async fn eval_subquery(&mut self, stk: &mut Stk, s: &Subquery) -> Result<Node, Error> {
|
||||||
|
@ -528,6 +582,7 @@ pub(super) type IndexRef = u16;
|
||||||
pub(super) struct IndexesMap {
|
pub(super) struct IndexesMap {
|
||||||
pub(super) options: Vec<(Arc<Expression>, IndexOption)>,
|
pub(super) options: Vec<(Arc<Expression>, IndexOption)>,
|
||||||
pub(super) definitions: Vec<DefineIndexStatement>,
|
pub(super) definitions: Vec<DefineIndexStatement>,
|
||||||
|
pub(super) order_limit: Option<IndexOption>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -567,9 +622,9 @@ pub(super) enum Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
pub(super) fn is_computed(&self) -> Option<&Value> {
|
pub(super) fn is_computed(&self) -> Option<Arc<Value>> {
|
||||||
if let Node::Computed(v) = self {
|
if let Self::Computed(v) = self {
|
||||||
Some(v)
|
Some(v.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -579,17 +634,17 @@ impl Node {
|
||||||
&self,
|
&self,
|
||||||
) -> Option<(&Idiom, LocalIndexRefs, Option<RemoteIndexRefs>)> {
|
) -> Option<(&Idiom, LocalIndexRefs, Option<RemoteIndexRefs>)> {
|
||||||
match self {
|
match self {
|
||||||
Node::IndexedField(id, irs) => Some((id, irs.clone(), None)),
|
Self::IndexedField(id, irs) => Some((id, irs.clone(), None)),
|
||||||
Node::RecordField(id, ro) => Some((id, ro.locals.clone(), Some(ro.remotes.clone()))),
|
Self::RecordField(id, ro) => Some((id, ro.locals.clone(), Some(ro.remotes.clone()))),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_field(&self) -> Option<&Idiom> {
|
pub(super) fn is_field(&self) -> Option<&Idiom> {
|
||||||
match self {
|
match self {
|
||||||
Node::IndexedField(id, _) => Some(id),
|
Self::IndexedField(id, _) => Some(id),
|
||||||
Node::RecordField(id, _) => Some(id),
|
Self::RecordField(id, _) => Some(id),
|
||||||
Node::NonIndexedField(id) => Some(id),
|
Self::NonIndexedField(id) => Some(id),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -597,8 +652,12 @@ impl Node {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||||
pub(super) enum IdiomPosition {
|
pub(super) enum IdiomPosition {
|
||||||
|
/// The idiom is on the left of the condition clause
|
||||||
Left,
|
Left,
|
||||||
|
/// The idiom is on the right tf the condition clause
|
||||||
Right,
|
Right,
|
||||||
|
/// Eg. ORDER LIMIT
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IdiomPosition {
|
impl IdiomPosition {
|
||||||
|
@ -613,6 +672,7 @@ impl IdiomPosition {
|
||||||
Operator::MoreThanOrEqual => Operator::LessThanOrEqual,
|
Operator::MoreThanOrEqual => Operator::LessThanOrEqual,
|
||||||
_ => op.clone(),
|
_ => op.clone(),
|
||||||
},
|
},
|
||||||
|
IdiomPosition::None => op.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ use crate::sql::id::Id;
|
||||||
use derive::Key;
|
use derive::Key;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::Range;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Key)]
|
||||||
struct Prefix<'a> {
|
struct Prefix<'a> {
|
||||||
|
@ -156,10 +155,6 @@ impl<'a> Index<'a> {
|
||||||
beg
|
beg
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range(ns: &str, db: &str, tb: &str, ix: &str) -> Range<Vec<u8>> {
|
|
||||||
Self::prefix_beg(ns, db, tb, ix)..Self::prefix_end(ns, db, tb, ix)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prefix_ids(ns: &str, db: &str, tb: &str, ix: &str, fd: &Array) -> Vec<u8> {
|
fn prefix_ids(ns: &str, db: &str, tb: &str, ix: &str, fd: &Array) -> Vec<u8> {
|
||||||
PrefixIds::new(ns, db, tb, ix, fd).encode().unwrap()
|
PrefixIds::new(ns, db, tb, ix, fd).encode().unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,10 +22,18 @@ impl Limit {
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
doc: Option<&CursorDoc>,
|
doc: Option<&CursorDoc>,
|
||||||
) -> Result<usize, Error> {
|
) -> Result<u32, Error> {
|
||||||
match self.0.compute(stk, ctx, opt, doc).await {
|
match self.0.compute(stk, ctx, opt, doc).await {
|
||||||
// This is a valid limiting number
|
// This is a valid limiting number
|
||||||
Ok(Value::Number(Number::Int(v))) if v >= 0 => Ok(v as usize),
|
Ok(Value::Number(Number::Int(v))) if v >= 0 => {
|
||||||
|
if v > u32::MAX as i64 {
|
||||||
|
Err(Error::InvalidLimit {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(v as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
// An invalid value was specified
|
// An invalid value was specified
|
||||||
Ok(v) => Err(Error::InvalidLimit {
|
Ok(v) => Err(Error::InvalidLimit {
|
||||||
value: v.as_string(),
|
value: v.as_string(),
|
||||||
|
|
|
@ -22,10 +22,18 @@ impl Start {
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
doc: Option<&CursorDoc>,
|
doc: Option<&CursorDoc>,
|
||||||
) -> Result<usize, Error> {
|
) -> Result<u32, Error> {
|
||||||
match self.0.compute(stk, ctx, opt, doc).await {
|
match self.0.compute(stk, ctx, opt, doc).await {
|
||||||
// This is a valid starting number
|
// This is a valid starting number
|
||||||
Ok(Value::Number(Number::Int(v))) if v >= 0 => Ok(v as usize),
|
Ok(Value::Number(Number::Int(v))) if v >= 0 => {
|
||||||
|
if v > u32::MAX as i64 {
|
||||||
|
Err(Error::InvalidStart {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(v as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
// An invalid value was specified
|
// An invalid value was specified
|
||||||
Ok(v) => Err(Error::InvalidStart {
|
Ok(v) => Err(Error::InvalidStart {
|
||||||
value: v.as_string(),
|
value: v.as_string(),
|
||||||
|
|
|
@ -68,22 +68,26 @@ impl SelectStatement {
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Valid options?
|
// Valid options?
|
||||||
opt.valid_for_db()?;
|
opt.valid_for_db()?;
|
||||||
|
// Assign the statement
|
||||||
|
let stm = Statement::from(self);
|
||||||
// Create a new iterator
|
// Create a new iterator
|
||||||
let mut i = Iterator::new();
|
let mut i = Iterator::new();
|
||||||
// Ensure futures are stored and the version is set if specified
|
// Ensure futures are stored and the version is set if specified
|
||||||
let version = self.version.as_ref().map(|v| v.to_u64());
|
let version = self.version.as_ref().map(|v| v.to_u64());
|
||||||
let opt =
|
let opt =
|
||||||
Arc::new(opt.new_with_futures(false).with_projections(true).with_version(version));
|
Arc::new(opt.new_with_futures(false).with_projections(true).with_version(version));
|
||||||
//;
|
|
||||||
// Get a query planner
|
// Get a query planner
|
||||||
let mut planner = QueryPlanner::new(
|
let mut planner = QueryPlanner::new(
|
||||||
opt.clone(),
|
opt.clone(),
|
||||||
self.with.as_ref().cloned().map(|w| w.into()),
|
self.with.as_ref().cloned().map(|w| w.into()),
|
||||||
self.cond.as_ref().cloned().map(|c| c.into()),
|
self.cond.as_ref().cloned().map(|c| c.into()),
|
||||||
|
self.order.as_ref().cloned().map(|o| o.into()),
|
||||||
);
|
);
|
||||||
|
// Extract the limit
|
||||||
|
let limit = i.setup_limit(stk, ctx, &opt, &stm).await?;
|
||||||
// Used for ONLY: is the limit 1?
|
// Used for ONLY: is the limit 1?
|
||||||
let limit_is_one_or_zero = match &self.limit {
|
let limit_is_one_or_zero = match limit {
|
||||||
Some(l) => l.process(stk, ctx, &opt, doc).await? <= 1,
|
Some(l) => l <= 1,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
// Fail for multiple targets without a limit
|
// Fail for multiple targets without a limit
|
||||||
|
@ -98,7 +102,6 @@ impl SelectStatement {
|
||||||
if self.only && !limit_is_one_or_zero {
|
if self.only && !limit_is_one_or_zero {
|
||||||
return Err(Error::SingleOnlyOutput);
|
return Err(Error::SingleOnlyOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
planner.add_iterables(stk, ctx, t, &mut i).await?;
|
planner.add_iterables(stk, ctx, t, &mut i).await?;
|
||||||
}
|
}
|
||||||
Value::Thing(v) => match &v.id {
|
Value::Thing(v) => match &v.id {
|
||||||
|
@ -147,8 +150,6 @@ impl SelectStatement {
|
||||||
}
|
}
|
||||||
// Create a new context
|
// Create a new context
|
||||||
let mut ctx = MutableContext::new(ctx);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
// Assign the statement
|
|
||||||
let stm = Statement::from(self);
|
|
||||||
// Add query executors if any
|
// Add query executors if any
|
||||||
if planner.has_executors() {
|
if planner.has_executors() {
|
||||||
ctx.set_query_planner(planner);
|
ctx.set_query_planner(planner);
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod parse;
|
||||||
|
|
||||||
use parse::Parse;
|
use parse::Parse;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
use crate::helpers::Test;
|
||||||
use helpers::{new_ds, skip_ok};
|
use helpers::{new_ds, skip_ok};
|
||||||
use surrealdb::dbs::{Response, Session};
|
use surrealdb::dbs::{Response, Session};
|
||||||
use surrealdb::err::Error;
|
use surrealdb::err::Error;
|
||||||
|
@ -1047,12 +1048,6 @@ const CONTAINS_TABLE_EXPLAIN: &str = r"[
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
@ -1953,12 +1948,6 @@ async fn select_with_record_id_link_no_index() -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
@ -2256,12 +2245,6 @@ async fn select_with_record_id_link_full_text_no_record_index() -> Result<(), Er
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
@ -2328,12 +2311,6 @@ async fn select_with_record_id_index() -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
@ -2600,3 +2577,205 @@ async fn select_with_non_boolean_expression() -> Result<(), Error> {
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_from_standard_index_ascending() -> Result<(), Error> {
|
||||||
|
//
|
||||||
|
let sql = "
|
||||||
|
DEFINE INDEX time ON TABLE session COLUMNS time;
|
||||||
|
CREATE session:1 SET time = d'2024-07-01T01:00:00Z';
|
||||||
|
CREATE session:2 SET time = d'2024-06-30T23:00:00Z';
|
||||||
|
CREATE session:3 SET other = 'test';
|
||||||
|
CREATE session:4 SET time = null;
|
||||||
|
CREATE session:5 SET time = d'2024-07-01T02:00:00Z';
|
||||||
|
CREATE session:6 SET time = d'2024-06-30T23:30:00Z';
|
||||||
|
SELECT * FROM session ORDER BY time ASC LIMIT 4 EXPLAIN;
|
||||||
|
SELECT * FROM session ORDER BY time ASC LIMIT 4;
|
||||||
|
SELECT * FROM session ORDER BY time ASC EXPLAIN;
|
||||||
|
SELECT * FROM session ORDER BY time ASC;
|
||||||
|
";
|
||||||
|
let mut t = Test::new(sql).await?;
|
||||||
|
t.skip_ok(7)?;
|
||||||
|
//
|
||||||
|
t.expect_vals(&vec![
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
ascending: true,
|
||||||
|
index: 'time',
|
||||||
|
operator: 'Order'
|
||||||
|
},
|
||||||
|
table: 'session'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
type: 'Memory'
|
||||||
|
},
|
||||||
|
operation: 'Collector'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: session:3,
|
||||||
|
other: 'test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:4,
|
||||||
|
time: NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:2,
|
||||||
|
time: d'2024-06-30T23:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:6,
|
||||||
|
time: d'2024-06-30T23:30:00Z'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
ascending: true,
|
||||||
|
index: 'time',
|
||||||
|
operator: 'Order'
|
||||||
|
},
|
||||||
|
table: 'session'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
type: 'Memory'
|
||||||
|
},
|
||||||
|
operation: 'Collector'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: session:3,
|
||||||
|
other: 'test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:4,
|
||||||
|
time: NULL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:2,
|
||||||
|
time: d'2024-06-30T23:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:6,
|
||||||
|
time: d'2024-06-30T23:30:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:1,
|
||||||
|
time: d'2024-07-01T01:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:5,
|
||||||
|
time: d'2024-07-01T02:00:00Z'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
])?;
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn select_from_unique_index_ascending() -> Result<(), Error> {
|
||||||
|
//
|
||||||
|
let sql = "
|
||||||
|
DEFINE INDEX time ON TABLE session COLUMNS time UNIQUE;
|
||||||
|
CREATE session:1 SET time = d'2024-07-01T01:00:00Z';
|
||||||
|
CREATE session:2 SET time = d'2024-06-30T23:00:00Z';
|
||||||
|
CREATE session:3 SET other = 'test';
|
||||||
|
CREATE session:4 SET time = null;
|
||||||
|
CREATE session:5 SET time = d'2024-07-01T02:00:00Z';
|
||||||
|
CREATE session:6 SET time = d'2024-06-30T23:30:00Z';
|
||||||
|
SELECT * FROM session ORDER BY time ASC LIMIT 3 EXPLAIN;
|
||||||
|
SELECT * FROM session ORDER BY time ASC LIMIT 3;
|
||||||
|
SELECT * FROM session ORDER BY time ASC EXPLAIN;
|
||||||
|
SELECT * FROM session ORDER BY time ASC;
|
||||||
|
";
|
||||||
|
let mut t = Test::new(sql).await?;
|
||||||
|
t.skip_ok(7)?;
|
||||||
|
//
|
||||||
|
t.expect_vals(&vec![
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
ascending: true,
|
||||||
|
index: 'time',
|
||||||
|
operator: 'Order'
|
||||||
|
},
|
||||||
|
table: 'session'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
type: 'Memory'
|
||||||
|
},
|
||||||
|
operation: 'Collector'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: session:2,
|
||||||
|
time: d'2024-06-30T23:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:6,
|
||||||
|
time: d'2024-06-30T23:30:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:1,
|
||||||
|
time: d'2024-07-01T01:00:00Z'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
plan: {
|
||||||
|
ascending: true,
|
||||||
|
index: 'time',
|
||||||
|
operator: 'Order'
|
||||||
|
},
|
||||||
|
table: 'session'
|
||||||
|
},
|
||||||
|
operation: 'Iterate Index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
type: 'Memory'
|
||||||
|
},
|
||||||
|
operation: 'Collector'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: session:2,
|
||||||
|
time: d'2024-06-30T23:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:6,
|
||||||
|
time: d'2024-06-30T23:30:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:1,
|
||||||
|
time: d'2024-07-01T01:00:00Z'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: session:5,
|
||||||
|
time: d'2024-07-01T02:00:00Z'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
])?;
|
||||||
|
//
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -181,12 +181,6 @@ async fn select_where_brute_force_knn() -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
@ -288,12 +282,6 @@ async fn select_where_hnsw_knn() -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
@ -485,12 +473,6 @@ async fn select_bruteforce_knn_with_condition() -> Result<(), Error> {
|
||||||
},
|
},
|
||||||
operation: 'Iterate Table'
|
operation: 'Iterate Table'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
detail: {
|
|
||||||
reason: 'NO INDEX FOUND'
|
|
||||||
},
|
|
||||||
operation: 'Fallback'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
detail: {
|
detail: {
|
||||||
type: 'Memory'
|
type: 'Memory'
|
||||||
|
|
Loading…
Reference in a new issue