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