From 9cfdd34fc015d5a104216648d820e93dbc8b26e5 Mon Sep 17 00:00:00 2001 From: Emmanuel Keller Date: Mon, 16 Sep 2024 14:17:20 +0100 Subject: [PATCH] Query planner to support composite indexes on the 1st column (#4746) --- core/src/idx/planner/executor.rs | 58 ++++++++++------- core/src/idx/planner/iterators.rs | 60 ++++++++--------- core/src/idx/planner/plan.rs | 21 ++++-- core/src/idx/planner/tree.rs | 103 ++++++++++++++++-------------- core/src/key/index/mod.rs | 37 ++++++++++- core/src/kvs/savepoint.rs | 1 + sdk/tests/helpers.rs | 2 +- sdk/tests/planner.rs | 98 ++++++++++++++++++++++++++++ 8 files changed, 272 insertions(+), 108 deletions(-) diff --git a/core/src/idx/planner/executor.rs b/core/src/idx/planner/executor.rs index 75635167..8fd8b579 100644 --- a/core/src/idx/planner/executor.rs +++ b/core/src/idx/planner/executor.rs @@ -69,7 +69,7 @@ pub(super) struct InnerQueryExecutor { mr_entries: HashMap, exp_entries: HashMap, FtEntry>, it_entries: Vec, - index_definitions: Vec, + index_definitions: Vec>, mt_entries: HashMap, MtEntry>, hnsw_entries: HashMap, HnswEntry>, knn_bruteforce_entries: HashMap, KnnBruteForceEntry>, @@ -87,7 +87,7 @@ pub(super) enum IteratorEntry { } impl IteratorEntry { - pub(super) fn explain(&self, ix_def: &[DefineIndexStatement]) -> Value { + pub(super) fn explain(&self, ix_def: &[Arc]) -> Value { match self { Self::Single(_, io) => io.explain(ix_def), Self::Range(_, ir, from, to) => { @@ -329,7 +329,7 @@ impl QueryExecutor { pub(crate) fn explain(&self, itr: IteratorRef) -> Value { match self.0.it_entries.get(itr as usize) { - Some(ie) => ie.explain(self.0.index_definitions.as_slice()), + Some(ie) => ie.explain(&self.0.index_definitions), None => Value::None, } } @@ -368,7 +368,7 @@ impl QueryExecutor { ) -> Result, Error> { if let Some(ix) = self.get_index_def(io.ix_ref()) { match ix.index { - Index::Idx => Ok(self.new_index_iterator(opt, irf, ix, io.clone()).await?), + Index::Idx => Ok(self.new_index_iterator(opt, irf, ix.clone(), io.clone()).await?), Index::Uniq => Ok(self.new_unique_index_iterator(opt, irf, ix, io.clone()).await?), Index::Search { .. @@ -385,7 +385,7 @@ impl QueryExecutor { &self, opt: &Options, irf: IteratorRef, - ix: &DefineIndexStatement, + ix: Arc, io: IndexOption, ) -> Result, Error> { Ok(match io.op() { @@ -393,16 +393,16 @@ impl QueryExecutor { if let Value::Number(n) = value.as_ref() { let values = Self::get_number_variants(n); if values.len() == 1 { - Some(Self::new_index_equal_iterator(irf, opt, ix, &values[0])?) + Some(Self::new_index_equal_iterator(irf, opt, &ix, &values[0])?) } else { - Some(Self::new_multiple_index_equal_iterators(irf, opt, ix, values)?) + Some(Self::new_multiple_index_equal_iterators(irf, opt, &ix, values)?) } } else { - Some(Self::new_index_equal_iterator(irf, opt, ix, value)?) + Some(Self::new_index_equal_iterator(irf, opt, &ix, value)?) } } IndexOperator::Union(value) => Some(ThingIterator::IndexUnion( - IndexUnionThingIterator::new(irf, opt.ns()?, opt.db()?, &ix.what, &ix.name, value), + IndexUnionThingIterator::new(irf, opt.ns()?, opt.db()?, &ix, value), )), IndexOperator::Join(ios) => { let iterators = self.build_iterators(opt, irf, ios).await?; @@ -426,8 +426,7 @@ impl QueryExecutor { irf, opt.ns()?, opt.db()?, - &ix.what, - &ix.name, + ix, value, ))) } @@ -527,7 +526,7 @@ impl QueryExecutor { &self, opt: &Options, irf: IteratorRef, - ix: &DefineIndexStatement, + ix: &Arc, io: IndexOption, ) -> Result, Error> { Ok(match io.op() { @@ -548,11 +547,12 @@ impl QueryExecutor { )), IndexOperator::Join(ios) => { let iterators = self.build_iterators(opt, irf, ios).await?; - let unique_join = Box::new(UniqueJoinThingIterator::new(irf, opt, ix, iterators)?); + let unique_join = + Box::new(UniqueJoinThingIterator::new(irf, opt, ix.clone(), iterators)?); Some(ThingIterator::UniqueJoin(unique_join)) } IndexOperator::Order => Some(ThingIterator::UniqueRange( - UniqueRangeThingIterator::full_range(irf, opt.ns()?, opt.db()?, &ix.what, &ix.name), + UniqueRangeThingIterator::full_range(irf, opt.ns()?, opt.db()?, ix), )), _ => None, }) @@ -564,14 +564,26 @@ impl QueryExecutor { ix: &DefineIndexStatement, value: &Value, ) -> Result { - Ok(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new( - irf, - opt.ns()?, - opt.db()?, - &ix.what, - &ix.name, - value, - ))) + if ix.cols.len() > 1 { + // If the index is unique and the index is a composite index, + // then we have the opportunity to iterate on the first column of the index + // and consider it as a standard index (rather than a unique one) + Ok(ThingIterator::IndexEqual(IndexEqualThingIterator::new( + irf, + opt.ns()?, + opt.db()?, + ix, + value, + ))) + } else { + Ok(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new( + irf, + opt.ns()?, + opt.db()?, + ix, + value, + ))) + } } fn new_multiple_unique_equal_iterators( @@ -641,7 +653,7 @@ impl QueryExecutor { Ok(iterators) } - fn get_index_def(&self, ir: IndexRef) -> Option<&DefineIndexStatement> { + fn get_index_def(&self, ir: IndexRef) -> Option<&Arc> { self.0.index_definitions.get(ir as usize) } diff --git a/core/src/idx/planner/iterators.rs b/core/src/idx/planner/iterators.rs index 05c54ddf..075b0596 100644 --- a/core/src/idx/planner/iterators.rs +++ b/core/src/idx/planner/iterators.rs @@ -152,13 +152,21 @@ impl IndexEqualThingIterator { irf: IteratorRef, ns: &str, db: &str, - ix_what: &Ident, - ix_name: &Ident, + ix: &DefineIndexStatement, v: &Value, ) -> Self { let a = Array::from(v.clone()); - let beg = Index::prefix_ids_beg(ns, db, ix_what, ix_name, &a); - let end = Index::prefix_ids_end(ns, db, ix_what, ix_name, &a); + let (beg, end) = if ix.cols.len() == 1 { + ( + Index::prefix_ids_beg(ns, db, &ix.what, &ix.name, &a), + Index::prefix_ids_end(ns, db, &ix.what, &ix.name, &a), + ) + } else { + ( + Index::prefix_ids_composite_beg(ns, db, &ix.what, &ix.name, &a), + Index::prefix_ids_composite_end(ns, db, &ix.what, &ix.name, &a), + ) + }; Self { irf, beg, @@ -343,8 +351,7 @@ impl IndexUnionThingIterator { irf: IteratorRef, ns: &str, db: &str, - ix_what: &Ident, - ix_name: &Ident, + ix: &DefineIndexStatement, a: &Value, ) -> Self { // We create a VecDeque to hold the prefix keys (begin and end) for each value in the array. @@ -352,8 +359,8 @@ impl IndexUnionThingIterator { a.0.iter() .map(|v| { let a = Array::from(v.clone()); - let beg = Index::prefix_ids_beg(ns, db, ix_what, ix_name, &a); - let end = Index::prefix_ids_end(ns, db, ix_what, ix_name, &a); + let beg = Index::prefix_ids_beg(ns, db, &ix.what, &ix.name, &a); + let end = Index::prefix_ids_end(ns, db, &ix.what, &ix.name, &a); (beg, end) }) .collect() @@ -392,8 +399,7 @@ impl IndexUnionThingIterator { struct JoinThingIterator { ns: String, db: String, - ix_what: Ident, - ix_name: Ident, + ix: Arc, remote_iterators: VecDeque, current_remote: Option, current_remote_batch: VecDeque, @@ -404,14 +410,13 @@ struct JoinThingIterator { impl JoinThingIterator { pub(super) fn new( opt: &Options, - ix: &DefineIndexStatement, + ix: Arc, remote_iterators: VecDeque, ) -> Result { Ok(Self { ns: opt.ns()?.to_string(), db: opt.db()?.to_string(), - ix_what: ix.what.clone(), - ix_name: ix.name.clone(), + ix, current_remote: None, current_remote_batch: VecDeque::with_capacity(1), remote_iterators, @@ -451,15 +456,14 @@ impl JoinThingIterator { new_iter: F, ) -> Result where - F: Fn(&str, &str, &Ident, &Ident, Value) -> ThingIterator, + F: Fn(&str, &str, &DefineIndexStatement, Value) -> ThingIterator, { while !ctx.is_done() { while let Some((thing, _, _)) = self.current_remote_batch.pop_front() { let k: Key = thing.as_ref().into(); let value = Value::from(thing.as_ref().clone()); if self.distinct.insert(k, true).is_none() { - self.current_local = - Some(new_iter(&self.ns, &self.db, &self.ix_what, &self.ix_name, value)); + self.current_local = Some(new_iter(&self.ns, &self.db, &self.ix, value)); return Ok(true); } } @@ -478,7 +482,7 @@ impl JoinThingIterator { new_iter: F, ) -> Result where - F: Fn(&str, &str, &Ident, &Ident, Value) -> ThingIterator + Copy, + F: Fn(&str, &str, &DefineIndexStatement, Value) -> ThingIterator + Copy, { while !ctx.is_done() { if let Some(current_local) = &mut self.current_local { @@ -501,7 +505,7 @@ impl IndexJoinThingIterator { pub(super) fn new( irf: IteratorRef, opt: &Options, - ix: &DefineIndexStatement, + ix: Arc, remote_iterators: VecDeque, ) -> Result { Ok(Self(irf, JoinThingIterator::new(opt, ix, remote_iterators)?)) @@ -513,8 +517,8 @@ impl IndexJoinThingIterator { tx: &Transaction, limit: u32, ) -> Result { - let new_iter = |ns: &str, db: &str, ix_what: &Ident, ix_name: &Ident, value: Value| { - let it = IndexEqualThingIterator::new(self.0, ns, db, ix_what, ix_name, &value); + let new_iter = |ns: &str, db: &str, ix: &DefineIndexStatement, value: Value| { + let it = IndexEqualThingIterator::new(self.0, ns, db, ix, &value); ThingIterator::IndexEqual(it) }; self.1.next_batch(ctx, tx, limit, new_iter).await @@ -531,12 +535,11 @@ impl UniqueEqualThingIterator { irf: IteratorRef, ns: &str, db: &str, - ix_what: &Ident, - ix_name: &Ident, + ix: &DefineIndexStatement, v: &Value, ) -> Self { let a = Array::from(v.to_owned()); - let key = Index::new(ns, db, ix_what, ix_name, &a, None).into(); + let key = Index::new(ns, db, &ix.what, &ix.name, &a, None).into(); Self { irf, key: Some(key), @@ -584,14 +587,13 @@ impl UniqueRangeThingIterator { irf: IteratorRef, ns: &str, db: &str, - ix_what: &Ident, - ix_name: &Ident, + ix: &DefineIndexStatement, ) -> Self { let full_range = RangeValue { value: Value::None, inclusive: true, }; - Self::new(irf, ns, db, ix_what, ix_name, &full_range, &full_range) + Self::new(irf, ns, db, &ix.what, &ix.name, &full_range, &full_range) } fn compute_beg( @@ -720,7 +722,7 @@ impl UniqueJoinThingIterator { pub(super) fn new( irf: IteratorRef, opt: &Options, - ix: &DefineIndexStatement, + ix: Arc, remote_iterators: VecDeque, ) -> Result { Ok(Self(irf, JoinThingIterator::new(opt, ix, remote_iterators)?)) @@ -732,8 +734,8 @@ impl UniqueJoinThingIterator { tx: &Transaction, limit: u32, ) -> Result { - let new_iter = |ns: &str, db: &str, ix_what: &Ident, ix_name: &Ident, value: Value| { - let it = UniqueEqualThingIterator::new(self.0, ns, db, ix_what, ix_name, &value); + let new_iter = |ns: &str, db: &str, ix: &DefineIndexStatement, value: Value| { + let it = UniqueEqualThingIterator::new(self.0, ns, db, ix, &value); ThingIterator::UniqueEqual(it) }; self.1.next_batch(ctx, tx, limit, new_iter).await diff --git a/core/src/idx/planner/plan.rs b/core/src/idx/planner/plan.rs index ffdae735..8b49e129 100644 --- a/core/src/idx/planner/plan.rs +++ b/core/src/idx/planner/plan.rs @@ -1,6 +1,6 @@ use crate::err::Error; use crate::idx::ft::MatchRef; -use crate::idx::planner::tree::{GroupRef, IdiomPosition, IndexRef, Node}; +use crate::idx::planner::tree::{GroupRef, IdiomCol, IdiomPosition, IndexRef, Node}; use crate::sql::statements::DefineIndexStatement; use crate::sql::with::With; use crate::sql::{Array, Expression, Idiom, Number, Object}; @@ -174,8 +174,13 @@ pub(super) enum Plan { pub(super) struct IndexOption { /// A reference to the index definition ix_ref: IndexRef, - id: Idiom, + /// The idiom matching this index + id: Arc, + /// The index of the idiom in the index columns + id_col: IdiomCol, + /// The position of the idiom in the expression (Left or Right) id_pos: IdiomPosition, + /// The index operator op: Arc, } @@ -195,13 +200,15 @@ pub(super) enum IndexOperator { impl IndexOption { pub(super) fn new( ix_ref: IndexRef, - id: Idiom, + id: Arc, + id_col: IdiomCol, id_pos: IdiomPosition, op: IndexOperator, ) -> Self { Self { ix_ref, id, + id_col, id_pos, op: Arc::new(op), } @@ -236,7 +243,7 @@ impl IndexOption { v.clone() } - pub(crate) fn explain(&self, ix_def: &[DefineIndexStatement]) -> Value { + pub(crate) fn explain(&self, ix_def: &[Arc]) -> Value { let mut e = HashMap::new(); if let Some(ix) = ix_def.get(self.ix_ref as usize) { e.insert("index", Value::from(ix.name.0.to_owned())); @@ -452,14 +459,16 @@ mod tests { let mut set = HashSet::new(); let io1 = IndexOption::new( 1, - Idiom::parse("test"), + Idiom::parse("test").into(), + 0, IdiomPosition::Right, IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()), ); let io2 = IndexOption::new( 1, - Idiom::parse("test"), + Idiom::parse("test").into(), + 0, IdiomPosition::Right, IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()), ); diff --git a/core/src/idx/planner/tree.rs b/core/src/idx/planner/tree.rs index 869c0057..ea637a7a 100644 --- a/core/src/idx/planner/tree.rs +++ b/core/src/idx/planner/tree.rs @@ -61,14 +61,14 @@ struct TreeBuilder<'a> { with: Option<&'a With>, first_order: Option<&'a Order>, schemas: HashMap, - idioms_indexes: HashMap>, + idioms_indexes: HashMap, LocalIndexRefs>>, resolved_expressions: HashMap, ResolvedExpression>, - resolved_idioms: HashMap, + resolved_idioms: HashMap, Node>, index_map: IndexesMap, with_indexes: Vec, knn_brute_force_expressions: HashMap, KnnBruteForceExpression>, knn_expressions: KnnExpressions, - idioms_record_options: HashMap, + idioms_record_options: HashMap, RecordOptions>, group_sequence: GroupRef, root: Option, knn_condition: Option, @@ -80,8 +80,9 @@ pub(super) struct RecordOptions { remotes: RemoteIndexRefs, } -pub(super) type LocalIndexRefs = Vec; -pub(super) type RemoteIndexRefs = Arc>; +pub(super) type IdiomCol = usize; +pub(super) type LocalIndexRefs = Vec<(IndexRef, IdiomCol)>; +pub(super) type RemoteIndexRefs = Arc, LocalIndexRefs)>>; impl<'a> TreeBuilder<'a> { fn new( @@ -138,13 +139,17 @@ impl<'a> TreeBuilder<'a> { if let Some(o) = self.first_order { if !o.random && o.direction { 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, - )); + for (ix_ref, id_col) in &irf { + if *id_col == 0 { + self.index_map.order_limit = Some(IndexOption::new( + *ix_ref, + id, + *id_col, + IdiomPosition::None, + IndexOperator::Order, + )); + break; + } } } } @@ -235,14 +240,14 @@ impl<'a> TreeBuilder<'a> { async fn resolve_idiom(&mut self, i: &Idiom) -> Result { let tx = self.ctx.tx(); self.lazy_load_schema_resolver(&tx, self.table).await?; - + let i = Arc::new(i.clone()); // Try to detect if it matches an index 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() { Node::IndexedField(i.clone(), irs) } else if let Some(ro) = - self.resolve_record_field(&tx, schema.fields.as_ref(), i).await? + self.resolve_record_field(&tx, schema.fields.as_ref(), &i).await? { // Try to detect an indexed record field Node::RecordField(i.clone(), ro) @@ -256,7 +261,7 @@ impl<'a> TreeBuilder<'a> { Ok(n) } - fn resolve_indexes(&mut self, t: &Table, i: &Idiom, schema: &SchemaCache) -> Vec { + fn resolve_indexes(&mut self, t: &Table, i: &Idiom, schema: &SchemaCache) -> LocalIndexRefs { if let Some(m) = self.idioms_indexes.get(t) { if let Some(irs) = m.get(i).cloned() { return irs; @@ -264,21 +269,22 @@ impl<'a> TreeBuilder<'a> { } let mut irs = Vec::new(); for ix in schema.indexes.iter() { - if ix.cols.len() == 1 && ix.cols[0].eq(i) { + if let Some(idiom_index) = ix.cols.iter().position(|p| p.eq(i)) { let ixr = self.index_map.definitions.len() as IndexRef; if let Some(With::Index(ixs)) = &self.with { if ixs.contains(&ix.name.0) { self.with_indexes.push(ixr); } } - self.index_map.definitions.push(ix.clone()); - irs.push(ixr); + self.index_map.definitions.push(ix.clone().into()); + irs.push((ixr, idiom_index)); } } + let i = Arc::new(i.clone()); if let Some(e) = self.idioms_indexes.get_mut(t) { - e.insert(i.clone(), irs.clone()); + e.insert(i, irs.clone()); } else { - self.idioms_indexes.insert(t.clone(), HashMap::from([(i.clone(), irs.clone())])); + self.idioms_indexes.insert(t.clone(), HashMap::from([(i, irs.clone())])); } irs } @@ -287,7 +293,7 @@ impl<'a> TreeBuilder<'a> { &mut self, tx: &Transaction, fields: &[DefineFieldStatement], - idiom: &Idiom, + idiom: &Arc, ) -> Result, Error> { for field in fields.iter() { if let Some(Kind::Record(tables)) = &field.kind { @@ -305,12 +311,12 @@ impl<'a> TreeBuilder<'a> { return Ok(None); } - let remote_field = Idiom::from(remote_field); + let remote_field = Arc::new(Idiom::from(remote_field)); let mut remotes = vec![]; for table in tables { self.lazy_load_schema_resolver(tx, table).await?; - if let Some(shema) = self.schemas.get(table).cloned() { - let remote_irs = self.resolve_indexes(table, &remote_field, &shema); + if let Some(schema) = self.schemas.get(table).cloned() { + let remote_irs = self.resolve_indexes(table, &remote_field, &schema); remotes.push((remote_field.clone(), remote_irs)); } else { return Ok(None); @@ -400,43 +406,44 @@ impl<'a> TreeBuilder<'a> { fn lookup_index_options( &mut self, o: &Operator, - id: &Idiom, + id: &Arc, node: &Node, exp: &Arc, p: IdiomPosition, - local_irs: LocalIndexRefs, - remote_irs: Option, + local_irs: &LocalIndexRefs, + remote_irs: Option<&RemoteIndexRefs>, ) -> Result, Error> { if let Some(remote_irs) = remote_irs { let mut remote_ios = Vec::with_capacity(remote_irs.len()); for (id, irs) in remote_irs.iter() { - if let Some(io) = self.lookup_index_option(irs.as_slice(), o, id, node, exp, p)? { + if let Some(io) = self.lookup_index_option(irs, o, id, node, exp, p)? { remote_ios.push(io); } else { return Ok(None); } } - if let Some(ir) = self.lookup_join_index_ref(local_irs.as_slice()) { - let io = IndexOption::new(ir, id.clone(), p, IndexOperator::Join(remote_ios)); + if let Some((irf, id_col)) = self.lookup_join_index_ref(local_irs) { + let io = + IndexOption::new(irf, id.clone(), id_col, p, IndexOperator::Join(remote_ios)); return Ok(Some(io)); } return Ok(None); } - let io = self.lookup_index_option(local_irs.as_slice(), o, id, node, exp, p)?; + let io = self.lookup_index_option(local_irs, o, id, node, exp, p)?; Ok(io) } fn lookup_index_option( &mut self, - irs: &[IndexRef], + irs: &LocalIndexRefs, op: &Operator, - id: &Idiom, + id: &Arc, n: &Node, e: &Arc, p: IdiomPosition, ) -> Result, Error> { - for ir in irs { - if let Some(ix) = self.index_map.definitions.get(*ir as usize) { + for (irf, id_col) in irs.iter().filter(|(_, id_col)| 0.eq(id_col)) { + if let Some(ix) = self.index_map.definitions.get(*irf as usize) { let op = match &ix.index { Index::Idx => self.eval_index_operator(op, n, p), Index::Uniq => self.eval_index_operator(op, n, p), @@ -447,7 +454,7 @@ impl<'a> TreeBuilder<'a> { Index::Hnsw(_) => self.eval_hnsw_knn(e, op, n)?, }; if let Some(op) = op { - let io = IndexOption::new(*ir, id.clone(), p, op); + let io = IndexOption::new(*irf, id.clone(), *id_col, p, op); self.index_map.options.push((e.clone(), io.clone())); return Ok(Some(io)); } @@ -456,11 +463,11 @@ impl<'a> TreeBuilder<'a> { Ok(None) } - fn lookup_join_index_ref(&self, irs: &[IndexRef]) -> Option { - for ir in irs { - if let Some(ix) = self.index_map.definitions.get(*ir as usize) { + fn lookup_join_index_ref(&self, irs: &LocalIndexRefs) -> Option<(IndexRef, IdiomCol)> { + for (irf, id_col) in irs.iter().filter(|(_, id_col)| 0.eq(id_col)) { + if let Some(ix) = self.index_map.definitions.get(*irf as usize) { match &ix.index { - Index::Idx | Index::Uniq => return Some(*ir), + Index::Idx | Index::Uniq => return Some((*irf, *id_col)), _ => {} }; } @@ -581,7 +588,7 @@ pub(super) type IndexRef = u16; #[derive(Default)] pub(super) struct IndexesMap { pub(super) options: Vec<(Arc, IndexOption)>, - pub(super) definitions: Vec, + pub(super) definitions: Vec>, pub(super) order_limit: Option, } @@ -613,9 +620,9 @@ pub(super) enum Node { right: Arc, exp: Arc, }, - IndexedField(Idiom, Vec), - RecordField(Idiom, RecordOptions), - NonIndexedField(Idiom), + IndexedField(Arc, LocalIndexRefs), + RecordField(Arc, RecordOptions), + NonIndexedField(Arc), Computable, Computed(Arc), Unsupported(String), @@ -632,10 +639,10 @@ impl Node { pub(super) fn is_indexed_field( &self, - ) -> Option<(&Idiom, LocalIndexRefs, Option)> { + ) -> Option<(&Arc, &LocalIndexRefs, Option<&RemoteIndexRefs>)> { match self { - Self::IndexedField(id, irs) => Some((id, irs.clone(), None)), - Self::RecordField(id, ro) => Some((id, ro.locals.clone(), Some(ro.remotes.clone()))), + Self::IndexedField(id, irs) => Some((id, irs, None)), + Self::RecordField(id, ro) => Some((id, &ro.locals, Some(&ro.remotes))), _ => None, } } diff --git a/core/src/key/index/mod.rs b/core/src/key/index/mod.rs index 9692cece..019f275e 100644 --- a/core/src/key/index/mod.rs +++ b/core/src/key/index/mod.rs @@ -172,13 +172,26 @@ impl<'a> Index<'a> { beg.extend_from_slice(&[0xff]); beg } + + pub fn prefix_ids_composite_beg(ns: &str, db: &str, tb: &str, ix: &str, fd: &Array) -> Vec { + let mut beg = Self::prefix_ids(ns, db, tb, ix, fd); + *beg.last_mut().unwrap() = 0x00; + beg + } + + pub fn prefix_ids_composite_end(ns: &str, db: &str, tb: &str, ix: &str, fd: &Array) -> Vec { + let mut beg = Self::prefix_ids(ns, db, tb, ix, fd); + *beg.last_mut().unwrap() = 0xff; + beg + } } #[cfg(test)] mod tests { + use super::*; + #[test] fn key() { - use super::*; #[rustfmt::skip] let fd = vec!["testfd1", "testfd2"].into(); let id = "testid".into(); @@ -192,4 +205,26 @@ mod tests { let dec = Index::decode(&enc).unwrap(); assert_eq!(val, dec); } + + #[test] + fn key_none() { + let fd = vec!["testfd1", "testfd2"].into(); + let val = Index::new("testns", "testdb", "testtb", "testix", &fd, None); + let enc = Index::encode(&val).unwrap(); + assert_eq!( + enc, + b"/*testns\0*testdb\0*testtb\0+testix\0*\0\0\0\x04testfd1\0\0\0\0\x04testfd2\0\x01\0" + ); + } + + #[test] + fn check_composite() { + let fd = vec!["testfd1"].into(); + + let enc = Index::prefix_ids_composite_beg("testns", "testdb", "testtb", "testix", &fd); + assert_eq!(enc, b"/*testns\0*testdb\0*testtb\0+testix\0*\0\0\0\x04testfd1\0\x00"); + + let enc = Index::prefix_ids_composite_end("testns", "testdb", "testtb", "testix", &fd); + assert_eq!(enc, b"/*testns\0*testdb\0*testtb\0+testix\0*\0\0\0\x04testfd1\0\xff"); + } } diff --git a/core/src/kvs/savepoint.rs b/core/src/kvs/savepoint.rs index 72938f2d..3e749ccf 100644 --- a/core/src/kvs/savepoint.rs +++ b/core/src/kvs/savepoint.rs @@ -27,6 +27,7 @@ impl SavedValue { } } + #[cfg(any(feature = "kv-surrealkv", feature = "kv-fdb", feature = "kv-tikv"))] pub(super) fn get_val(&self) -> Option<&Val> { self.saved_val.as_ref() } diff --git a/sdk/tests/helpers.rs b/sdk/tests/helpers.rs index 2534078e..dbfa5d00 100644 --- a/sdk/tests/helpers.rs +++ b/sdk/tests/helpers.rs @@ -344,7 +344,7 @@ impl Test { /// Expect the given value to be equals to the next response. #[allow(dead_code)] pub fn expect_val(&mut self, val: &str) -> Result<&mut Self, Error> { - self.expect_value(value(val).unwrap()) + self.expect_value(value(val).unwrap_or_else(|_| panic!("INVALID VALUE:\n{val}"))) } #[allow(dead_code)] diff --git a/sdk/tests/planner.rs b/sdk/tests/planner.rs index ce5faacb..74373ee0 100644 --- a/sdk/tests/planner.rs +++ b/sdk/tests/planner.rs @@ -2965,3 +2965,101 @@ async fn select_from_unique_index_descending() -> Result<(), Error> { // Ok(()) } + +async fn select_composite_index(unique: bool) -> Result<(), Error> { + // + let sql = format!( + " + DEFINE INDEX t_idx ON TABLE t COLUMNS on, value {}; + CREATE t:1 SET on = true, value = 1; + CREATE t:2 SET on = false, value = 1; + CREATE t:3 SET on = true, value = 2; + CREATE t:4 SET on = false, value = 2; + SELECT * FROM t WHERE on = true EXPLAIN; + SELECT * FROM t WHERE on = true; + SELECT * FROM t WHERE on = false AND value = 2 EXPLAIN; + SELECT * FROM t WHERE on = false AND value = 2; + ", + if unique { + "UNIQUE" + } else { + "" + } + ); + let mut t = Test::new(&sql).await?; + // + t.expect_size(9)?; + t.skip_ok(5)?; + // + t.expect_vals(&[ + "[ + { + detail: { + plan: { + index: 't_idx', + operator: '=', + value: true + }, + table: 't' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + "[ + { + id: t:1, + on: true, + value: 1 + }, + { + id: t:3, + on: true, + value: 2 + } + ]", + "[ + { + detail: { + plan: { + index: 't_idx', + operator: '=', + value: false + }, + table: 't' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + "[ + { + id: t:4, + on: false, + value: 2 + } + ]", + ])?; + // + Ok(()) +} + +#[tokio::test] +async fn select_composite_standard_index() -> Result<(), Error> { + select_composite_index(false).await +} + +#[tokio::test] +async fn select_composite_unique_index() -> Result<(), Error> { + select_composite_index(true).await +}