use loro::LoroValue;
use nodedb_types::collection_config::PrimaryEngine;
use nodedb_types::sync::wire::CollectionDescriptor;
use crate::engine::crdt::CrdtEngine;
use crate::engine::vector::AttachedVectors;
use crate::error::LiteError;
use crate::nodedb::collection::CollectionMeta;
use crate::storage::engine::StorageEngine;
pub(crate) const EMBEDDING_DIM_FIELD: &str = "embedding_dim";
pub(crate) const VECTOR_FIELD_TAG: &str = "__field";
pub(crate) const VECTOR_ROW_FIELDS: &[&str] = &[EMBEDDING_DIM_FIELD, VECTOR_FIELD_TAG];
pub(crate) async fn is_vector_primary<S: StorageEngine>(
storage: &S,
collection: &str,
) -> Result<bool, LiteError> {
let key = format!("collection:{collection}");
let Some(bytes) = storage
.get(nodedb_types::Namespace::Meta, key.as_bytes())
.await?
else {
return Ok(false);
};
let Ok(meta) = sonic_rs::from_slice::<CollectionMeta>(&bytes) else {
return Ok(false);
};
let Some(descriptor) = meta
.descriptor_json
.as_deref()
.and_then(|json| sonic_rs::from_str::<CollectionDescriptor>(json).ok())
else {
return Ok(false);
};
Ok(matches!(descriptor.primary, PrimaryEngine::Vector) || descriptor.vector_primary.is_some())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum VectorSlot<'a> {
Base,
Named(&'a str),
}
pub(crate) fn detach_vector_row(
crdt: &mut CrdtEngine,
collection: &str,
doc_id: &str,
detached: VectorSlot<'_>,
remaining: &AttachedVectors,
vector_primary: bool,
) -> Result<u64, LiteError> {
let Some(row) = crdt.read(collection, doc_id) else {
return Ok(0);
};
if !remaining.any() {
if vector_primary || !has_document_fields(&row) {
return crdt.delete(collection, doc_id);
}
let (_, mutation_id) = crdt.remove_fields(collection, doc_id, VECTOR_ROW_FIELDS)?;
return Ok(mutation_id);
}
let VectorSlot::Named(field) = detached else {
return Ok(0);
};
match remaining.named.first() {
None => {
let (_, mutation_id) = crdt.remove_fields(collection, doc_id, &[VECTOR_FIELD_TAG])?;
Ok(mutation_id)
}
Some(other) if field_tag(&row) == Some(field) => crdt.set_fields(
collection,
doc_id,
&[(VECTOR_FIELD_TAG, LoroValue::String(other.as_str().into()))],
),
Some(_) => Ok(0),
}
}
fn field_tag(row: &LoroValue) -> Option<&str> {
match row {
LoroValue::Map(map) => match map.get(VECTOR_FIELD_TAG) {
Some(LoroValue::String(tag)) => Some(tag.as_str()),
_ => None,
},
_ => None,
}
}
fn has_document_fields(row: &LoroValue) -> bool {
match row {
LoroValue::Map(map) => map.keys().any(|k| !VECTOR_ROW_FIELDS.contains(&k.as_str())),
_ => false,
}
}