use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use nodedb_types::Surrogate;
use nodedb_types::value::Value;
use crate::engine::crdt::CrdtEngine;
use crate::engine::vector::VectorState;
use crate::engine::vector::nodes::{encode_sidecar, unbind_node, upsert_node};
use crate::engine::vector::resident::{check_insert_widths, lock_resident};
use crate::error::LiteError;
use crate::nodedb::LockExt;
use crate::nodedb::convert::{loro_value_to_document, value_to_loro};
use crate::storage::engine::StorageEngine;
pub(super) use crate::query::on_conflict::apply_patch;
pub(super) type StoredRow = (String, HashMap<String, Value>);
pub(super) use crate::engine::vector::row::EMBEDDING_DIM_FIELD;
pub(super) fn index_key(collection: &str, field: &str) -> String {
if field.is_empty() {
collection.to_string()
} else {
format!("{collection}:{field}")
}
}
pub(super) fn doc_id_for(pk_bytes: &[u8], surrogate: Surrogate) -> Result<String, LiteError> {
if pk_bytes.is_empty() {
return Ok(surrogate.to_string());
}
String::from_utf8(pk_bytes.to_vec()).map_err(|e| LiteError::BadRequest {
detail: format!("vector-primary key is not UTF-8: {e}"),
})
}
pub(super) fn decode_payload(payload: &[u8]) -> Result<HashMap<String, Value>, LiteError> {
if payload.is_empty() {
return Ok(HashMap::new());
}
zerompk::from_msgpack(payload).map_err(|e| LiteError::Serialization {
detail: format!("decode vector-primary payload: {e}"),
})
}
pub(in crate::query::physical_visitor) async fn remove_live_node<S: StorageEngine>(
vector_state: &Arc<VectorState<S>>,
index_key: &str,
doc_id: &str,
) -> Result<bool, LiteError> {
let mut indices = lock_resident(vector_state, index_key).await?;
let Some(index) = indices.get_mut(index_key) else {
return Ok(false);
};
Ok(unbind_node(vector_state, Some(index), index_key, doc_id).is_some())
}
pub(super) async fn insert_node<S: StorageEngine>(
vector_state: &Arc<VectorState<S>>,
index_key: &str,
doc_id: &str,
embedding: &[f32],
op_name: &str,
) -> Result<u32, LiteError> {
check_insert_widths(vector_state, index_key, [embedding.len()]).await?;
let op = crate::engine::vector::durable::put_op(index_key, doc_id, embedding);
vector_state
.storage
.batch_write(std::slice::from_ref(&op))
.await
.map_err(|e| LiteError::Storage {
detail: format!("{op_name}: durable vector write failed: {e}"),
})?;
let internal_id = upsert_node(vector_state, index_key, doc_id, embedding).await?;
encode_sidecar(vector_state, index_key, internal_id, embedding).map_err(|e| {
LiteError::BadRequest {
detail: format!("{op_name}: sidecar install failed: {e}"),
}
})?;
Ok(internal_id)
}
pub(super) async fn remove_durable<S: StorageEngine>(
vector_state: &VectorState<S>,
index_key: &str,
doc_id: &str,
op_name: &str,
) -> Result<(), LiteError> {
crate::engine::vector::durable::remove(&*vector_state.storage, index_key, doc_id)
.await
.map_err(|e| LiteError::Storage {
detail: format!("{op_name}: durable vector remove failed: {e}"),
})
}
pub(super) fn read_row(
crdt: &Mutex<CrdtEngine>,
collection: &str,
doc_id: &str,
) -> Option<HashMap<String, Value>> {
let guard = crdt.lock_or_recover();
let value = guard.read(collection, doc_id)?;
Some(loro_value_to_document(doc_id, &value).fields)
}
pub(super) fn read_all_rows(crdt: &Mutex<CrdtEngine>, collection: &str) -> Vec<StoredRow> {
let guard = crdt.lock_or_recover();
guard
.list_ids(collection)
.into_iter()
.filter_map(|id| {
let value = guard.read(collection, &id)?;
let fields = loro_value_to_document(&id, &value).fields;
Some((id, fields))
})
.collect()
}
pub(super) fn write_row(
crdt: &Mutex<CrdtEngine>,
collection: &str,
doc_id: &str,
dim: usize,
fields: &HashMap<String, Value>,
op_name: &str,
) -> Result<(), LiteError> {
let mut loro_fields: Vec<(&str, loro::LoroValue)> = fields
.iter()
.filter(|(k, _)| k.as_str() != EMBEDDING_DIM_FIELD)
.map(|(k, v)| (k.as_str(), value_to_loro(v)))
.collect();
loro_fields.push((EMBEDDING_DIM_FIELD, loro::LoroValue::I64(dim as i64)));
let mut guard = crdt.lock_or_recover();
if guard.exists(collection, doc_id) {
guard
.delete(collection, doc_id)
.map_err(|e| e.within(&format!("{op_name}: CRDT row replace")))?;
}
guard
.upsert(collection, doc_id, &loro_fields)
.map_err(|e| e.within(&format!("{op_name}: CRDT upsert")))?;
Ok(())
}
pub(super) fn delete_row(
crdt: &Mutex<CrdtEngine>,
collection: &str,
doc_id: &str,
op_name: &str,
) -> Result<bool, LiteError> {
let mut guard = crdt.lock_or_recover();
if !guard.exists(collection, doc_id) {
return Ok(false);
}
guard
.delete(collection, doc_id)
.map_err(|e| e.within(&format!("{op_name}: CRDT delete")))?;
Ok(true)
}