1use crate::{FieldId, LnmpValue};
51
52#[derive(Debug, Clone, PartialEq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct LnmpField {
56 pub fid: FieldId,
58 pub value: LnmpValue,
60}
61
62#[derive(Debug, Clone, PartialEq, Default)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65pub struct LnmpRecord {
66 fields: Vec<LnmpField>,
67}
68
69impl LnmpRecord {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn add_field(&mut self, field: LnmpField) {
77 self.fields.push(field);
78 }
79
80 pub fn get_field(&self, fid: FieldId) -> Option<&LnmpField> {
82 self.fields.iter().find(|f| f.fid == fid)
83 }
84
85 pub fn remove_field(&mut self, fid: FieldId) {
87 self.fields.retain(|f| f.fid != fid);
88 }
89
90 pub fn fields(&self) -> &[LnmpField] {
92 &self.fields
93 }
94
95 pub fn into_fields(self) -> Vec<LnmpField> {
97 self.fields
98 }
99
100 pub fn sorted_fields(&self) -> Vec<LnmpField> {
102 let mut sorted = self.fields.clone();
103 sorted.sort_by_key(|f| f.fid);
104 sorted
105 }
106
107 pub fn from_sorted_fields(fields: Vec<LnmpField>) -> Self {
109 Self { fields }
110 }
111
112 pub fn from_fields(mut fields: Vec<LnmpField>) -> Self {
136 fields.sort_by_key(|f| f.fid);
137 Self::from_sorted_fields(fields)
138 }
139
140 pub fn validate_with_limits(
142 &self,
143 limits: &crate::limits::StructuralLimits,
144 ) -> Result<(), crate::limits::StructuralError> {
145 limits.validate_record(self)
146 }
147
148 pub fn canonical_eq(&self, other: &Self) -> bool {
174 self.sorted_fields() == other.sorted_fields()
175 }
176
177 pub fn canonical_hash<H: std::hash::Hasher>(&self, state: &mut H) {
210 use std::hash::Hash;
211
212 for field in self.sorted_fields() {
214 field.fid.hash(state);
215
216 match &field.value {
218 LnmpValue::Int(i) => {
219 0u8.hash(state); i.hash(state);
221 }
222 LnmpValue::Float(f) => {
223 1u8.hash(state); f.to_bits().hash(state);
226 }
227 LnmpValue::Bool(b) => {
228 2u8.hash(state); b.hash(state);
230 }
231 LnmpValue::String(s) => {
232 3u8.hash(state); s.hash(state);
234 }
235 LnmpValue::StringArray(arr) => {
236 4u8.hash(state); arr.len().hash(state);
238 for s in arr {
239 s.hash(state);
240 }
241 }
242 LnmpValue::IntArray(arr) => {
243 10u8.hash(state); arr.len().hash(state);
245 for &i in arr {
246 i.hash(state);
247 }
248 }
249 LnmpValue::FloatArray(arr) => {
250 11u8.hash(state); arr.len().hash(state);
252 for &f in arr {
253 f.to_bits().hash(state);
254 }
255 }
256 LnmpValue::BoolArray(arr) => {
257 12u8.hash(state); arr.len().hash(state);
259 for &b in arr {
260 b.hash(state);
261 }
262 }
263 LnmpValue::NestedRecord(record) => {
264 5u8.hash(state); record.canonical_hash(state);
267 }
268 LnmpValue::NestedArray(records) => {
269 6u8.hash(state); records.len().hash(state);
271 for rec in records {
272 rec.canonical_hash(state);
273 }
274 }
275 LnmpValue::Embedding(vec) => {
276 7u8.hash(state); vec.dim.hash(state);
279 format!("{:?}", vec.dtype).hash(state); vec.data.hash(state);
281 }
282 LnmpValue::EmbeddingDelta(delta) => {
283 8u8.hash(state); delta.base_id.hash(state);
286 delta.changes.len().hash(state);
287 for change in &delta.changes {
288 change.index.hash(state);
289 change.delta.to_bits().hash(state);
290 }
291 }
292 #[cfg(feature = "quant")]
293 LnmpValue::QuantizedEmbedding(qv) => {
294 9u8.hash(state); format!("{:?}", qv.scheme).hash(state);
297 qv.scale.to_bits().hash(state);
298 qv.zero_point.hash(state);
299 qv.data.hash(state);
300 }
301 }
302 }
303 }
304
305 pub fn validate_field_ordering(&self) -> Result<(), FieldOrderingError> {
329 let fields = self.fields();
330
331 for i in 1..fields.len() {
332 if fields[i].fid < fields[i - 1].fid {
333 return Err(FieldOrderingError {
334 position: i,
335 current_fid: fields[i].fid,
336 previous_fid: fields[i - 1].fid,
337 });
338 }
339 }
340
341 Ok(())
342 }
343
344 pub fn is_canonical_order(&self) -> bool {
360 self.validate_field_ordering().is_ok()
361 }
362
363 pub fn count_ordering_violations(&self) -> usize {
382 let fields = self.fields();
383 let mut count = 0;
384
385 for i in 1..fields.len() {
386 if fields[i].fid < fields[i - 1].fid {
387 count += 1;
388 }
389 }
390
391 count
392 }
393}
394
395#[derive(Debug, Clone, PartialEq)]
397pub struct FieldOrderingError {
398 pub position: usize,
400 pub current_fid: FieldId,
402 pub previous_fid: FieldId,
404}
405
406impl std::fmt::Display for FieldOrderingError {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 write!(
409 f,
410 "Field ordering violation at position {}: F{} appears after F{} (expected ascending FID order)",
411 self.position, self.current_fid, self.previous_fid
412 )
413 }
414}
415
416impl std::error::Error for FieldOrderingError {}
417
418#[cfg(test)]
419#[allow(clippy::approx_constant)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn test_validate_field_ordering_sorted() {
425 let record = LnmpRecord::from_fields(vec![
426 LnmpField {
427 fid: 7,
428 value: LnmpValue::Int(1),
429 },
430 LnmpField {
431 fid: 12,
432 value: LnmpValue::Int(2),
433 },
434 LnmpField {
435 fid: 23,
436 value: LnmpValue::Int(3),
437 },
438 ]);
439
440 assert!(record.validate_field_ordering().is_ok());
441 }
442
443 #[test]
444 fn test_validate_field_ordering_unsorted() {
445 let mut record = LnmpRecord::new();
446 record.add_field(LnmpField {
447 fid: 12,
448 value: LnmpValue::Int(2),
449 });
450 record.add_field(LnmpField {
451 fid: 7,
452 value: LnmpValue::Int(1),
453 });
454
455 let result = record.validate_field_ordering();
456 assert!(result.is_err());
457
458 let err = result.unwrap_err();
459 assert_eq!(err.position, 1);
460 assert_eq!(err.current_fid, 7);
461 assert_eq!(err.previous_fid, 12);
462 }
463
464 #[test]
465 fn test_validate_field_ordering_empty() {
466 let record = LnmpRecord::new();
467 assert!(record.validate_field_ordering().is_ok());
468 }
469
470 #[test]
471 fn test_is_canonical_order() {
472 let sorted = LnmpRecord::from_fields(vec![
473 LnmpField {
474 fid: 1,
475 value: LnmpValue::Int(1),
476 },
477 LnmpField {
478 fid: 2,
479 value: LnmpValue::Int(2),
480 },
481 ]);
482 assert!(sorted.is_canonical_order());
483
484 let mut unsorted = LnmpRecord::new();
485 unsorted.add_field(LnmpField {
486 fid: 2,
487 value: LnmpValue::Int(2),
488 });
489 unsorted.add_field(LnmpField {
490 fid: 1,
491 value: LnmpValue::Int(1),
492 });
493 assert!(!unsorted.is_canonical_order());
494 }
495
496 #[test]
497 fn test_count_ordering_violations_none() {
498 let record = LnmpRecord::from_fields(vec![
499 LnmpField {
500 fid: 1,
501 value: LnmpValue::Int(1),
502 },
503 LnmpField {
504 fid: 2,
505 value: LnmpValue::Int(2),
506 },
507 LnmpField {
508 fid: 3,
509 value: LnmpValue::Int(3),
510 },
511 ]);
512
513 assert_eq!(record.count_ordering_violations(), 0);
514 }
515
516 #[test]
517 fn test_count_ordering_violations_one() {
518 let mut record = LnmpRecord::new();
519 record.add_field(LnmpField {
520 fid: 23,
521 value: LnmpValue::Int(3),
522 });
523 record.add_field(LnmpField {
524 fid: 7,
525 value: LnmpValue::Int(1),
526 });
527 record.add_field(LnmpField {
528 fid: 12,
529 value: LnmpValue::Int(2),
530 });
531
532 assert_eq!(record.count_ordering_violations(), 1);
534 }
535
536 #[test]
537 fn test_count_ordering_violations_multiple() {
538 let mut record = LnmpRecord::new();
539 record.add_field(LnmpField {
540 fid: 23,
541 value: LnmpValue::Int(3),
542 });
543 record.add_field(LnmpField {
544 fid: 7,
545 value: LnmpValue::Int(1),
546 });
547 record.add_field(LnmpField {
548 fid: 3,
549 value: LnmpValue::Int(0),
550 });
551
552 assert_eq!(record.count_ordering_violations(), 2);
554 }
555
556 #[test]
557 fn test_field_ordering_error_display() {
558 let error = FieldOrderingError {
559 position: 1,
560 current_fid: 7,
561 previous_fid: 12,
562 };
563
564 let msg = error.to_string();
565 assert!(msg.contains("position 1"));
566 assert!(msg.contains("F7"));
567 assert!(msg.contains("F12"));
568 assert!(msg.contains("ascending FID order"));
569 }
570
571 #[test]
572 fn test_new_record_is_empty() {
573 let record = LnmpRecord::new();
574 assert_eq!(record.fields().len(), 0);
575 }
576
577 #[test]
578 fn test_add_field() {
579 let mut record = LnmpRecord::new();
580 record.add_field(LnmpField {
581 fid: 12,
582 value: LnmpValue::Int(14532),
583 });
584
585 assert_eq!(record.fields().len(), 1);
586 assert_eq!(record.fields()[0].fid, 12);
587 }
588
589 #[test]
590 fn test_get_field() {
591 let mut record = LnmpRecord::new();
592 record.add_field(LnmpField {
593 fid: 12,
594 value: LnmpValue::Int(14532),
595 });
596 record.add_field(LnmpField {
597 fid: 7,
598 value: LnmpValue::Bool(true),
599 });
600
601 let field = record.get_field(12);
602 assert!(field.is_some());
603 assert_eq!(field.unwrap().value, LnmpValue::Int(14532));
604
605 let missing = record.get_field(99);
606 assert!(missing.is_none());
607 }
608
609 #[test]
610 fn test_get_field_with_duplicates() {
611 let mut record = LnmpRecord::new();
612 record.add_field(LnmpField {
613 fid: 5,
614 value: LnmpValue::String("first".to_string()),
615 });
616 record.add_field(LnmpField {
617 fid: 5,
618 value: LnmpValue::String("second".to_string()),
619 });
620
621 let field = record.get_field(5);
623 assert!(field.is_some());
624 assert_eq!(field.unwrap().value, LnmpValue::String("first".to_string()));
625 }
626
627 #[test]
628 fn test_fields_iteration() {
629 let mut record = LnmpRecord::new();
630 record.add_field(LnmpField {
631 fid: 1,
632 value: LnmpValue::Int(100),
633 });
634 record.add_field(LnmpField {
635 fid: 2,
636 value: LnmpValue::Float(3.14),
637 });
638 record.add_field(LnmpField {
639 fid: 3,
640 value: LnmpValue::Bool(false),
641 });
642
643 let fields = record.fields();
644 assert_eq!(fields.len(), 3);
645 assert_eq!(fields[0].fid, 1);
646 assert_eq!(fields[1].fid, 2);
647 assert_eq!(fields[2].fid, 3);
648 }
649
650 #[test]
651 fn test_into_fields() {
652 let mut record = LnmpRecord::new();
653 record.add_field(LnmpField {
654 fid: 10,
655 value: LnmpValue::String("test".to_string()),
656 });
657
658 let fields = record.into_fields();
659 assert_eq!(fields.len(), 1);
660 assert_eq!(fields[0].fid, 10);
661 }
662
663 #[test]
664 fn test_lnmp_value_equality() {
665 assert_eq!(LnmpValue::Int(42), LnmpValue::Int(42));
666 assert_ne!(LnmpValue::Int(42), LnmpValue::Int(43));
667
668 assert_eq!(LnmpValue::Float(3.14), LnmpValue::Float(3.14));
669
670 assert_eq!(LnmpValue::Bool(true), LnmpValue::Bool(true));
671 assert_ne!(LnmpValue::Bool(true), LnmpValue::Bool(false));
672
673 assert_eq!(
674 LnmpValue::String("hello".to_string()),
675 LnmpValue::String("hello".to_string())
676 );
677
678 assert_eq!(
679 LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()]),
680 LnmpValue::StringArray(vec!["a".to_string(), "b".to_string()])
681 );
682 }
683
684 #[test]
685 fn test_lnmp_value_clone() {
686 let original = LnmpValue::StringArray(vec!["test".to_string()]);
687 let cloned = original.clone();
688 assert_eq!(original, cloned);
689 }
690
691 #[test]
692 fn test_empty_record() {
693 let record = LnmpRecord::new();
694 assert_eq!(record.fields().len(), 0);
695 assert!(record.get_field(1).is_none());
696 }
697
698 #[test]
699 fn test_record_with_all_value_types() {
700 let mut record = LnmpRecord::new();
701
702 record.add_field(LnmpField {
703 fid: 1,
704 value: LnmpValue::Int(-42),
705 });
706 record.add_field(LnmpField {
707 fid: 2,
708 value: LnmpValue::Float(3.14159),
709 });
710 record.add_field(LnmpField {
711 fid: 3,
712 value: LnmpValue::Bool(true),
713 });
714 record.add_field(LnmpField {
715 fid: 4,
716 value: LnmpValue::String("hello world".to_string()),
717 });
718 record.add_field(LnmpField {
719 fid: 5,
720 value: LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()]),
721 });
722
723 assert_eq!(record.fields().len(), 5);
724 assert_eq!(record.get_field(1).unwrap().value, LnmpValue::Int(-42));
725 assert_eq!(
726 record.get_field(2).unwrap().value,
727 LnmpValue::Float(3.14159)
728 );
729 assert_eq!(record.get_field(3).unwrap().value, LnmpValue::Bool(true));
730 assert_eq!(
731 record.get_field(4).unwrap().value,
732 LnmpValue::String("hello world".to_string())
733 );
734 assert_eq!(
735 record.get_field(5).unwrap().value,
736 LnmpValue::StringArray(vec!["admin".to_string(), "dev".to_string()])
737 );
738 }
739
740 #[test]
741 fn test_sorted_fields_basic() {
742 let mut record = LnmpRecord::new();
743 record.add_field(LnmpField {
744 fid: 23,
745 value: LnmpValue::Int(3),
746 });
747 record.add_field(LnmpField {
748 fid: 7,
749 value: LnmpValue::Int(2),
750 });
751 record.add_field(LnmpField {
752 fid: 12,
753 value: LnmpValue::Int(1),
754 });
755
756 let sorted = record.sorted_fields();
757 assert_eq!(sorted.len(), 3);
758 assert_eq!(sorted[0].fid, 7);
759 assert_eq!(sorted[1].fid, 12);
760 assert_eq!(sorted[2].fid, 23);
761 }
762
763 #[test]
764 fn test_sorted_fields_preserves_duplicate_order() {
765 let mut record = LnmpRecord::new();
766 record.add_field(LnmpField {
767 fid: 5,
768 value: LnmpValue::String("first".to_string()),
769 });
770 record.add_field(LnmpField {
771 fid: 10,
772 value: LnmpValue::Int(100),
773 });
774 record.add_field(LnmpField {
775 fid: 5,
776 value: LnmpValue::String("second".to_string()),
777 });
778
779 let sorted = record.sorted_fields();
780 assert_eq!(sorted.len(), 3);
781 assert_eq!(sorted[0].fid, 5);
782 assert_eq!(sorted[0].value, LnmpValue::String("first".to_string()));
783 assert_eq!(sorted[1].fid, 5);
784 assert_eq!(sorted[1].value, LnmpValue::String("second".to_string()));
785 assert_eq!(sorted[2].fid, 10);
786 }
787
788 #[test]
789 fn test_sorted_fields_already_sorted() {
790 let mut record = LnmpRecord::new();
791 record.add_field(LnmpField {
792 fid: 1,
793 value: LnmpValue::Int(1),
794 });
795 record.add_field(LnmpField {
796 fid: 2,
797 value: LnmpValue::Int(2),
798 });
799 record.add_field(LnmpField {
800 fid: 3,
801 value: LnmpValue::Int(3),
802 });
803
804 let sorted = record.sorted_fields();
805 assert_eq!(sorted.len(), 3);
806 assert_eq!(sorted[0].fid, 1);
807 assert_eq!(sorted[1].fid, 2);
808 assert_eq!(sorted[2].fid, 3);
809 }
810
811 #[test]
812 fn test_sorted_fields_empty_record() {
813 let record = LnmpRecord::new();
814 let sorted = record.sorted_fields();
815 assert_eq!(sorted.len(), 0);
816 }
817
818 #[test]
819 fn test_sorted_fields_does_not_modify_original() {
820 let mut record = LnmpRecord::new();
821 record.add_field(LnmpField {
822 fid: 23,
823 value: LnmpValue::Int(3),
824 });
825 record.add_field(LnmpField {
826 fid: 7,
827 value: LnmpValue::Int(2),
828 });
829
830 let _sorted = record.sorted_fields();
831
832 assert_eq!(record.fields()[0].fid, 23);
834 assert_eq!(record.fields()[1].fid, 7);
835 }
836
837 #[test]
838 fn test_canonical_eq_same_order() {
839 let mut rec1 = LnmpRecord::new();
840 rec1.add_field(LnmpField {
841 fid: 7,
842 value: LnmpValue::Int(1),
843 });
844 rec1.add_field(LnmpField {
845 fid: 12,
846 value: LnmpValue::Int(2),
847 });
848
849 let mut rec2 = LnmpRecord::new();
850 rec2.add_field(LnmpField {
851 fid: 7,
852 value: LnmpValue::Int(1),
853 });
854 rec2.add_field(LnmpField {
855 fid: 12,
856 value: LnmpValue::Int(2),
857 });
858
859 assert_eq!(rec1, rec2); assert!(rec1.canonical_eq(&rec2)); }
862
863 #[test]
864 fn test_canonical_eq_different_order() {
865 let mut rec1 = LnmpRecord::new();
866 rec1.add_field(LnmpField {
867 fid: 12,
868 value: LnmpValue::Int(1),
869 });
870 rec1.add_field(LnmpField {
871 fid: 7,
872 value: LnmpValue::Int(2),
873 });
874
875 let mut rec2 = LnmpRecord::new();
876 rec2.add_field(LnmpField {
877 fid: 7,
878 value: LnmpValue::Int(2),
879 });
880 rec2.add_field(LnmpField {
881 fid: 12,
882 value: LnmpValue::Int(1),
883 });
884
885 assert_ne!(rec1, rec2); assert!(rec1.canonical_eq(&rec2)); }
888
889 #[test]
890 fn test_canonical_eq_different_values() {
891 let mut rec1 = LnmpRecord::new();
892 rec1.add_field(LnmpField {
893 fid: 7,
894 value: LnmpValue::Int(1),
895 });
896
897 let mut rec2 = LnmpRecord::new();
898 rec2.add_field(LnmpField {
899 fid: 7,
900 value: LnmpValue::Int(2), });
902
903 assert!(!rec1.canonical_eq(&rec2));
904 }
905
906 #[test]
907 fn test_canonical_eq_with_nested_records() {
908 let mut inner1 = LnmpRecord::new();
912 inner1.add_field(LnmpField {
913 fid: 1,
914 value: LnmpValue::Int(100),
915 });
916 inner1.add_field(LnmpField {
917 fid: 2,
918 value: LnmpValue::Int(200),
919 });
920
921 let mut inner2 = LnmpRecord::new();
922 inner2.add_field(LnmpField {
923 fid: 1,
924 value: LnmpValue::Int(100),
925 });
926 inner2.add_field(LnmpField {
927 fid: 2,
928 value: LnmpValue::Int(200),
929 });
930
931 let mut rec1 = LnmpRecord::new();
933 rec1.add_field(LnmpField {
934 fid: 50,
935 value: LnmpValue::NestedRecord(Box::new(inner1)),
936 });
937 rec1.add_field(LnmpField {
938 fid: 30,
939 value: LnmpValue::Int(999),
940 });
941
942 let mut rec2 = LnmpRecord::new();
943 rec2.add_field(LnmpField {
944 fid: 30,
945 value: LnmpValue::Int(999),
946 });
947 rec2.add_field(LnmpField {
948 fid: 50,
949 value: LnmpValue::NestedRecord(Box::new(inner2)),
950 });
951
952 assert_ne!(rec1, rec2);
954
955 assert!(rec1.canonical_eq(&rec2));
957 }
958
959 #[test]
960 fn test_canonical_hash_same_order() {
961 use std::collections::hash_map::DefaultHasher;
962 use std::hash::Hasher;
963
964 let mut rec1 = LnmpRecord::new();
965 rec1.add_field(LnmpField {
966 fid: 7,
967 value: LnmpValue::Int(1),
968 });
969 rec1.add_field(LnmpField {
970 fid: 12,
971 value: LnmpValue::Int(2),
972 });
973
974 let mut rec2 = LnmpRecord::new();
975 rec2.add_field(LnmpField {
976 fid: 7,
977 value: LnmpValue::Int(1),
978 });
979 rec2.add_field(LnmpField {
980 fid: 12,
981 value: LnmpValue::Int(2),
982 });
983
984 let mut hasher1 = DefaultHasher::new();
985 rec1.canonical_hash(&mut hasher1);
986 let hash1 = hasher1.finish();
987
988 let mut hasher2 = DefaultHasher::new();
989 rec2.canonical_hash(&mut hasher2);
990 let hash2 = hasher2.finish();
991
992 assert_eq!(hash1, hash2);
993 }
994
995 #[test]
996 fn test_canonical_hash_different_order() {
997 use std::collections::hash_map::DefaultHasher;
998 use std::hash::Hasher;
999
1000 let mut rec1 = LnmpRecord::new();
1001 rec1.add_field(LnmpField {
1002 fid: 12,
1003 value: LnmpValue::Int(1),
1004 });
1005 rec1.add_field(LnmpField {
1006 fid: 7,
1007 value: LnmpValue::Int(2),
1008 });
1009
1010 let mut rec2 = LnmpRecord::new();
1011 rec2.add_field(LnmpField {
1012 fid: 7,
1013 value: LnmpValue::Int(2),
1014 });
1015 rec2.add_field(LnmpField {
1016 fid: 12,
1017 value: LnmpValue::Int(1),
1018 });
1019
1020 let mut hasher1 = DefaultHasher::new();
1021 rec1.canonical_hash(&mut hasher1);
1022 let hash1 = hasher1.finish();
1023
1024 let mut hasher2 = DefaultHasher::new();
1025 rec2.canonical_hash(&mut hasher2);
1026 let hash2 = hasher2.finish();
1027
1028 assert_eq!(hash1, hash2);
1030 }
1031
1032 #[test]
1033 fn test_canonical_hash_different_values() {
1034 use std::collections::hash_map::DefaultHasher;
1035 use std::hash::Hasher;
1036
1037 let mut rec1 = LnmpRecord::new();
1038 rec1.add_field(LnmpField {
1039 fid: 7,
1040 value: LnmpValue::Int(1),
1041 });
1042
1043 let mut rec2 = LnmpRecord::new();
1044 rec2.add_field(LnmpField {
1045 fid: 7,
1046 value: LnmpValue::Int(2),
1047 });
1048
1049 let mut hasher1 = DefaultHasher::new();
1050 rec1.canonical_hash(&mut hasher1);
1051 let hash1 = hasher1.finish();
1052
1053 let mut hasher2 = DefaultHasher::new();
1054 rec2.canonical_hash(&mut hasher2);
1055 let hash2 = hasher2.finish();
1056
1057 assert_ne!(hash1, hash2);
1059 }
1060}