use std::collections::BTreeMap;
use std::sync::Arc;
use uqa_core::{FieldName, IndexStats, PostingList};
use uqa_storage::{
DocumentStore, InvertedIndex, StorageBackendError, StorageBackendResult, VectorIndex,
};
pub type OperatorResult = StorageBackendResult<PostingList>;
pub(crate) fn missing_backend(backend: &str, operation: &str) -> StorageBackendError {
StorageBackendError::Other(format!(
"{operation} requires an execution-context {backend} backend"
))
}
pub(crate) fn require_finite_score(score: f64, operation: &str) -> StorageBackendResult<()> {
if score.is_finite() {
Ok(())
} else {
Err(StorageBackendError::Other(format!(
"{operation} received a non-finite score {score}"
)))
}
}
pub(crate) fn require_probability(probability: f64, operation: &str) -> StorageBackendResult<()> {
if probability.is_finite() && (0.0..=1.0).contains(&probability) {
Ok(())
} else {
Err(StorageBackendError::Other(format!(
"{operation} requires probability scores in [0, 1], got {probability}"
)))
}
}
#[derive(Default, Clone)]
pub struct ExecutionContext {
pub document_store: Option<Arc<dyn DocumentStore>>,
pub inverted_index: Option<Arc<dyn InvertedIndex>>,
pub vector_indexes: BTreeMap<FieldName, Arc<dyn VectorIndex>>,
pub stats: Option<IndexStats>,
pub graph: Option<Arc<dyn GraphNeighborLookup>>,
}
pub trait GraphNeighborLookup: Send + Sync {
fn neighbors(
&self,
vertex: u64,
label: &str,
direction: Direction,
) -> StorageBackendResult<Vec<u64>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Out,
In,
Both,
}
impl ExecutionContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_inverted_index(mut self, idx: Arc<dyn InvertedIndex>) -> Self {
self.inverted_index = Some(idx);
self
}
pub fn with_document_store(mut self, ds: Arc<dyn DocumentStore>) -> Self {
self.document_store = Some(ds);
self
}
pub fn with_vector_index(
mut self,
field: impl Into<FieldName>,
idx: Arc<dyn VectorIndex>,
) -> Self {
self.vector_indexes.insert(field.into(), idx);
self
}
pub fn with_stats(mut self, stats: IndexStats) -> Self {
self.stats = Some(stats);
self
}
pub fn with_graph(mut self, graph: Arc<dyn GraphNeighborLookup>) -> Self {
self.graph = Some(graph);
self
}
}
pub trait Operator: Send + Sync {
fn execute(&self, ctx: &ExecutionContext) -> OperatorResult;
fn cost_estimate(&self, stats: &IndexStats) -> f64 {
stats.total_docs as f64
}
}
pub struct ComposedOperator {
pub operands: Vec<Arc<dyn Operator>>,
}
impl ComposedOperator {
pub fn new(operands: Vec<Arc<dyn Operator>>) -> Self {
Self { operands }
}
}
impl Operator for ComposedOperator {
fn execute(&self, ctx: &ExecutionContext) -> OperatorResult {
let mut result = PostingList::new();
for op in &self.operands {
result = op.execute(ctx)?;
}
Ok(result)
}
fn cost_estimate(&self, stats: &IndexStats) -> f64 {
self.operands.iter().map(|op| op.cost_estimate(stats)).sum()
}
}