use crate::clock::{Clock, SystemClock};
use crate::decide::{Decide, DefaultDecider};
use crate::engine::config::EngineConfig;
use crate::engine::engine::Engine;
use crate::location::table::LocationTable;
use crate::schema::attr::AttrType;
use crate::scoring::VectorBackend;
use crate::Schema;
use std::sync::Arc;
#[derive(Debug, thiserror::Error)]
pub enum BuildError {
#[error("EngineBuilder: schema is required")]
MissingSchema,
#[error("unknown kind: {0}")]
UnknownKind(String),
#[error("unknown attribute: {0}")]
UnknownAttr(String),
#[error("attribute '{attr}' is not declared in kind '{kind}'")]
AttrNotInKind {
kind: String,
attr: String,
},
#[error("embedding slot '{kind}.{attr}' must be AttrType::F32Arr")]
EmbeddingSlotNotF32Arr {
kind: String,
attr: String,
},
}
pub struct EngineBuilder<T: Clone + Send + Sync + 'static> {
schema: Option<Arc<Schema>>,
clock: Option<Arc<dyn Clock>>,
decider: Option<Arc<dyn Decide>>,
default_vector_backend: VectorBackend,
n_shards: usize,
embedding_slot_name: Option<(String, String)>,
_marker: std::marker::PhantomData<fn() -> T>,
}
impl<T: Clone + Send + Sync + 'static> EngineBuilder<T> {
#[must_use]
pub fn new() -> Self {
Self {
schema: None,
clock: None,
decider: None,
default_vector_backend: VectorBackend::Linear,
n_shards: 256,
embedding_slot_name: None,
_marker: std::marker::PhantomData,
}
}
#[must_use]
pub fn schema(mut self, s: Arc<Schema>) -> Self {
self.schema = Some(s);
self
}
#[must_use]
pub fn clock<C: Clock + 'static>(mut self, c: C) -> Self {
self.clock = Some(Arc::new(c));
self
}
#[must_use]
pub fn clock_arc(mut self, c: Arc<dyn Clock>) -> Self {
self.clock = Some(c);
self
}
#[must_use]
pub fn decider<D: Decide + 'static>(mut self, d: D) -> Self {
self.decider = Some(Arc::new(d));
self
}
#[must_use]
pub const fn default_vector_backend(mut self, b: VectorBackend) -> Self {
self.default_vector_backend = b;
self
}
#[must_use]
pub const fn n_shards(mut self, n: usize) -> Self {
self.n_shards = n;
self
}
#[must_use]
pub fn with_embedding_slot(mut self, kind: impl Into<String>, attr: impl Into<String>) -> Self {
self.embedding_slot_name = Some((kind.into(), attr.into()));
self
}
pub fn build(self) -> Result<Engine<T>, BuildError> {
let schema = self.schema.ok_or(BuildError::MissingSchema)?;
let embedding_slot = if let Some((ref kn, ref an)) = self.embedding_slot_name {
let kind_id = schema
.kind_names
.get(kn)
.ok_or_else(|| BuildError::UnknownKind(kn.clone()))?;
let attr_id = schema
.attr_names
.get(an)
.ok_or_else(|| BuildError::UnknownAttr(an.clone()))?;
let slot = schema
.slot_layout
.resolve(kind_id, attr_id)
.ok_or_else(|| BuildError::AttrNotInKind {
kind: kn.clone(),
attr: an.clone(),
})?;
if !matches!(slot.ty, AttrType::F32Arr) {
return Err(BuildError::EmbeddingSlotNotF32Arr {
kind: kn.clone(),
attr: an.clone(),
});
}
Some((kind_id, attr_id))
} else {
None
};
Ok(Engine {
schema: schema.clone(),
table: Arc::new(LocationTable::new(schema)),
config: EngineConfig {
clock: self.clock.unwrap_or_else(|| Arc::new(SystemClock)),
decider: self.decider.unwrap_or_else(|| Arc::new(DefaultDecider)),
default_vector_backend: self.default_vector_backend,
n_shards: self.n_shards,
embedding_slot,
},
metrics: Arc::new(crate::metrics::EngineMetrics::new()),
})
}
}
impl<T: Clone + Send + Sync + 'static> Default for EngineBuilder<T> {
fn default() -> Self {
Self::new()
}
}