nodedb_lite/engine/vector/
row.rs1use loro::LoroValue;
11use nodedb_types::collection_config::PrimaryEngine;
12use nodedb_types::sync::wire::CollectionDescriptor;
13
14use crate::engine::crdt::CrdtEngine;
15use crate::engine::vector::AttachedVectors;
16use crate::error::LiteError;
17use crate::nodedb::collection::CollectionMeta;
18use crate::storage::engine::StorageEngine;
19
20pub(crate) const EMBEDDING_DIM_FIELD: &str = "embedding_dim";
22
23pub(crate) const VECTOR_FIELD_TAG: &str = "__field";
25
26pub(crate) const VECTOR_ROW_FIELDS: &[&str] = &[EMBEDDING_DIM_FIELD, VECTOR_FIELD_TAG];
28
29pub(crate) async fn is_vector_primary<S: StorageEngine>(
36 storage: &S,
37 collection: &str,
38) -> Result<bool, LiteError> {
39 let key = format!("collection:{collection}");
40 let Some(bytes) = storage
41 .get(nodedb_types::Namespace::Meta, key.as_bytes())
42 .await?
43 else {
44 return Ok(false);
45 };
46 let Ok(meta) = sonic_rs::from_slice::<CollectionMeta>(&bytes) else {
47 return Ok(false);
48 };
49 let Some(descriptor) = meta
50 .descriptor_json
51 .as_deref()
52 .and_then(|json| sonic_rs::from_str::<CollectionDescriptor>(json).ok())
53 else {
54 return Ok(false);
55 };
56 Ok(matches!(descriptor.primary, PrimaryEngine::Vector) || descriptor.vector_primary.is_some())
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub(crate) enum VectorSlot<'a> {
62 Base,
64 Named(&'a str),
66}
67
68pub(crate) fn detach_vector_row(
82 crdt: &mut CrdtEngine,
83 collection: &str,
84 doc_id: &str,
85 detached: VectorSlot<'_>,
86 remaining: &AttachedVectors,
87 vector_primary: bool,
88) -> Result<u64, LiteError> {
89 let Some(row) = crdt.read(collection, doc_id) else {
90 return Ok(0);
91 };
92 if !remaining.any() {
93 if vector_primary || !has_document_fields(&row) {
94 return crdt.delete(collection, doc_id);
95 }
96 let (_, mutation_id) = crdt.remove_fields(collection, doc_id, VECTOR_ROW_FIELDS)?;
97 return Ok(mutation_id);
98 }
99 let VectorSlot::Named(field) = detached else {
100 return Ok(0);
101 };
102 match remaining.named.first() {
103 None => {
104 let (_, mutation_id) = crdt.remove_fields(collection, doc_id, &[VECTOR_FIELD_TAG])?;
105 Ok(mutation_id)
106 }
107 Some(other) if field_tag(&row) == Some(field) => crdt.set_fields(
108 collection,
109 doc_id,
110 &[(VECTOR_FIELD_TAG, LoroValue::String(other.as_str().into()))],
111 ),
112 Some(_) => Ok(0),
113 }
114}
115
116fn field_tag(row: &LoroValue) -> Option<&str> {
118 match row {
119 LoroValue::Map(map) => match map.get(VECTOR_FIELD_TAG) {
120 Some(LoroValue::String(tag)) => Some(tag.as_str()),
121 _ => None,
122 },
123 _ => None,
124 }
125}
126
127fn has_document_fields(row: &LoroValue) -> bool {
129 match row {
130 LoroValue::Map(map) => map.keys().any(|k| !VECTOR_ROW_FIELDS.contains(&k.as_str())),
131 _ => false,
132 }
133}