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