Query planner to support composite indexes on the 1st column (#4746)

This commit is contained in:
Emmanuel Keller 2024-09-16 14:17:20 +01:00 committed by GitHub
parent 4d92005125
commit 9cfdd34fc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 272 additions and 108 deletions

View file

@ -69,7 +69,7 @@ pub(super) struct InnerQueryExecutor {
mr_entries: HashMap<MatchRef, FtEntry>, mr_entries: HashMap<MatchRef, FtEntry>,
exp_entries: HashMap<Arc<Expression>, FtEntry>, exp_entries: HashMap<Arc<Expression>, FtEntry>,
it_entries: Vec<IteratorEntry>, it_entries: Vec<IteratorEntry>,
index_definitions: Vec<DefineIndexStatement>, index_definitions: Vec<Arc<DefineIndexStatement>>,
mt_entries: HashMap<Arc<Expression>, MtEntry>, mt_entries: HashMap<Arc<Expression>, MtEntry>,
hnsw_entries: HashMap<Arc<Expression>, HnswEntry>, hnsw_entries: HashMap<Arc<Expression>, HnswEntry>,
knn_bruteforce_entries: HashMap<Arc<Expression>, KnnBruteForceEntry>, knn_bruteforce_entries: HashMap<Arc<Expression>, KnnBruteForceEntry>,
@ -87,7 +87,7 @@ pub(super) enum IteratorEntry {
} }
impl IteratorEntry { impl IteratorEntry {
pub(super) fn explain(&self, ix_def: &[DefineIndexStatement]) -> Value { pub(super) fn explain(&self, ix_def: &[Arc<DefineIndexStatement>]) -> Value {
match self { match self {
Self::Single(_, io) => io.explain(ix_def), Self::Single(_, io) => io.explain(ix_def),
Self::Range(_, ir, from, to) => { Self::Range(_, ir, from, to) => {
@ -329,7 +329,7 @@ impl QueryExecutor {
pub(crate) fn explain(&self, itr: IteratorRef) -> Value { pub(crate) fn explain(&self, itr: IteratorRef) -> Value {
match self.0.it_entries.get(itr as usize) { 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, None => Value::None,
} }
} }
@ -368,7 +368,7 @@ impl QueryExecutor {
) -> Result<Option<ThingIterator>, Error> { ) -> Result<Option<ThingIterator>, Error> {
if let Some(ix) = self.get_index_def(io.ix_ref()) { if let Some(ix) = self.get_index_def(io.ix_ref()) {
match ix.index { 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::Uniq => Ok(self.new_unique_index_iterator(opt, irf, ix, io.clone()).await?),
Index::Search { Index::Search {
.. ..
@ -385,7 +385,7 @@ impl QueryExecutor {
&self, &self,
opt: &Options, opt: &Options,
irf: IteratorRef, irf: IteratorRef,
ix: &DefineIndexStatement, ix: Arc<DefineIndexStatement>,
io: IndexOption, io: IndexOption,
) -> Result<Option<ThingIterator>, Error> { ) -> Result<Option<ThingIterator>, Error> {
Ok(match io.op() { Ok(match io.op() {
@ -393,16 +393,16 @@ impl QueryExecutor {
if let Value::Number(n) = value.as_ref() { if let Value::Number(n) = value.as_ref() {
let values = Self::get_number_variants(n); let values = Self::get_number_variants(n);
if values.len() == 1 { 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 { } else {
Some(Self::new_multiple_index_equal_iterators(irf, opt, ix, values)?) Some(Self::new_multiple_index_equal_iterators(irf, opt, &ix, values)?)
} }
} else { } 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( 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) => { IndexOperator::Join(ios) => {
let iterators = self.build_iterators(opt, irf, ios).await?; let iterators = self.build_iterators(opt, irf, ios).await?;
@ -426,8 +426,7 @@ impl QueryExecutor {
irf, irf,
opt.ns()?, opt.ns()?,
opt.db()?, opt.db()?,
&ix.what, ix,
&ix.name,
value, value,
))) )))
} }
@ -527,7 +526,7 @@ impl QueryExecutor {
&self, &self,
opt: &Options, opt: &Options,
irf: IteratorRef, irf: IteratorRef,
ix: &DefineIndexStatement, ix: &Arc<DefineIndexStatement>,
io: IndexOption, io: IndexOption,
) -> Result<Option<ThingIterator>, Error> { ) -> Result<Option<ThingIterator>, Error> {
Ok(match io.op() { Ok(match io.op() {
@ -548,11 +547,12 @@ impl QueryExecutor {
)), )),
IndexOperator::Join(ios) => { IndexOperator::Join(ios) => {
let iterators = self.build_iterators(opt, irf, ios).await?; 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)) Some(ThingIterator::UniqueJoin(unique_join))
} }
IndexOperator::Order => Some(ThingIterator::UniqueRange( 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, _ => None,
}) })
@ -564,15 +564,27 @@ impl QueryExecutor {
ix: &DefineIndexStatement, ix: &DefineIndexStatement,
value: &Value, value: &Value,
) -> Result<ThingIterator, Error> { ) -> Result<ThingIterator, Error> {
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( Ok(ThingIterator::UniqueEqual(UniqueEqualThingIterator::new(
irf, irf,
opt.ns()?, opt.ns()?,
opt.db()?, opt.db()?,
&ix.what, ix,
&ix.name,
value, value,
))) )))
} }
}
fn new_multiple_unique_equal_iterators( fn new_multiple_unique_equal_iterators(
irf: IteratorRef, irf: IteratorRef,
@ -641,7 +653,7 @@ impl QueryExecutor {
Ok(iterators) Ok(iterators)
} }
fn get_index_def(&self, ir: IndexRef) -> Option<&DefineIndexStatement> { fn get_index_def(&self, ir: IndexRef) -> Option<&Arc<DefineIndexStatement>> {
self.0.index_definitions.get(ir as usize) self.0.index_definitions.get(ir as usize)
} }

View file

@ -152,13 +152,21 @@ impl IndexEqualThingIterator {
irf: IteratorRef, irf: IteratorRef,
ns: &str, ns: &str,
db: &str, db: &str,
ix_what: &Ident, ix: &DefineIndexStatement,
ix_name: &Ident,
v: &Value, v: &Value,
) -> Self { ) -> Self {
let a = Array::from(v.clone()); let a = Array::from(v.clone());
let beg = Index::prefix_ids_beg(ns, db, ix_what, ix_name, &a); let (beg, end) = if ix.cols.len() == 1 {
let end = Index::prefix_ids_end(ns, db, ix_what, ix_name, &a); (
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 { Self {
irf, irf,
beg, beg,
@ -343,8 +351,7 @@ impl IndexUnionThingIterator {
irf: IteratorRef, irf: IteratorRef,
ns: &str, ns: &str,
db: &str, db: &str,
ix_what: &Ident, ix: &DefineIndexStatement,
ix_name: &Ident,
a: &Value, 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.
@ -352,8 +359,8 @@ impl IndexUnionThingIterator {
a.0.iter() a.0.iter()
.map(|v| { .map(|v| {
let a = Array::from(v.clone()); let a = Array::from(v.clone());
let beg = Index::prefix_ids_beg(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); let end = Index::prefix_ids_end(ns, db, &ix.what, &ix.name, &a);
(beg, end) (beg, end)
}) })
.collect() .collect()
@ -392,8 +399,7 @@ impl IndexUnionThingIterator {
struct JoinThingIterator { struct JoinThingIterator {
ns: String, ns: String,
db: String, db: String,
ix_what: Ident, ix: Arc<DefineIndexStatement>,
ix_name: Ident,
remote_iterators: VecDeque<ThingIterator>, remote_iterators: VecDeque<ThingIterator>,
current_remote: Option<ThingIterator>, current_remote: Option<ThingIterator>,
current_remote_batch: VecDeque<CollectorRecord>, current_remote_batch: VecDeque<CollectorRecord>,
@ -404,14 +410,13 @@ struct JoinThingIterator {
impl JoinThingIterator { impl JoinThingIterator {
pub(super) fn new( pub(super) fn new(
opt: &Options, opt: &Options,
ix: &DefineIndexStatement, ix: Arc<DefineIndexStatement>,
remote_iterators: VecDeque<ThingIterator>, remote_iterators: VecDeque<ThingIterator>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self { Ok(Self {
ns: opt.ns()?.to_string(), ns: opt.ns()?.to_string(),
db: opt.db()?.to_string(), db: opt.db()?.to_string(),
ix_what: ix.what.clone(), ix,
ix_name: ix.name.clone(),
current_remote: None, current_remote: None,
current_remote_batch: VecDeque::with_capacity(1), current_remote_batch: VecDeque::with_capacity(1),
remote_iterators, remote_iterators,
@ -451,15 +456,14 @@ impl JoinThingIterator {
new_iter: F, new_iter: F,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
F: Fn(&str, &str, &Ident, &Ident, Value) -> ThingIterator, F: Fn(&str, &str, &DefineIndexStatement, Value) -> ThingIterator,
{ {
while !ctx.is_done() { while !ctx.is_done() {
while let Some((thing, _, _)) = self.current_remote_batch.pop_front() { while let Some((thing, _, _)) = self.current_remote_batch.pop_front() {
let k: Key = thing.as_ref().into(); let k: Key = thing.as_ref().into();
let value = Value::from(thing.as_ref().clone()); let value = Value::from(thing.as_ref().clone());
if self.distinct.insert(k, true).is_none() { if self.distinct.insert(k, true).is_none() {
self.current_local = self.current_local = Some(new_iter(&self.ns, &self.db, &self.ix, value));
Some(new_iter(&self.ns, &self.db, &self.ix_what, &self.ix_name, value));
return Ok(true); return Ok(true);
} }
} }
@ -478,7 +482,7 @@ impl JoinThingIterator {
new_iter: F, new_iter: F,
) -> Result<B, Error> ) -> Result<B, Error>
where where
F: Fn(&str, &str, &Ident, &Ident, Value) -> ThingIterator + Copy, F: Fn(&str, &str, &DefineIndexStatement, Value) -> ThingIterator + Copy,
{ {
while !ctx.is_done() { while !ctx.is_done() {
if let Some(current_local) = &mut self.current_local { if let Some(current_local) = &mut self.current_local {
@ -501,7 +505,7 @@ impl IndexJoinThingIterator {
pub(super) fn new( pub(super) fn new(
irf: IteratorRef, irf: IteratorRef,
opt: &Options, opt: &Options,
ix: &DefineIndexStatement, ix: Arc<DefineIndexStatement>,
remote_iterators: VecDeque<ThingIterator>, remote_iterators: VecDeque<ThingIterator>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self(irf, JoinThingIterator::new(opt, ix, remote_iterators)?)) Ok(Self(irf, JoinThingIterator::new(opt, ix, remote_iterators)?))
@ -513,8 +517,8 @@ impl IndexJoinThingIterator {
tx: &Transaction, tx: &Transaction,
limit: u32, limit: u32,
) -> Result<B, Error> { ) -> Result<B, Error> {
let new_iter = |ns: &str, db: &str, ix_what: &Ident, ix_name: &Ident, value: Value| { let new_iter = |ns: &str, db: &str, ix: &DefineIndexStatement, value: Value| {
let it = IndexEqualThingIterator::new(self.0, ns, db, ix_what, ix_name, &value); let it = IndexEqualThingIterator::new(self.0, ns, db, ix, &value);
ThingIterator::IndexEqual(it) ThingIterator::IndexEqual(it)
}; };
self.1.next_batch(ctx, tx, limit, new_iter).await self.1.next_batch(ctx, tx, limit, new_iter).await
@ -531,12 +535,11 @@ impl UniqueEqualThingIterator {
irf: IteratorRef, irf: IteratorRef,
ns: &str, ns: &str,
db: &str, db: &str,
ix_what: &Ident, ix: &DefineIndexStatement,
ix_name: &Ident,
v: &Value, v: &Value,
) -> Self { ) -> Self {
let a = Array::from(v.to_owned()); 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 { Self {
irf, irf,
key: Some(key), key: Some(key),
@ -584,14 +587,13 @@ impl UniqueRangeThingIterator {
irf: IteratorRef, irf: IteratorRef,
ns: &str, ns: &str,
db: &str, db: &str,
ix_what: &Ident, ix: &DefineIndexStatement,
ix_name: &Ident,
) -> Self { ) -> Self {
let full_range = RangeValue { let full_range = RangeValue {
value: Value::None, value: Value::None,
inclusive: true, 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( fn compute_beg(
@ -720,7 +722,7 @@ impl UniqueJoinThingIterator {
pub(super) fn new( pub(super) fn new(
irf: IteratorRef, irf: IteratorRef,
opt: &Options, opt: &Options,
ix: &DefineIndexStatement, ix: Arc<DefineIndexStatement>,
remote_iterators: VecDeque<ThingIterator>, remote_iterators: VecDeque<ThingIterator>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self(irf, JoinThingIterator::new(opt, ix, remote_iterators)?)) Ok(Self(irf, JoinThingIterator::new(opt, ix, remote_iterators)?))
@ -732,8 +734,8 @@ impl UniqueJoinThingIterator {
tx: &Transaction, tx: &Transaction,
limit: u32, limit: u32,
) -> Result<B, Error> { ) -> Result<B, Error> {
let new_iter = |ns: &str, db: &str, ix_what: &Ident, ix_name: &Ident, value: Value| { let new_iter = |ns: &str, db: &str, ix: &DefineIndexStatement, value: Value| {
let it = UniqueEqualThingIterator::new(self.0, ns, db, ix_what, ix_name, &value); let it = UniqueEqualThingIterator::new(self.0, ns, db, ix, &value);
ThingIterator::UniqueEqual(it) ThingIterator::UniqueEqual(it)
}; };
self.1.next_batch(ctx, tx, limit, new_iter).await self.1.next_batch(ctx, tx, limit, new_iter).await

View file

@ -1,6 +1,6 @@
use crate::err::Error; use crate::err::Error;
use crate::idx::ft::MatchRef; 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::statements::DefineIndexStatement;
use crate::sql::with::With; use crate::sql::with::With;
use crate::sql::{Array, Expression, Idiom, Number, Object}; use crate::sql::{Array, Expression, Idiom, Number, Object};
@ -174,8 +174,13 @@ pub(super) enum Plan {
pub(super) struct IndexOption { pub(super) struct IndexOption {
/// A reference to the index definition /// A reference to the index definition
ix_ref: IndexRef, ix_ref: IndexRef,
id: Idiom, /// The idiom matching this index
id: Arc<Idiom>,
/// 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, id_pos: IdiomPosition,
/// The index operator
op: Arc<IndexOperator>, op: Arc<IndexOperator>,
} }
@ -195,13 +200,15 @@ pub(super) enum IndexOperator {
impl IndexOption { impl IndexOption {
pub(super) fn new( pub(super) fn new(
ix_ref: IndexRef, ix_ref: IndexRef,
id: Idiom, id: Arc<Idiom>,
id_col: IdiomCol,
id_pos: IdiomPosition, id_pos: IdiomPosition,
op: IndexOperator, op: IndexOperator,
) -> Self { ) -> Self {
Self { Self {
ix_ref, ix_ref,
id, id,
id_col,
id_pos, id_pos,
op: Arc::new(op), op: Arc::new(op),
} }
@ -236,7 +243,7 @@ impl IndexOption {
v.clone() v.clone()
} }
pub(crate) fn explain(&self, ix_def: &[DefineIndexStatement]) -> Value { pub(crate) fn explain(&self, ix_def: &[Arc<DefineIndexStatement>]) -> Value {
let mut e = HashMap::new(); let mut e = HashMap::new();
if let Some(ix) = ix_def.get(self.ix_ref as usize) { if let Some(ix) = ix_def.get(self.ix_ref as usize) {
e.insert("index", Value::from(ix.name.0.to_owned())); e.insert("index", Value::from(ix.name.0.to_owned()));
@ -452,14 +459,16 @@ mod tests {
let mut set = HashSet::new(); let mut set = HashSet::new();
let io1 = IndexOption::new( let io1 = IndexOption::new(
1, 1,
Idiom::parse("test"), Idiom::parse("test").into(),
0,
IdiomPosition::Right, IdiomPosition::Right,
IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()), 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").into(),
0,
IdiomPosition::Right, IdiomPosition::Right,
IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()), IndexOperator::Equality(Value::Array(Array::from(vec!["test"])).into()),
); );

View file

@ -61,14 +61,14 @@ struct TreeBuilder<'a> {
with: Option<&'a With>, with: Option<&'a With>,
first_order: Option<&'a Order>, 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<Arc<Idiom>, LocalIndexRefs>>,
resolved_expressions: HashMap<Arc<Expression>, ResolvedExpression>, resolved_expressions: HashMap<Arc<Expression>, ResolvedExpression>,
resolved_idioms: HashMap<Idiom, Node>, resolved_idioms: HashMap<Arc<Idiom>, Node>,
index_map: IndexesMap, index_map: IndexesMap,
with_indexes: Vec<IndexRef>, with_indexes: Vec<IndexRef>,
knn_brute_force_expressions: HashMap<Arc<Expression>, KnnBruteForceExpression>, knn_brute_force_expressions: HashMap<Arc<Expression>, KnnBruteForceExpression>,
knn_expressions: KnnExpressions, knn_expressions: KnnExpressions,
idioms_record_options: HashMap<Idiom, RecordOptions>, idioms_record_options: HashMap<Arc<Idiom>, RecordOptions>,
group_sequence: GroupRef, group_sequence: GroupRef,
root: Option<Node>, root: Option<Node>,
knn_condition: Option<Cond>, knn_condition: Option<Cond>,
@ -80,8 +80,9 @@ pub(super) struct RecordOptions {
remotes: RemoteIndexRefs, remotes: RemoteIndexRefs,
} }
pub(super) type LocalIndexRefs = Vec<IndexRef>; pub(super) type IdiomCol = usize;
pub(super) type RemoteIndexRefs = Arc<Vec<(Idiom, LocalIndexRefs)>>; pub(super) type LocalIndexRefs = Vec<(IndexRef, IdiomCol)>;
pub(super) type RemoteIndexRefs = Arc<Vec<(Arc<Idiom>, LocalIndexRefs)>>;
impl<'a> TreeBuilder<'a> { impl<'a> TreeBuilder<'a> {
fn new( fn new(
@ -138,13 +139,17 @@ impl<'a> TreeBuilder<'a> {
if let Some(o) = self.first_order { if let Some(o) = self.first_order {
if !o.random && o.direction { if !o.random && o.direction {
if let Node::IndexedField(id, irf) = self.resolve_idiom(&o.order).await? { if let Node::IndexedField(id, irf) = self.resolve_idiom(&o.order).await? {
if let Some(ix_ref) = irf.first().cloned() { for (ix_ref, id_col) in &irf {
if *id_col == 0 {
self.index_map.order_limit = Some(IndexOption::new( self.index_map.order_limit = Some(IndexOption::new(
ix_ref, *ix_ref,
id, id,
*id_col,
IdiomPosition::None, IdiomPosition::None,
IndexOperator::Order, IndexOperator::Order,
)); ));
break;
}
} }
} }
} }
@ -235,14 +240,14 @@ impl<'a> TreeBuilder<'a> {
async fn resolve_idiom(&mut self, i: &Idiom) -> Result<Node, Error> { async fn resolve_idiom(&mut self, i: &Idiom) -> Result<Node, Error> {
let tx = self.ctx.tx(); let tx = self.ctx.tx();
self.lazy_load_schema_resolver(&tx, self.table).await?; self.lazy_load_schema_resolver(&tx, self.table).await?;
let i = Arc::new(i.clone());
// Try to detect if it matches an index // Try to detect if it matches an index
let n = 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() {
Node::IndexedField(i.clone(), irs) Node::IndexedField(i.clone(), irs)
} else if let Some(ro) = } 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 // Try to detect an indexed record field
Node::RecordField(i.clone(), ro) Node::RecordField(i.clone(), ro)
@ -256,7 +261,7 @@ impl<'a> TreeBuilder<'a> {
Ok(n) Ok(n)
} }
fn resolve_indexes(&mut self, t: &Table, i: &Idiom, schema: &SchemaCache) -> Vec<IndexRef> { fn resolve_indexes(&mut self, t: &Table, i: &Idiom, schema: &SchemaCache) -> LocalIndexRefs {
if let Some(m) = self.idioms_indexes.get(t) { if let Some(m) = self.idioms_indexes.get(t) {
if let Some(irs) = m.get(i).cloned() { if let Some(irs) = m.get(i).cloned() {
return irs; return irs;
@ -264,21 +269,22 @@ impl<'a> TreeBuilder<'a> {
} }
let mut irs = Vec::new(); let mut irs = Vec::new();
for ix in schema.indexes.iter() { 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; let ixr = self.index_map.definitions.len() as IndexRef;
if let Some(With::Index(ixs)) = &self.with { if let Some(With::Index(ixs)) = &self.with {
if ixs.contains(&ix.name.0) { if ixs.contains(&ix.name.0) {
self.with_indexes.push(ixr); self.with_indexes.push(ixr);
} }
} }
self.index_map.definitions.push(ix.clone()); self.index_map.definitions.push(ix.clone().into());
irs.push(ixr); irs.push((ixr, idiom_index));
} }
} }
let i = Arc::new(i.clone());
if let Some(e) = self.idioms_indexes.get_mut(t) { if let Some(e) = self.idioms_indexes.get_mut(t) {
e.insert(i.clone(), irs.clone()); e.insert(i, irs.clone());
} else { } 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 irs
} }
@ -287,7 +293,7 @@ impl<'a> TreeBuilder<'a> {
&mut self, &mut self,
tx: &Transaction, tx: &Transaction,
fields: &[DefineFieldStatement], fields: &[DefineFieldStatement],
idiom: &Idiom, idiom: &Arc<Idiom>,
) -> Result<Option<RecordOptions>, Error> { ) -> Result<Option<RecordOptions>, Error> {
for field in fields.iter() { for field in fields.iter() {
if let Some(Kind::Record(tables)) = &field.kind { if let Some(Kind::Record(tables)) = &field.kind {
@ -305,12 +311,12 @@ impl<'a> TreeBuilder<'a> {
return Ok(None); return Ok(None);
} }
let remote_field = Idiom::from(remote_field); let remote_field = Arc::new(Idiom::from(remote_field));
let mut remotes = vec![]; let mut remotes = vec![];
for table in tables { for table in tables {
self.lazy_load_schema_resolver(tx, table).await?; self.lazy_load_schema_resolver(tx, table).await?;
if let Some(shema) = self.schemas.get(table).cloned() { if let Some(schema) = self.schemas.get(table).cloned() {
let remote_irs = self.resolve_indexes(table, &remote_field, &shema); let remote_irs = self.resolve_indexes(table, &remote_field, &schema);
remotes.push((remote_field.clone(), remote_irs)); remotes.push((remote_field.clone(), remote_irs));
} else { } else {
return Ok(None); return Ok(None);
@ -400,43 +406,44 @@ impl<'a> TreeBuilder<'a> {
fn lookup_index_options( fn lookup_index_options(
&mut self, &mut self,
o: &Operator, o: &Operator,
id: &Idiom, id: &Arc<Idiom>,
node: &Node, node: &Node,
exp: &Arc<Expression>, exp: &Arc<Expression>,
p: IdiomPosition, p: IdiomPosition,
local_irs: LocalIndexRefs, local_irs: &LocalIndexRefs,
remote_irs: Option<RemoteIndexRefs>, remote_irs: Option<&RemoteIndexRefs>,
) -> Result<Option<IndexOption>, Error> { ) -> Result<Option<IndexOption>, Error> {
if let Some(remote_irs) = remote_irs { if let Some(remote_irs) = remote_irs {
let mut remote_ios = Vec::with_capacity(remote_irs.len()); let mut remote_ios = Vec::with_capacity(remote_irs.len());
for (id, irs) in remote_irs.iter() { 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); remote_ios.push(io);
} else { } else {
return Ok(None); return Ok(None);
} }
} }
if let Some(ir) = self.lookup_join_index_ref(local_irs.as_slice()) { if let Some((irf, id_col)) = self.lookup_join_index_ref(local_irs) {
let io = IndexOption::new(ir, id.clone(), p, IndexOperator::Join(remote_ios)); let io =
IndexOption::new(irf, id.clone(), id_col, p, IndexOperator::Join(remote_ios));
return Ok(Some(io)); return Ok(Some(io));
} }
return Ok(None); 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) Ok(io)
} }
fn lookup_index_option( fn lookup_index_option(
&mut self, &mut self,
irs: &[IndexRef], irs: &LocalIndexRefs,
op: &Operator, op: &Operator,
id: &Idiom, id: &Arc<Idiom>,
n: &Node, n: &Node,
e: &Arc<Expression>, e: &Arc<Expression>,
p: IdiomPosition, p: IdiomPosition,
) -> Result<Option<IndexOption>, Error> { ) -> Result<Option<IndexOption>, Error> {
for ir in irs { for (irf, id_col) in irs.iter().filter(|(_, id_col)| 0.eq(id_col)) {
if let Some(ix) = self.index_map.definitions.get(*ir as usize) { if let Some(ix) = self.index_map.definitions.get(*irf 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),
@ -447,7 +454,7 @@ impl<'a> TreeBuilder<'a> {
Index::Hnsw(_) => self.eval_hnsw_knn(e, op, n)?, Index::Hnsw(_) => self.eval_hnsw_knn(e, op, n)?,
}; };
if let Some(op) = op { 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())); self.index_map.options.push((e.clone(), io.clone()));
return Ok(Some(io)); return Ok(Some(io));
} }
@ -456,11 +463,11 @@ impl<'a> TreeBuilder<'a> {
Ok(None) Ok(None)
} }
fn lookup_join_index_ref(&self, irs: &[IndexRef]) -> Option<IndexRef> { fn lookup_join_index_ref(&self, irs: &LocalIndexRefs) -> Option<(IndexRef, IdiomCol)> {
for ir in irs { for (irf, id_col) in irs.iter().filter(|(_, id_col)| 0.eq(id_col)) {
if let Some(ix) = self.index_map.definitions.get(*ir as usize) { if let Some(ix) = self.index_map.definitions.get(*irf as usize) {
match &ix.index { 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)] #[derive(Default)]
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<Arc<DefineIndexStatement>>,
pub(super) order_limit: Option<IndexOption>, pub(super) order_limit: Option<IndexOption>,
} }
@ -613,9 +620,9 @@ pub(super) enum Node {
right: Arc<Node>, right: Arc<Node>,
exp: Arc<Expression>, exp: Arc<Expression>,
}, },
IndexedField(Idiom, Vec<IndexRef>), IndexedField(Arc<Idiom>, LocalIndexRefs),
RecordField(Idiom, RecordOptions), RecordField(Arc<Idiom>, RecordOptions),
NonIndexedField(Idiom), NonIndexedField(Arc<Idiom>),
Computable, Computable,
Computed(Arc<Value>), Computed(Arc<Value>),
Unsupported(String), Unsupported(String),
@ -632,10 +639,10 @@ impl Node {
pub(super) fn is_indexed_field( pub(super) fn is_indexed_field(
&self, &self,
) -> Option<(&Idiom, LocalIndexRefs, Option<RemoteIndexRefs>)> { ) -> Option<(&Arc<Idiom>, &LocalIndexRefs, Option<&RemoteIndexRefs>)> {
match self { match self {
Self::IndexedField(id, irs) => Some((id, irs.clone(), None)), Self::IndexedField(id, irs) => Some((id, irs, None)),
Self::RecordField(id, ro) => Some((id, ro.locals.clone(), Some(ro.remotes.clone()))), Self::RecordField(id, ro) => Some((id, &ro.locals, Some(&ro.remotes))),
_ => None, _ => None,
} }
} }

View file

@ -172,13 +172,26 @@ impl<'a> Index<'a> {
beg.extend_from_slice(&[0xff]); beg.extend_from_slice(&[0xff]);
beg beg
} }
pub fn prefix_ids_composite_beg(ns: &str, db: &str, tb: &str, ix: &str, fd: &Array) -> Vec<u8> {
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<u8> {
let mut beg = Self::prefix_ids(ns, db, tb, ix, fd);
*beg.last_mut().unwrap() = 0xff;
beg
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
#[test] #[test]
fn key() { fn key() {
use super::*;
#[rustfmt::skip] #[rustfmt::skip]
let fd = vec!["testfd1", "testfd2"].into(); let fd = vec!["testfd1", "testfd2"].into();
let id = "testid".into(); let id = "testid".into();
@ -192,4 +205,26 @@ mod tests {
let dec = Index::decode(&enc).unwrap(); let dec = Index::decode(&enc).unwrap();
assert_eq!(val, dec); 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");
}
} }

View file

@ -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> { pub(super) fn get_val(&self) -> Option<&Val> {
self.saved_val.as_ref() self.saved_val.as_ref()
} }

View file

@ -344,7 +344,7 @@ impl Test {
/// Expect the given value to be equals to the next response. /// Expect the given value to be equals to the next response.
#[allow(dead_code)] #[allow(dead_code)]
pub fn expect_val(&mut self, val: &str) -> Result<&mut Self, Error> { 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)] #[allow(dead_code)]

View file

@ -2965,3 +2965,101 @@ async fn select_from_unique_index_descending() -> Result<(), Error> {
// //
Ok(()) 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
}