1use std::collections::HashMap;
2
3use crate::application::ports::RuntimeEntityPort;
4use crate::json::{parse_json, to_vec as json_to_vec, Map, Value as JsonValue};
5use crate::storage::unified::devx::refs::{NodeRef, TableRef, VectorRef};
6use crate::storage::unified::{Metadata, MetadataValue, RefTarget, SparseVector, VectorData};
7use crate::storage::{EntityId, UnifiedEntity};
8use crate::{RedDBError, RedDBResult};
9use reddb_types::Value;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::Arc;
12
13pub(crate) struct AutocommitUpdate {
14 manager: Arc<crate::storage::transaction::snapshot::SnapshotManager>,
15 pub(crate) xid: u64,
16 finished: AtomicBool,
17}
18
19impl AutocommitUpdate {
20 pub(crate) fn new(
21 manager: Arc<crate::storage::transaction::snapshot::SnapshotManager>,
22 ) -> Arc<Self> {
23 let xid = manager.begin_cache_coherent_update();
24 Arc::new(Self {
25 manager,
26 xid,
27 finished: AtomicBool::new(false),
28 })
29 }
30
31 pub(crate) fn commit(&self) {
32 if !self.finished.swap(true, Ordering::AcqRel) {
33 self.manager.commit(self.xid);
34 }
35 }
36}
37
38impl std::fmt::Debug for AutocommitUpdate {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("AutocommitUpdate")
41 .field("xid", &self.xid)
42 .finish()
43 }
44}
45
46impl Drop for AutocommitUpdate {
47 fn drop(&mut self) {
48 if !self.finished.load(Ordering::Acquire) {
49 self.manager.rollback(self.xid);
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
55pub struct CreateEntityOutput {
56 pub id: EntityId,
57 pub entity: Option<UnifiedEntity>,
58}
59
60#[derive(Debug, Clone)]
61pub struct AppliedEntityMutation {
62 pub(crate) autocommit_update: Option<Arc<AutocommitUpdate>>,
63 pub id: EntityId,
64 pub collection: String,
65 pub entity: UnifiedEntity,
66 pub metadata: Option<crate::storage::unified::Metadata>,
67 pub modified_columns: Vec<String>,
68 pub persist_metadata: bool,
69 pub context_index_dirty: bool,
70 pub replaced_entity: Option<UnifiedEntity>,
73 pub replaced_entity_previous_xmax: u64,
75 pub pre_mutation_fields: Vec<(String, Value)>,
81}
82
83#[derive(Debug, Clone, Default, PartialEq)]
93pub struct RowDamageVector {
94 pub changed: Vec<(String, Value, Value)>,
97 pub added: Vec<(String, Value)>,
99 pub removed: Vec<(String, Value)>,
101}
102
103impl RowDamageVector {
104 pub fn is_empty(&self) -> bool {
108 self.changed.is_empty() && self.added.is_empty() && self.removed.is_empty()
109 }
110
111 pub fn touched_columns(&self) -> Vec<&str> {
116 let mut out: Vec<&str> =
117 Vec::with_capacity(self.changed.len() + self.added.len() + self.removed.len());
118 out.extend(self.changed.iter().map(|(c, _, _)| c.as_str()));
119 out.extend(self.added.iter().map(|(c, _)| c.as_str()));
120 out.extend(self.removed.iter().map(|(c, _)| c.as_str()));
121 out
122 }
123}
124
125pub fn row_damage_vector(
131 old_fields: &[(String, Value)],
132 new_fields: &[(String, Value)],
133) -> RowDamageVector {
134 let old_map: HashMap<&str, &Value> = old_fields.iter().map(|(k, v)| (k.as_str(), v)).collect();
139 let new_map: HashMap<&str, &Value> = new_fields.iter().map(|(k, v)| (k.as_str(), v)).collect();
140
141 let mut changed = Vec::new();
142 let mut added = Vec::new();
143 let mut removed = Vec::new();
144
145 for (name, new_value) in &new_map {
146 match old_map.get(name) {
147 Some(old_value) if old_value == new_value => {}
148 Some(old_value) => changed.push((
149 (*name).to_string(),
150 (*old_value).clone(),
151 (*new_value).clone(),
152 )),
153 None => added.push(((*name).to_string(), (*new_value).clone())),
154 }
155 }
156 for (name, old_value) in &old_map {
157 if !new_map.contains_key(name) {
158 removed.push(((*name).to_string(), (*old_value).clone()));
159 }
160 }
161
162 RowDamageVector {
163 changed,
164 added,
165 removed,
166 }
167}
168
169pub use reddb_engine::catalog::enforcement::{RowUpdateColumnRule, RowUpdateContractPlan};
170
171#[derive(Debug, Clone)]
172pub struct CreateRowInput {
173 pub collection: String,
174 pub fields: Vec<(String, Value)>,
175 pub metadata: Vec<(String, MetadataValue)>,
176 pub node_links: Vec<NodeRef>,
177 pub vector_links: Vec<VectorRef>,
178}
179
180#[derive(Debug, Clone)]
181pub struct CreateRowsBatchInput {
182 pub collection: String,
183 pub rows: Vec<CreateRowInput>,
184 pub suppress_events: bool,
186}
187
188#[derive(Debug, Clone)]
189pub struct CreateNodeEmbeddingInput {
190 pub name: String,
191 pub vector: Vec<f32>,
192 pub model: Option<String>,
193}
194
195#[derive(Debug, Clone)]
196pub struct CreateNodeTableLinkInput {
197 pub key: String,
198 pub table: TableRef,
199}
200
201#[derive(Debug, Clone)]
202pub struct CreateNodeGraphLinkInput {
203 pub target: EntityId,
204 pub edge_label: String,
205 pub weight: f32,
206}
207
208#[derive(Debug, Clone)]
209pub struct CreateNodeInput {
210 pub collection: String,
211 pub label: String,
212 pub node_type: Option<String>,
213 pub properties: Vec<(String, Value)>,
214 pub metadata: Vec<(String, MetadataValue)>,
215 pub embeddings: Vec<CreateNodeEmbeddingInput>,
216 pub table_links: Vec<CreateNodeTableLinkInput>,
217 pub node_links: Vec<CreateNodeGraphLinkInput>,
218}
219
220#[derive(Debug, Clone)]
221pub struct CreateEdgeInput {
222 pub collection: String,
223 pub label: String,
224 pub from: EntityId,
225 pub to: EntityId,
226 pub weight: Option<f32>,
227 pub properties: Vec<(String, Value)>,
228 pub metadata: Vec<(String, MetadataValue)>,
229}
230
231#[derive(Debug, Clone)]
232pub struct CreateVectorInput {
233 pub collection: String,
234 pub dense: Vec<f32>,
235 pub content: Option<String>,
236 pub metadata: Vec<(String, MetadataValue)>,
237 pub link_row: Option<TableRef>,
238 pub link_node: Option<NodeRef>,
239}
240
241#[derive(Debug, Clone)]
242pub struct CreateDocumentInput {
243 pub collection: String,
244 pub body: JsonValue,
245 pub metadata: Vec<(String, MetadataValue)>,
246 pub node_links: Vec<NodeRef>,
247 pub vector_links: Vec<VectorRef>,
248}
249
250#[derive(Debug, Clone)]
251pub struct CreateKvInput {
252 pub collection: String,
253 pub key: String,
254 pub value: Value,
255 pub metadata: Vec<(String, MetadataValue)>,
256}
257
258#[derive(Debug, Clone)]
259pub struct CreateTimeSeriesPointInput {
260 pub collection: String,
261 pub metric: String,
262 pub value: f64,
263 pub timestamp_ns: Option<u64>,
264 pub tags: Vec<(String, String)>,
265 pub metadata: Vec<(String, MetadataValue)>,
266}
267
268#[derive(Debug, Clone)]
269pub struct DeleteEntityInput {
270 pub collection: String,
271 pub id: EntityId,
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum PatchEntityOperationType {
276 Set,
277 Replace,
278 Unset,
279}
280
281#[derive(Debug, Clone)]
282pub struct PatchEntityOperation {
283 pub op: PatchEntityOperationType,
284 pub path: Vec<String>,
285 pub value: Option<JsonValue>,
286}
287
288#[derive(Debug, Clone)]
289pub struct PatchEntityInput {
290 pub collection: String,
291 pub id: EntityId,
292 pub payload: JsonValue,
293 pub operations: Vec<PatchEntityOperation>,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
297pub struct DeleteEntityOutput {
298 pub deleted: bool,
299 pub id: EntityId,
300}
301
302pub struct EntityUseCases<'a, P: ?Sized> {
303 runtime: &'a P,
304}
305
306impl<'a, P: RuntimeEntityPort + ?Sized> EntityUseCases<'a, P> {
307 pub fn new(runtime: &'a P) -> Self {
308 Self { runtime }
309 }
310
311 pub fn create_row(&self, input: CreateRowInput) -> RedDBResult<CreateEntityOutput> {
312 self.runtime.create_row(input)
313 }
314
315 pub fn create_rows_batch(
316 &self,
317 input: CreateRowsBatchInput,
318 ) -> RedDBResult<Vec<CreateEntityOutput>> {
319 self.runtime.create_rows_batch(input)
320 }
321
322 pub fn create_node(&self, input: CreateNodeInput) -> RedDBResult<CreateEntityOutput> {
323 self.runtime.create_node(input)
324 }
325
326 pub fn create_edge(&self, input: CreateEdgeInput) -> RedDBResult<CreateEntityOutput> {
327 self.runtime.create_edge(input)
328 }
329
330 pub fn create_vector(&self, input: CreateVectorInput) -> RedDBResult<CreateEntityOutput> {
331 self.runtime.create_vector(input)
332 }
333
334 pub fn create_document(&self, input: CreateDocumentInput) -> RedDBResult<CreateEntityOutput> {
335 self.runtime.create_document(input)
336 }
337
338 pub fn create_kv(&self, input: CreateKvInput) -> RedDBResult<CreateEntityOutput> {
339 self.runtime.create_kv(input)
340 }
341
342 pub fn create_timeseries_point(
343 &self,
344 input: CreateTimeSeriesPointInput,
345 ) -> RedDBResult<CreateEntityOutput> {
346 self.runtime.create_timeseries_point(input)
347 }
348
349 pub fn get_kv(&self, collection: &str, key: &str) -> RedDBResult<Option<(Value, EntityId)>> {
350 self.runtime.get_kv(collection, key)
351 }
352
353 pub fn delete_kv(&self, collection: &str, key: &str) -> RedDBResult<bool> {
354 self.runtime.delete_kv(collection, key)
355 }
356
357 pub fn patch(&self, input: PatchEntityInput) -> RedDBResult<CreateEntityOutput> {
358 self.runtime.patch_entity(input)
359 }
360
361 pub fn delete(&self, input: DeleteEntityInput) -> RedDBResult<DeleteEntityOutput> {
362 self.runtime.delete_entity(input)
363 }
364}
365
366pub(crate) fn json_to_storage_value(value: &JsonValue) -> RedDBResult<Value> {
367 match value {
368 JsonValue::Null => Ok(Value::Null),
369 JsonValue::Bool(value) => Ok(Value::Boolean(*value)),
370 JsonValue::Integer(value) => Ok(Value::Integer(*value)),
371 JsonValue::Number(value) => {
372 if value.fract().abs() < f64::EPSILON {
373 Ok(Value::Integer(*value as i64))
374 } else {
375 Ok(Value::Float(*value))
376 }
377 }
378 JsonValue::Decimal(value) => Ok(Value::DecimalText(value.clone())),
379 JsonValue::String(value) => Ok(Value::text(value.clone())),
380 JsonValue::Array(_) | JsonValue::Object(_) => json_to_vec(value)
381 .map(Value::Json)
382 .map_err(|err| RedDBError::Query(format!("failed to serialize JSON value: {err}"))),
383 }
384}
385
386pub(crate) fn json_to_metadata_value(value: &JsonValue) -> RedDBResult<MetadataValue> {
387 match value {
388 JsonValue::Null => Ok(MetadataValue::Null),
389 JsonValue::Bool(value) => Ok(MetadataValue::Bool(*value)),
390 JsonValue::Integer(value) => Ok(MetadataValue::Int(*value)),
391 JsonValue::Number(value) => {
392 if value.fract().abs() < f64::EPSILON {
393 Ok(MetadataValue::Int(*value as i64))
394 } else {
395 Ok(MetadataValue::Float(*value))
396 }
397 }
398 JsonValue::Decimal(value) => Ok(MetadataValue::String(value.clone())),
399 JsonValue::String(value) => Ok(MetadataValue::String(value.clone())),
400 JsonValue::Array(values) => {
401 let mut items = Vec::with_capacity(values.len());
402 for value in values {
403 items.push(json_to_metadata_value(value)?);
404 }
405 Ok(MetadataValue::Array(items))
406 }
407 JsonValue::Object(map) => {
408 let mut object = HashMap::with_capacity(map.len());
409 for (key, value) in map {
410 object.insert(key.clone(), json_to_metadata_value(value)?);
411 }
412 Ok(MetadataValue::Object(object))
413 }
414 }
415}
416
417pub(crate) fn apply_patch_operations_to_storage_map(
418 fields: &mut HashMap<String, Value>,
419 operations: &[PatchEntityOperation],
420) -> RedDBResult<()> {
421 if operations.is_empty() {
422 return Ok(());
423 }
424
425 let mut patch_target = JsonValue::Object(
426 fields
427 .iter()
428 .map(|(key, value)| (key.clone(), value.to_json()))
429 .collect(),
430 );
431 apply_patch_operations_to_json(&mut patch_target, operations)
432 .map_err(|error| RedDBError::Query(format!("patch fields failed: {error}")))?;
433
434 let JsonValue::Object(object) = patch_target else {
435 return Err(RedDBError::Query(
436 "patch operations require object roots".to_string(),
437 ));
438 };
439
440 let mut merged = HashMap::with_capacity(object.len());
441 for (key, value) in object {
442 merged.insert(key, json_to_storage_value(&value)?);
443 }
444 *fields = merged;
445 Ok(())
446}
447
448pub fn apply_patch_operations_to_json_public(
453 value: &mut JsonValue,
454 operations: &[PatchEntityOperation],
455) -> Result<(), String> {
456 apply_patch_operations_to_json(value, operations)
457}
458
459pub(crate) fn apply_patch_operations_to_json(
460 value: &mut JsonValue,
461 operations: &[PatchEntityOperation],
462) -> Result<(), String> {
463 for operation in operations {
464 if operation.path.is_empty() {
465 return Err("patch path cannot be empty".to_string());
466 }
467
468 match operation.op {
469 PatchEntityOperationType::Set | PatchEntityOperationType::Replace => {
470 let Some(patch_value) = &operation.value else {
471 return Err("set/replace operations require a value".to_string());
472 };
473 apply_patch_json_set(value, &operation.path, patch_value.clone())?;
474 }
475 PatchEntityOperationType::Unset => {
476 apply_patch_json_unset(value, &operation.path)?;
477 }
478 }
479 }
480 Ok(())
481}
482
483pub(crate) fn apply_patch_operations_to_vector_fields(
484 vector: &mut VectorData,
485 operations: &[PatchEntityOperation],
486) -> RedDBResult<()> {
487 if operations.is_empty() {
488 return Ok(());
489 }
490
491 let mut vector_target = JsonValue::Object({
492 let mut object = Map::new();
493 object.insert(
494 "dense".to_string(),
495 JsonValue::Array(
496 vector
497 .dense
498 .iter()
499 .map(|value| JsonValue::Number(*value as f64))
500 .collect(),
501 ),
502 );
503 object.insert(
504 "sparse".to_string(),
505 vector.sparse.as_ref().map_or(JsonValue::Null, |sparse| {
506 let mut object = Map::new();
507 object.insert(
508 "indices".to_string(),
509 JsonValue::Array(
510 sparse
511 .indices
512 .iter()
513 .map(|value| JsonValue::Number(*value as f64))
514 .collect(),
515 ),
516 );
517 object.insert(
518 "values".to_string(),
519 JsonValue::Array(
520 sparse
521 .values
522 .iter()
523 .map(|value| JsonValue::Number(*value as f64))
524 .collect(),
525 ),
526 );
527 object.insert(
528 "dimension".to_string(),
529 JsonValue::Number(sparse.dimension as f64),
530 );
531 JsonValue::Object(object)
532 }),
533 );
534 object.insert(
535 "content".to_string(),
536 match vector.content.as_ref() {
537 Some(value) => JsonValue::String(value.clone()),
538 None => JsonValue::Null,
539 },
540 );
541 object
542 });
543
544 let touched_dense = operations
545 .iter()
546 .any(|operation| operation.path.first().is_some_and(|key| key == "dense"));
547 let touched_sparse = operations
548 .iter()
549 .any(|operation| operation.path.first().is_some_and(|key| key == "sparse"));
550 let touched_content = operations
551 .iter()
552 .any(|operation| operation.path.first().is_some_and(|key| key == "content"));
553
554 apply_patch_operations_to_json(&mut vector_target, operations)
555 .map_err(|error| RedDBError::Query(format!("patch fields failed: {error}")))?;
556
557 let JsonValue::Object(object) = vector_target else {
558 return Err(RedDBError::Query(
559 "patch operations require object roots".to_string(),
560 ));
561 };
562
563 if touched_dense {
564 let Some(value) = object.get("dense") else {
565 return Err(RedDBError::Query(
566 "field 'dense' cannot be unset".to_string(),
567 ));
568 };
569 vector.dense = parse_patch_f32_vector(value, "dense")?;
570 }
571
572 if touched_content {
573 vector.content = match object.get("content") {
574 None | Some(JsonValue::Null) => None,
575 Some(value) => Some(
576 value
577 .as_str()
578 .ok_or_else(|| {
579 RedDBError::Query("field 'content' must be a string".to_string())
580 })?
581 .to_string(),
582 ),
583 };
584 }
585
586 if touched_sparse {
587 vector.sparse = match object.get("sparse") {
588 Some(value) => parse_sparse_vector_value(value)?,
589 None => None,
590 };
591 }
592
593 Ok(())
594}
595
596pub(crate) fn metadata_to_json(metadata: &Metadata) -> JsonValue {
597 JsonValue::Object(
598 metadata
599 .iter()
600 .map(|(key, value)| (key.clone(), metadata_value_to_json(value)))
601 .collect(),
602 )
603}
604
605pub(crate) fn metadata_from_json(payload: &JsonValue) -> RedDBResult<Metadata> {
606 let JsonValue::Object(object) = payload else {
607 return Err(RedDBError::Query(
608 "metadata patch requires an object".to_string(),
609 ));
610 };
611
612 let mut metadata = Metadata::new();
613 for (key, value) in object {
614 metadata.set(key.clone(), metadata_value_from_json(value)?);
615 }
616 Ok(metadata)
617}
618
619fn metadata_value_to_json(value: &MetadataValue) -> JsonValue {
620 match value {
621 MetadataValue::Null => JsonValue::Null,
622 MetadataValue::Bool(value) => JsonValue::Bool(*value),
623 MetadataValue::Int(value) => JsonValue::Number(*value as f64),
624 MetadataValue::Float(value) => JsonValue::Number(*value),
625 MetadataValue::String(value) => JsonValue::String(value.clone()),
626 MetadataValue::Bytes(value) => {
627 let mut object = Map::new();
628 object.insert(
629 "__redb_type".to_string(),
630 JsonValue::String("bytes".to_string()),
631 );
632 object.insert(
633 "value".to_string(),
634 JsonValue::Array(
635 value
636 .iter()
637 .map(|value| JsonValue::Number(*value as f64))
638 .collect(),
639 ),
640 );
641 JsonValue::Object(object)
642 }
643 MetadataValue::Array(values) => {
644 JsonValue::Array(values.iter().map(metadata_value_to_json).collect())
645 }
646 MetadataValue::Object(object) => JsonValue::Object(
647 object
648 .iter()
649 .map(|(key, value)| (key.clone(), metadata_value_to_json(value)))
650 .collect(),
651 ),
652 MetadataValue::Timestamp(value) => JsonValue::Number(*value as f64),
653 MetadataValue::Geo { lat, lon } => {
654 let mut object = Map::new();
655 object.insert(
656 "__redb_type".to_string(),
657 JsonValue::String("geo".to_string()),
658 );
659 object.insert("lat".to_string(), JsonValue::Number(*lat));
660 object.insert("lon".to_string(), JsonValue::Number(*lon));
661 JsonValue::Object(object)
662 }
663 MetadataValue::Reference(value) => {
664 let mut object = Map::new();
665 object.insert(
666 "__redb_type".to_string(),
667 JsonValue::String("reference".to_string()),
668 );
669 let (kind, collection, id) = match value {
670 RefTarget::TableRow { table, row_id } => ("table_row", table.as_str(), *row_id),
671 RefTarget::Node {
672 collection,
673 node_id,
674 } => ("node", collection.as_str(), node_id.raw()),
675 RefTarget::Edge {
676 collection,
677 edge_id,
678 } => ("edge", collection.as_str(), edge_id.raw()),
679 RefTarget::Vector {
680 collection,
681 vector_id,
682 } => ("vector", collection.as_str(), vector_id.raw()),
683 RefTarget::Entity {
684 collection,
685 entity_id,
686 } => ("entity", collection.as_str(), entity_id.raw()),
687 };
688 object.insert("kind".to_string(), JsonValue::String(kind.to_string()));
689 object.insert(
690 "collection".to_string(),
691 JsonValue::String(collection.to_string()),
692 );
693 object.insert("id".to_string(), JsonValue::Number(id as f64));
694 JsonValue::Object(object)
695 }
696 MetadataValue::References(values) => {
697 let mut object = Map::new();
698 object.insert(
699 "__redb_type".to_string(),
700 JsonValue::String("references".to_string()),
701 );
702 object.insert(
703 "values".to_string(),
704 JsonValue::Array(
705 values
706 .iter()
707 .map(|r| metadata_value_to_json(&MetadataValue::Reference(r.clone())))
708 .collect(),
709 ),
710 );
711 JsonValue::Object(object)
712 }
713 }
714}
715
716fn metadata_value_from_json(value: &JsonValue) -> RedDBResult<MetadataValue> {
717 match value {
718 JsonValue::Null => Ok(MetadataValue::Null),
719 JsonValue::Bool(value) => Ok(MetadataValue::Bool(*value)),
720 JsonValue::Integer(value) => Ok(MetadataValue::Int(*value)),
721 JsonValue::Number(value) => {
722 if value.fract().abs() < f64::EPSILON {
723 Ok(MetadataValue::Int(*value as i64))
724 } else {
725 Ok(MetadataValue::Float(*value))
726 }
727 }
728 JsonValue::Decimal(value) => Ok(MetadataValue::String(value.clone())),
729 JsonValue::String(value) => Ok(MetadataValue::String(value.clone())),
730 JsonValue::Array(values) => {
731 let mut out = Vec::with_capacity(values.len());
732 for value in values {
733 out.push(metadata_value_from_json(value)?);
734 }
735 Ok(MetadataValue::Array(out))
736 }
737 JsonValue::Object(object) => {
738 if let Some(marker) = object.get("__redb_type").and_then(JsonValue::as_str) {
739 match marker {
740 "bytes" => {
741 let values = object
742 .get("value")
743 .and_then(JsonValue::as_array)
744 .ok_or_else(|| {
745 RedDBError::Query(
746 "metadata marker 'bytes' requires array value".to_string(),
747 )
748 })?;
749 let mut out = Vec::with_capacity(values.len());
750 for value in values {
751 let value = value.as_i64().ok_or_else(|| {
752 RedDBError::Query(
753 "metadata bytes must contain integer values".to_string(),
754 )
755 })?;
756 if !(0..=255).contains(&value) {
757 return Err(RedDBError::Query(
758 "metadata bytes must contain values between 0 and 255"
759 .to_string(),
760 ));
761 }
762 out.push(value as u8);
763 }
764 return Ok(MetadataValue::Bytes(out));
765 }
766 "geo" => {
767 let lat =
768 object
769 .get("lat")
770 .and_then(JsonValue::as_f64)
771 .ok_or_else(|| {
772 RedDBError::Query(
773 "metadata marker 'geo' requires numeric 'lat'".to_string(),
774 )
775 })?;
776 let lon =
777 object
778 .get("lon")
779 .and_then(JsonValue::as_f64)
780 .ok_or_else(|| {
781 RedDBError::Query(
782 "metadata marker 'geo' requires numeric 'lon'".to_string(),
783 )
784 })?;
785 return Ok(MetadataValue::Geo { lat, lon });
786 }
787 "reference" => {
788 return parse_metadata_reference(object).map(MetadataValue::Reference)
789 }
790 "references" => {
791 let values = object
792 .get("values")
793 .and_then(JsonValue::as_array)
794 .ok_or_else(|| {
795 RedDBError::Query(
796 "metadata marker 'references' requires array 'values'"
797 .to_string(),
798 )
799 })?;
800 let mut references = Vec::with_capacity(values.len());
801 for value in values {
802 references.push(parse_metadata_reference_value(value)?);
803 }
804 return Ok(MetadataValue::References(references));
805 }
806 _ => {}
807 }
808 }
809
810 let mut out = HashMap::with_capacity(object.len());
811 for (key, value) in object {
812 out.insert(key.clone(), metadata_value_from_json(value)?);
813 }
814 Ok(MetadataValue::Object(out))
815 }
816 }
817}
818
819fn parse_metadata_reference(object: &Map<String, JsonValue>) -> RedDBResult<RefTarget> {
820 let kind = object
821 .get("kind")
822 .and_then(JsonValue::as_str)
823 .ok_or_else(|| RedDBError::Query("metadata reference requires 'kind'".to_string()))?;
824 let collection = object
825 .get("collection")
826 .and_then(JsonValue::as_str)
827 .ok_or_else(|| RedDBError::Query("metadata reference requires 'collection'".to_string()))?;
828 let id = object
829 .get("id")
830 .ok_or_else(|| RedDBError::Query("metadata reference requires 'id'".to_string()))?;
831 let id = parse_patch_u64_value(id, "id")?;
832
833 let target = match kind {
834 "table_row" | "table" => RefTarget::table(collection.to_string(), id),
835 "node" => RefTarget::node(collection.to_string(), EntityId::new(id)),
836 "edge" => RefTarget::Edge {
837 collection: collection.to_string(),
838 edge_id: EntityId::new(id),
839 },
840 "vector" => RefTarget::vector(collection.to_string(), EntityId::new(id)),
841 "entity" => RefTarget::Entity {
842 collection: collection.to_string(),
843 entity_id: EntityId::new(id),
844 },
845 _ => {
846 return Err(RedDBError::Query(format!(
847 "unsupported metadata reference kind '{kind}'"
848 )));
849 }
850 };
851
852 Ok(target)
853}
854
855fn parse_metadata_reference_value(value: &JsonValue) -> RedDBResult<RefTarget> {
856 let JsonValue::Object(object) = value else {
857 return Err(RedDBError::Query(
858 "metadata reference entries must be objects".to_string(),
859 ));
860 };
861 parse_metadata_reference(object)
862}
863
864fn parse_patch_u64_value(value: &JsonValue, field: &str) -> RedDBResult<u64> {
865 let Some(value) = value.as_f64() else {
866 return Err(RedDBError::Query(format!(
867 "field '{field}' must be a number"
868 )));
869 };
870 if value.is_sign_negative() {
871 return Err(RedDBError::Query(format!(
872 "field '{field}' cannot be negative"
873 )));
874 }
875 if value.fract().abs() > f64::EPSILON {
876 return Err(RedDBError::Query(format!(
877 "field '{field}' must be an integer"
878 )));
879 }
880 if value > u64::MAX as f64 {
881 return Err(RedDBError::Query(format!("field '{field}' is too large")));
882 }
883 Ok(value as u64)
884}
885
886fn parse_patch_f32_vector(value: &JsonValue, field: &str) -> RedDBResult<Vec<f32>> {
887 let values = value
888 .as_array()
889 .ok_or_else(|| RedDBError::Query(format!("field '{field}' must be an array")))?;
890 let mut out = Vec::with_capacity(values.len());
891 for value in values {
892 let number = value.as_f64().ok_or_else(|| {
893 RedDBError::Query(format!("field '{field}' must contain only numbers"))
894 })?;
895 out.push(number as f32);
896 }
897 if out.is_empty() {
898 return Err(RedDBError::Query(format!(
899 "field '{field}' cannot be empty"
900 )));
901 }
902 Ok(out)
903}
904
905fn parse_sparse_index_array(value: &JsonValue, field: &str) -> RedDBResult<Vec<u32>> {
906 let values = value
907 .as_array()
908 .ok_or_else(|| RedDBError::Query(format!("field '{field}' must be an array")))?;
909 let mut out = Vec::with_capacity(values.len());
910 for value in values {
911 let value = value.as_f64().ok_or_else(|| {
912 RedDBError::Query(format!("field '{field}' must contain only integers"))
913 })?;
914 if value.is_sign_negative() || value.fract().abs() > f64::EPSILON {
915 return Err(RedDBError::Query(format!(
916 "field '{field}' must contain only u32 values"
917 )));
918 }
919 if value > u32::MAX as f64 {
920 return Err(RedDBError::Query(format!(
921 "field '{field}' value is too large"
922 )));
923 }
924 out.push(value as u32);
925 }
926 Ok(out)
927}
928
929fn parse_sparse_value_array(value: &JsonValue, field: &str) -> RedDBResult<Vec<f32>> {
930 parse_patch_f32_vector(value, field)
931}
932
933fn parse_sparse_vector_value(value: &JsonValue) -> RedDBResult<Option<SparseVector>> {
934 match value {
935 JsonValue::Null => Ok(None),
936 JsonValue::Object(object) => {
937 let indices = parse_sparse_index_array(
938 object.get("indices").ok_or_else(|| {
939 RedDBError::Query("sparse metadata requires 'indices'".to_string())
940 })?,
941 "sparse.indices",
942 )?;
943 let values = parse_sparse_value_array(
944 object.get("values").ok_or_else(|| {
945 RedDBError::Query("sparse metadata requires 'values'".to_string())
946 })?,
947 "sparse.values",
948 )?;
949 if indices.len() != values.len() {
950 return Err(RedDBError::Query(
951 "sparse indices and values lengths must match".to_string(),
952 ));
953 }
954 let dimension = match object.get("dimension").and_then(JsonValue::as_f64) {
955 Some(value) => {
956 if value.is_sign_negative() || value.fract().abs() > f64::EPSILON {
957 return Err(RedDBError::Query(
958 "sparse dimension must be a non-negative integer".to_string(),
959 ));
960 }
961 if value > usize::MAX as f64 {
962 return Err(RedDBError::Query(
963 "sparse dimension is too large".to_string(),
964 ));
965 }
966 value as usize
967 }
968 None => indices
969 .iter()
970 .max()
971 .map_or(0, |index| (*index as usize) + 1),
972 };
973 if indices.iter().any(|index| (*index as usize) >= dimension) {
974 return Err(RedDBError::Query(
975 "sparse indices must be smaller than dimension".to_string(),
976 ));
977 }
978 Ok(Some(SparseVector::new(indices, values, dimension)))
979 }
980 _ => Err(RedDBError::Query(
981 "field 'sparse' must be an object or null".to_string(),
982 )),
983 }
984}
985
986fn apply_patch_json_set(
987 target: &mut JsonValue,
988 path: &[String],
989 value: JsonValue,
990) -> Result<(), String> {
991 if path.is_empty() {
992 return Err("patch path cannot be empty".to_string());
993 }
994
995 let mut current = target;
996 for segment in &path[..path.len() - 1] {
997 let JsonValue::Object(object) = current else {
998 return Err("patch path target must be an object".to_string());
999 };
1000 let value = object
1001 .entry(segment.clone())
1002 .or_insert_with(|| JsonValue::Object(Map::new()));
1003 if !matches!(value, JsonValue::Object(_)) {
1004 *value = JsonValue::Object(Map::new());
1005 }
1006 current = value;
1007 }
1008
1009 let JsonValue::Object(object) = current else {
1010 return Err("patch path target must be an object".to_string());
1011 };
1012 object.insert(path[path.len() - 1].clone(), value);
1013 Ok(())
1014}
1015
1016fn apply_patch_json_unset(target: &mut JsonValue, path: &[String]) -> Result<(), String> {
1017 if path.is_empty() {
1018 return Err("patch path cannot be empty".to_string());
1019 }
1020
1021 if path.len() == 1 {
1022 let JsonValue::Object(object) = target else {
1023 return Err("patch path target must be an object".to_string());
1024 };
1025 object.remove(&path[0]);
1026 return Ok(());
1027 }
1028
1029 let mut current = target;
1030 for segment in &path[..path.len() - 1] {
1031 let Some(value) = (match current {
1032 JsonValue::Object(object) => object.get_mut(segment),
1033 _ => {
1034 return Err("patch path target must be an object".to_string());
1035 }
1036 }) else {
1037 return Ok(());
1038 };
1039
1040 if !matches!(value, JsonValue::Object(_)) {
1041 return Ok(());
1042 }
1043 current = value;
1044 }
1045
1046 let JsonValue::Object(object) = current else {
1047 return Err("patch path target must be an object".to_string());
1048 };
1049 object.remove(&path[path.len() - 1]);
1050 Ok(())
1051}
1052
1053fn format_mac(bytes: &[u8; 6]) -> String {
1054 bytes
1055 .iter()
1056 .map(|byte| format!("{byte:02x}"))
1057 .collect::<Vec<_>>()
1058 .join(":")
1059}
1060
1061#[cfg(test)]
1062mod damage_vector_tests {
1063 use super::*;
1064
1065 fn s(n: &str) -> String {
1066 n.to_string()
1067 }
1068
1069 #[test]
1070 fn identical_rows_produce_empty_vector() {
1071 let old = vec![
1072 (s("name"), Value::text(s("alice"))),
1073 (s("age"), Value::Integer(30)),
1074 ];
1075 let new = old.clone();
1076 let dv = row_damage_vector(&old, &new);
1077 assert!(dv.is_empty());
1078 assert!(dv.touched_columns().is_empty());
1079 }
1080
1081 #[test]
1082 fn detects_changed_column_only() {
1083 let old = vec![
1084 (s("name"), Value::text(s("alice"))),
1085 (s("age"), Value::Integer(30)),
1086 ];
1087 let new = vec![
1088 (s("name"), Value::text(s("alice"))),
1089 (s("age"), Value::Integer(31)),
1090 ];
1091 let dv = row_damage_vector(&old, &new);
1092 assert_eq!(dv.changed.len(), 1);
1093 assert_eq!(dv.changed[0].0, "age");
1094 assert!(dv.added.is_empty());
1095 assert!(dv.removed.is_empty());
1096 assert_eq!(dv.touched_columns(), vec!["age"]);
1097 }
1098
1099 #[test]
1100 fn detects_added_and_removed_columns() {
1101 let old = vec![
1102 (s("name"), Value::text(s("alice"))),
1103 (s("nickname"), Value::text(s("al"))),
1104 ];
1105 let new = vec![
1106 (s("name"), Value::text(s("alice"))),
1107 (s("email"), Value::text(s("a@x.com"))),
1108 ];
1109 let dv = row_damage_vector(&old, &new);
1110 assert!(dv.changed.is_empty());
1111 assert_eq!(dv.added.len(), 1);
1112 assert_eq!(dv.added[0].0, "email");
1113 assert_eq!(dv.removed.len(), 1);
1114 assert_eq!(dv.removed[0].0, "nickname");
1115 }
1116
1117 #[test]
1118 fn field_order_does_not_affect_diff() {
1119 let old = vec![
1122 (s("name"), Value::text(s("bob"))),
1123 (s("age"), Value::Integer(42)),
1124 ];
1125 let new = vec![
1126 (s("age"), Value::Integer(42)),
1127 (s("name"), Value::text(s("bob"))),
1128 ];
1129 assert!(row_damage_vector(&old, &new).is_empty());
1130 }
1131
1132 #[test]
1133 fn mixed_changed_added_removed() {
1134 let old = vec![
1135 (s("a"), Value::Integer(1)),
1136 (s("b"), Value::Integer(2)),
1137 (s("gone"), Value::text(s("x"))),
1138 ];
1139 let new = vec![
1140 (s("a"), Value::Integer(10)), (s("b"), Value::Integer(2)), (s("new"), Value::Boolean(true)), ];
1144 let dv = row_damage_vector(&old, &new);
1145 assert_eq!(dv.changed.len(), 1);
1146 assert_eq!(dv.added.len(), 1);
1147 assert_eq!(dv.removed.len(), 1);
1148 let mut touched: Vec<&str> = dv.touched_columns();
1149 touched.sort();
1150 assert_eq!(touched, vec!["a", "gone", "new"]);
1151 }
1152}