use daimon_core::{DaimonError, Result};
use opensearch::OpenSearch;
use opensearch::http::transport::Transport;
use crate::index_settings;
use crate::store::OpenSearchVectorStore;
use crate::{Engine, SpaceType};
pub struct OpenSearchVectorStoreBuilder {
url: String,
dimensions: usize,
index: String,
space_type: SpaceType,
engine: Engine,
auto_create_index: bool,
hnsw_m: Option<usize>,
hnsw_ef_construction: Option<usize>,
}
impl OpenSearchVectorStoreBuilder {
pub fn new(url: impl Into<String>, dimensions: usize) -> Self {
Self {
url: url.into(),
dimensions,
index: "daimon_vectors".into(),
space_type: SpaceType::default(),
engine: Engine::default(),
auto_create_index: true,
hnsw_m: None,
hnsw_ef_construction: None,
}
}
pub fn index(mut self, index: impl Into<String>) -> Self {
self.index = index.into();
self
}
pub fn space_type(mut self, space_type: SpaceType) -> Self {
self.space_type = space_type;
self
}
pub fn engine(mut self, engine: Engine) -> Self {
self.engine = engine;
self
}
pub fn auto_create_index(mut self, enabled: bool) -> Self {
self.auto_create_index = enabled;
self
}
pub fn hnsw_m(mut self, m: usize) -> Self {
self.hnsw_m = Some(m);
self
}
pub fn hnsw_ef_construction(mut self, ef: usize) -> Self {
self.hnsw_ef_construction = Some(ef);
self
}
pub async fn build_with_client(self, client: OpenSearch) -> Result<OpenSearchVectorStore> {
if self.auto_create_index {
self.ensure_index(&client).await?;
}
Ok(OpenSearchVectorStore {
client,
index: self.index,
dimensions: self.dimensions,
space_type: self.space_type,
})
}
pub async fn build(self) -> Result<OpenSearchVectorStore> {
let transport = Transport::single_node(&self.url)
.map_err(|e| DaimonError::Other(format!("opensearch transport error: {e}")))?;
let client = OpenSearch::new(transport);
self.build_with_client(client).await
}
async fn ensure_index(&self, client: &OpenSearch) -> Result<()> {
let exists = client
.indices()
.exists(opensearch::indices::IndicesExistsParts::Index(&[
&self.index
]))
.send()
.await
.map_err(|e| DaimonError::Other(format!("opensearch index check error: {e}")))?;
if exists.status_code().is_success() {
tracing::debug!("opensearch: index '{}' already exists", self.index);
return Ok(());
}
tracing::info!("opensearch: creating k-NN index '{}'", self.index);
let body = index_settings::create_index_body(
self.dimensions,
self.space_type,
self.engine,
self.hnsw_m,
self.hnsw_ef_construction,
);
let response = client
.indices()
.create(opensearch::indices::IndicesCreateParts::Index(&self.index))
.body(body)
.send()
.await
.map_err(|e| DaimonError::Other(format!("opensearch index create error: {e}")))?;
let status = response.status_code();
if !status.is_success() {
let text = response
.text()
.await
.unwrap_or_else(|_| "unknown error".into());
return Err(DaimonError::Other(format!(
"opensearch index creation failed ({status}): {text}"
)));
}
tracing::info!("opensearch: index '{}' created", self.index);
Ok(())
}
}