use std::sync::Arc;
use super::IndexCandidate;
use crate::catalog::IndexDefinition;
use crate::expr::BinaryOperator;
use crate::expr::operator::MatchesOperator;
use crate::expr::with::With;
use crate::idx::planner::ScanDirection;
use crate::val::{Number, Value};
#[derive(Debug, Clone)]
pub struct IndexRef {
pub(crate) indexes: Arc<[IndexDefinition]>,
pub(crate) idx: usize,
}
impl IndexRef {
pub fn new(indexes: Arc<[IndexDefinition]>, idx: usize) -> Self {
Self {
indexes,
idx,
}
}
pub fn definition(&self) -> &IndexDefinition {
&self.indexes[self.idx]
}
pub fn is_unique(&self) -> bool {
matches!(self.definition().index, crate::catalog::Index::Uniq)
}
}
impl std::ops::Deref for IndexRef {
type Target = IndexDefinition;
fn deref(&self) -> &Self::Target {
self.definition()
}
}
impl std::hash::Hash for IndexRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.idx.hash(state);
}
}
impl PartialEq for IndexRef {
fn eq(&self, other: &Self) -> bool {
self.idx == other.idx
}
}
impl Eq for IndexRef {}
#[derive(Debug, Clone)]
pub enum AccessPath {
TableScan,
EmptyScan,
BTreeScan {
index_ref: IndexRef,
access: BTreeAccess,
direction: ScanDirection,
},
FullTextSearch {
index_ref: IndexRef,
query: String,
operator: MatchesOperator,
},
KnnSearch {
index_ref: IndexRef,
vector: Vec<Number>,
k: u32,
ef: u32,
},
Union {
paths: Vec<AccessPath>,
dedupe: bool,
},
}
impl AccessPath {
pub fn is_full_range_scan(&self) -> bool {
matches!(
self,
AccessPath::BTreeScan {
access: BTreeAccess::Range {
from: None,
to: None,
},
..
}
)
}
}
#[derive(Debug, Clone)]
pub enum BTreeAccess {
Equality(Value),
Range {
from: Option<RangeBound>,
to: Option<RangeBound>,
},
Compound {
prefix: Vec<Value>,
range: Option<(BinaryOperator, Value)>,
},
FullText {
query: String,
operator: crate::expr::operator::MatchesOperator,
},
Knn {
vector: Vec<Number>,
k: u32,
ef: u32,
},
}
#[derive(Debug, Clone)]
pub struct RangeBound {
pub value: Value,
pub inclusive: bool,
}
impl RangeBound {
pub fn inclusive(value: Value) -> Self {
Self {
value,
inclusive: true,
}
}
pub fn exclusive(value: Value) -> Self {
Self {
value,
inclusive: false,
}
}
}
pub fn select_access_path(
candidates: Vec<IndexCandidate>,
with_hints: Option<&With>,
direction: ScanDirection,
) -> AccessPath {
if matches!(with_hints, Some(With::NoIndex)) {
return AccessPath::TableScan;
}
if let Some(With::Index(names)) = with_hints {
if let Some(candidate) = find_hinted_index(&candidates, names) {
return candidate.to_access_path(direction);
}
tracing::warn!(
target: "surreal::index",
hinted = ?names,
candidates = ?candidates.iter().map(|c| c.index_ref.name.as_str()).collect::<Vec<_>>(),
"WITH INDEX hint did not match any analyzed candidate; falling back to best-effort plan",
);
}
if candidates.is_empty() {
return AccessPath::TableScan;
}
candidates
.into_iter()
.max_by_key(|c| c.score())
.map(|c| c.to_access_path(direction))
.unwrap_or(AccessPath::TableScan)
}
fn find_hinted_index<'a>(
candidates: &'a [IndexCandidate],
names: &[String],
) -> Option<&'a IndexCandidate> {
for name in names {
if let Some(candidate) = candidates.iter().find(|c| &c.index_ref.name == name) {
return Some(candidate);
}
}
None
}