1use std::ops::Deref;
5
6use reifydb_value::util::hash::{Hash64, xxh3_64};
7use serde::{Deserialize, Serialize};
8
9use crate::{constraint::type_constraint_to_ffi, encoded::shape::RowShapeField};
10
11#[repr(transparent)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13pub struct RowShapeFingerprint(pub Hash64);
14
15impl Deref for RowShapeFingerprint {
16 type Target = u64;
17
18 fn deref(&self) -> &Self::Target {
19 &self.0.0
20 }
21}
22
23impl RowShapeFingerprint {
24 #[inline]
25 pub const fn new(value: u64) -> Self {
26 Self(Hash64(value))
27 }
28
29 #[inline]
30 pub const fn zero() -> Self {
31 Self(Hash64(0))
32 }
33
34 #[inline]
35 pub const fn as_u64(&self) -> u64 {
36 self.0.0
37 }
38
39 #[inline]
40 pub const fn to_le_bytes(&self) -> [u8; 8] {
41 self.0.0.to_le_bytes()
42 }
43
44 #[inline]
45 pub const fn from_le_bytes(bytes: [u8; 8]) -> Self {
46 Self(Hash64(u64::from_le_bytes(bytes)))
47 }
48}
49
50impl From<Hash64> for RowShapeFingerprint {
51 fn from(hash: Hash64) -> Self {
52 Self(hash)
53 }
54}
55
56impl From<RowShapeFingerprint> for Hash64 {
57 fn from(fp: RowShapeFingerprint) -> Self {
58 fp.0
59 }
60}
61
62impl From<u64> for RowShapeFingerprint {
63 fn from(value: u64) -> Self {
64 Self(Hash64(value))
65 }
66}
67
68pub fn compute_fingerprint(fields: &[RowShapeField]) -> RowShapeFingerprint {
69 let estimated_size = 2 + fields.len() * 42;
70 let mut buffer = Vec::with_capacity(estimated_size);
71
72 let field_count = fields.len() as u16;
73 buffer.extend_from_slice(&field_count.to_le_bytes());
74
75 for field in fields {
76 let name_bytes = field.name.as_bytes();
77 let name_len = name_bytes.len() as u16;
78 buffer.extend_from_slice(&name_len.to_le_bytes());
79 buffer.extend_from_slice(name_bytes);
80
81 let ffi = type_constraint_to_ffi(&field.constraint)
82 .expect("row shape field constraint exceeds tag capacity");
83 buffer.push(ffi.base_type);
84 buffer.push(ffi.constraint_type);
85 buffer.extend_from_slice(&ffi.constraint_param1.to_le_bytes());
86 buffer.extend_from_slice(&ffi.constraint_param2.to_le_bytes());
87 }
88
89 RowShapeFingerprint(xxh3_64(&buffer))
90}
91
92#[cfg(test)]
93mod tests {
94 use reifydb_value::value::{
95 constraint::{Constraint, TypeConstraint, bytes::MaxBytes, precision::Precision, scale::Scale},
96 value_type::ValueType,
97 };
98
99 use super::*;
100
101 fn make_field(name: &str, field_type: ValueType) -> RowShapeField {
102 RowShapeField {
103 name: name.to_string(),
104 constraint: TypeConstraint::unconstrained(field_type),
105 offset: 0,
106 size: 0,
107 align: 0,
108 }
109 }
110
111 fn make_constrained_field(name: &str, constraint: TypeConstraint) -> RowShapeField {
112 RowShapeField {
113 name: name.to_string(),
114 constraint,
115 offset: 0,
116 size: 0,
117 align: 0,
118 }
119 }
120
121 #[test]
122 fn test_fingerprint_deterministic() {
123 let fields1 = vec![make_field("a", ValueType::Int4), make_field("b", ValueType::Utf8)];
124
125 let fields2 = vec![make_field("a", ValueType::Int4), make_field("b", ValueType::Utf8)];
126
127 assert_eq!(compute_fingerprint(&fields1), compute_fingerprint(&fields2));
128 }
129
130 #[test]
131 fn test_fingerprint_different_names() {
132 let fields1 = vec![make_field("a", ValueType::Int4)];
133 let fields2 = vec![make_field("b", ValueType::Int4)];
134
135 assert_ne!(compute_fingerprint(&fields1), compute_fingerprint(&fields2));
136 }
137
138 #[test]
139 fn test_fingerprint_different_types() {
140 let fields1 = vec![make_field("a", ValueType::Int4)];
141 let fields2 = vec![make_field("a", ValueType::Int8)];
142
143 assert_ne!(compute_fingerprint(&fields1), compute_fingerprint(&fields2));
144 }
145
146 #[test]
147 fn test_fingerprint_different_order() {
148 let fields1 = vec![make_field("a", ValueType::Int4), make_field("b", ValueType::Utf8)];
149
150 let fields2 = vec![make_field("b", ValueType::Utf8), make_field("a", ValueType::Int4)];
151
152 assert_ne!(compute_fingerprint(&fields1), compute_fingerprint(&fields2));
153 }
154
155 #[test]
156 fn test_fingerprint_empty_shape() {
157 let fields: Vec<RowShapeField> = vec![];
158 let fp = compute_fingerprint(&fields);
160 assert_ne!(*fp, 0);
161 }
162
163 #[test]
164 fn test_fingerprint_utf8_constrained_vs_unconstrained() {
165 let unconstrained = vec![make_field("text", ValueType::Utf8)];
166 let constrained = vec![make_constrained_field(
167 "text",
168 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(255))),
169 )];
170
171 assert_ne!(
172 compute_fingerprint(&unconstrained),
173 compute_fingerprint(&constrained),
174 "Utf8 unconstrained should differ from Utf8(255)"
175 );
176 }
177
178 #[test]
179 fn test_fingerprint_utf8_same_constraint_deterministic() {
180 let fields1 = vec![make_constrained_field(
181 "text",
182 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(100))),
183 )];
184 let fields2 = vec![make_constrained_field(
185 "text",
186 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(100))),
187 )];
188
189 assert_eq!(
190 compute_fingerprint(&fields1),
191 compute_fingerprint(&fields2),
192 "Utf8(100) should produce same fingerprint"
193 );
194 }
195
196 #[test]
197 fn test_fingerprint_utf8_different_max_bytes() {
198 let small = vec![make_constrained_field(
199 "text",
200 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(50))),
201 )];
202 let large = vec![make_constrained_field(
203 "text",
204 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(500))),
205 )];
206
207 assert_ne!(
208 compute_fingerprint(&small),
209 compute_fingerprint(&large),
210 "Utf8(50) should differ from Utf8(500)"
211 );
212 }
213
214 #[test]
215 fn test_fingerprint_int_constrained_vs_unconstrained() {
216 let unconstrained = vec![make_field("num", ValueType::Int)];
217 let constrained = vec![make_constrained_field(
218 "num",
219 TypeConstraint::with_constraint(ValueType::Int, Constraint::MaxBytes(MaxBytes::new(8))),
220 )];
221
222 assert_ne!(
223 compute_fingerprint(&unconstrained),
224 compute_fingerprint(&constrained),
225 "Int unconstrained should differ from Int(8)"
226 );
227 }
228
229 #[test]
230 fn test_fingerprint_int_same_constraint_deterministic() {
231 let fields1 = vec![make_constrained_field(
232 "num",
233 TypeConstraint::with_constraint(ValueType::Int, Constraint::MaxBytes(MaxBytes::new(16))),
234 )];
235 let fields2 = vec![make_constrained_field(
236 "num",
237 TypeConstraint::with_constraint(ValueType::Int, Constraint::MaxBytes(MaxBytes::new(16))),
238 )];
239
240 assert_eq!(
241 compute_fingerprint(&fields1),
242 compute_fingerprint(&fields2),
243 "Int(16) should produce same fingerprint"
244 );
245 }
246
247 #[test]
248 fn test_fingerprint_int_different_max_bytes() {
249 let small = vec![make_constrained_field(
250 "num",
251 TypeConstraint::with_constraint(ValueType::Int, Constraint::MaxBytes(MaxBytes::new(4))),
252 )];
253 let large = vec![make_constrained_field(
254 "num",
255 TypeConstraint::with_constraint(ValueType::Int, Constraint::MaxBytes(MaxBytes::new(32))),
256 )];
257
258 assert_ne!(
259 compute_fingerprint(&small),
260 compute_fingerprint(&large),
261 "Int(4) should differ from Int(32)"
262 );
263 }
264
265 #[test]
266 fn test_fingerprint_uint_constrained_vs_unconstrained() {
267 let unconstrained = vec![make_field("num", ValueType::Uint)];
268 let constrained = vec![make_constrained_field(
269 "num",
270 TypeConstraint::with_constraint(ValueType::Uint, Constraint::MaxBytes(MaxBytes::new(8))),
271 )];
272
273 assert_ne!(
274 compute_fingerprint(&unconstrained),
275 compute_fingerprint(&constrained),
276 "Uint unconstrained should differ from Uint(8)"
277 );
278 }
279
280 #[test]
281 fn test_fingerprint_uint_same_constraint_deterministic() {
282 let fields1 = vec![make_constrained_field(
283 "num",
284 TypeConstraint::with_constraint(ValueType::Uint, Constraint::MaxBytes(MaxBytes::new(64))),
285 )];
286 let fields2 = vec![make_constrained_field(
287 "num",
288 TypeConstraint::with_constraint(ValueType::Uint, Constraint::MaxBytes(MaxBytes::new(64))),
289 )];
290
291 assert_eq!(
292 compute_fingerprint(&fields1),
293 compute_fingerprint(&fields2),
294 "Uint(64) should produce same fingerprint"
295 );
296 }
297
298 #[test]
299 fn test_fingerprint_uint_different_max_bytes() {
300 let small = vec![make_constrained_field(
301 "num",
302 TypeConstraint::with_constraint(ValueType::Uint, Constraint::MaxBytes(MaxBytes::new(2))),
303 )];
304 let large = vec![make_constrained_field(
305 "num",
306 TypeConstraint::with_constraint(ValueType::Uint, Constraint::MaxBytes(MaxBytes::new(128))),
307 )];
308
309 assert_ne!(
310 compute_fingerprint(&small),
311 compute_fingerprint(&large),
312 "Uint(2) should differ from Uint(128)"
313 );
314 }
315
316 #[test]
317 fn test_fingerprint_blob_constrained_vs_unconstrained() {
318 let unconstrained = vec![make_field("data", ValueType::Blob)];
319 let constrained = vec![make_constrained_field(
320 "data",
321 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(1024))),
322 )];
323
324 assert_ne!(
325 compute_fingerprint(&unconstrained),
326 compute_fingerprint(&constrained),
327 "Blob unconstrained should differ from Blob(1024)"
328 );
329 }
330
331 #[test]
332 fn test_fingerprint_blob_same_constraint_deterministic() {
333 let fields1 = vec![make_constrained_field(
334 "data",
335 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(4096))),
336 )];
337 let fields2 = vec![make_constrained_field(
338 "data",
339 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(4096))),
340 )];
341
342 assert_eq!(
343 compute_fingerprint(&fields1),
344 compute_fingerprint(&fields2),
345 "Blob(4096) should produce same fingerprint"
346 );
347 }
348
349 #[test]
350 fn test_fingerprint_blob_different_max_bytes() {
351 let small = vec![make_constrained_field(
352 "data",
353 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(256))),
354 )];
355 let large = vec![make_constrained_field(
356 "data",
357 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(65536))),
358 )];
359
360 assert_ne!(
361 compute_fingerprint(&small),
362 compute_fingerprint(&large),
363 "Blob(256) should differ from Blob(65536)"
364 );
365 }
366
367 #[test]
368 fn test_fingerprint_decimal_constrained_vs_unconstrained() {
369 let unconstrained = vec![make_field("amount", ValueType::Decimal)];
370 let constrained = vec![make_constrained_field(
371 "amount",
372 TypeConstraint::with_constraint(
373 ValueType::Decimal,
374 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
375 ),
376 )];
377
378 assert_ne!(
379 compute_fingerprint(&unconstrained),
380 compute_fingerprint(&constrained),
381 "Decimal unconstrained should differ from Decimal(10,2)"
382 );
383 }
384
385 #[test]
386 fn test_fingerprint_decimal_same_constraint_deterministic() {
387 let fields1 = vec![make_constrained_field(
388 "amount",
389 TypeConstraint::with_constraint(
390 ValueType::Decimal,
391 Constraint::PrecisionScale(Precision::new(18), Scale::new(6)),
392 ),
393 )];
394 let fields2 = vec![make_constrained_field(
395 "amount",
396 TypeConstraint::with_constraint(
397 ValueType::Decimal,
398 Constraint::PrecisionScale(Precision::new(18), Scale::new(6)),
399 ),
400 )];
401
402 assert_eq!(
403 compute_fingerprint(&fields1),
404 compute_fingerprint(&fields2),
405 "Decimal(18,6) should produce same fingerprint"
406 );
407 }
408
409 #[test]
410 fn test_fingerprint_decimal_different_precision() {
411 let low_precision = vec![make_constrained_field(
412 "amount",
413 TypeConstraint::with_constraint(
414 ValueType::Decimal,
415 Constraint::PrecisionScale(Precision::new(5), Scale::new(2)),
416 ),
417 )];
418 let high_precision = vec![make_constrained_field(
419 "amount",
420 TypeConstraint::with_constraint(
421 ValueType::Decimal,
422 Constraint::PrecisionScale(Precision::new(38), Scale::new(2)),
423 ),
424 )];
425
426 assert_ne!(
427 compute_fingerprint(&low_precision),
428 compute_fingerprint(&high_precision),
429 "Decimal(5,2) should differ from Decimal(38,2)"
430 );
431 }
432
433 #[test]
434 fn test_fingerprint_decimal_different_scale() {
435 let low_scale = vec![make_constrained_field(
436 "amount",
437 TypeConstraint::with_constraint(
438 ValueType::Decimal,
439 Constraint::PrecisionScale(Precision::new(10), Scale::new(0)),
440 ),
441 )];
442 let high_scale = vec![make_constrained_field(
443 "amount",
444 TypeConstraint::with_constraint(
445 ValueType::Decimal,
446 Constraint::PrecisionScale(Precision::new(10), Scale::new(8)),
447 ),
448 )];
449
450 assert_ne!(
451 compute_fingerprint(&low_scale),
452 compute_fingerprint(&high_scale),
453 "Decimal(10,0) should differ from Decimal(10,8)"
454 );
455 }
456
457 #[test]
458 fn test_fingerprint_decimal_different_precision_and_scale() {
459 let fields1 = vec![make_constrained_field(
460 "amount",
461 TypeConstraint::with_constraint(
462 ValueType::Decimal,
463 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
464 ),
465 )];
466 let fields2 = vec![make_constrained_field(
467 "amount",
468 TypeConstraint::with_constraint(
469 ValueType::Decimal,
470 Constraint::PrecisionScale(Precision::new(15), Scale::new(4)),
471 ),
472 )];
473
474 assert_ne!(
475 compute_fingerprint(&fields1),
476 compute_fingerprint(&fields2),
477 "Decimal(10,2) should differ from Decimal(15,4)"
478 );
479 }
480
481 #[test]
482 fn test_fingerprint_different_types_same_max_bytes() {
483 let utf8 = vec![make_constrained_field(
485 "field",
486 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(100))),
487 )];
488 let blob = vec![make_constrained_field(
489 "field",
490 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(100))),
491 )];
492 let int = vec![make_constrained_field(
493 "field",
494 TypeConstraint::with_constraint(ValueType::Int, Constraint::MaxBytes(MaxBytes::new(100))),
495 )];
496 let uint = vec![make_constrained_field(
497 "field",
498 TypeConstraint::with_constraint(ValueType::Uint, Constraint::MaxBytes(MaxBytes::new(100))),
499 )];
500
501 let fp_utf8 = compute_fingerprint(&utf8);
502 let fp_blob = compute_fingerprint(&blob);
503 let fp_int = compute_fingerprint(&int);
504 let fp_uint = compute_fingerprint(&uint);
505
506 assert_ne!(fp_utf8, fp_blob, "Utf8(100) should differ from Blob(100)");
507 assert_ne!(fp_utf8, fp_int, "Utf8(100) should differ from Int(100)");
508 assert_ne!(fp_utf8, fp_uint, "Utf8(100) should differ from Uint(100)");
509 assert_ne!(fp_blob, fp_int, "Blob(100) should differ from Int(100)");
510 assert_ne!(fp_blob, fp_uint, "Blob(100) should differ from Uint(100)");
511 assert_ne!(fp_int, fp_uint, "Int(100) should differ from Uint(100)");
512 }
513
514 #[test]
515 fn test_fingerprint_multiple_constrained_fields() {
516 let fields1 = vec![
517 make_constrained_field(
518 "name",
519 TypeConstraint::with_constraint(
520 ValueType::Utf8,
521 Constraint::MaxBytes(MaxBytes::new(255)),
522 ),
523 ),
524 make_constrained_field(
525 "price",
526 TypeConstraint::with_constraint(
527 ValueType::Decimal,
528 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
529 ),
530 ),
531 make_constrained_field(
532 "data",
533 TypeConstraint::with_constraint(
534 ValueType::Blob,
535 Constraint::MaxBytes(MaxBytes::new(1024)),
536 ),
537 ),
538 ];
539
540 let fields2 = vec![
541 make_constrained_field(
542 "name",
543 TypeConstraint::with_constraint(
544 ValueType::Utf8,
545 Constraint::MaxBytes(MaxBytes::new(255)),
546 ),
547 ),
548 make_constrained_field(
549 "price",
550 TypeConstraint::with_constraint(
551 ValueType::Decimal,
552 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
553 ),
554 ),
555 make_constrained_field(
556 "data",
557 TypeConstraint::with_constraint(
558 ValueType::Blob,
559 Constraint::MaxBytes(MaxBytes::new(1024)),
560 ),
561 ),
562 ];
563
564 assert_eq!(
565 compute_fingerprint(&fields1),
566 compute_fingerprint(&fields2),
567 "Identical multi-field constrained shapes should produce same fingerprint"
568 );
569 }
570
571 #[test]
572 fn test_fingerprint_multiple_fields_one_constraint_differs() {
573 let fields1 = vec![
574 make_constrained_field(
575 "name",
576 TypeConstraint::with_constraint(
577 ValueType::Utf8,
578 Constraint::MaxBytes(MaxBytes::new(255)),
579 ),
580 ),
581 make_constrained_field(
582 "price",
583 TypeConstraint::with_constraint(
584 ValueType::Decimal,
585 Constraint::PrecisionScale(Precision::new(10), Scale::new(2)),
586 ),
587 ),
588 ];
589
590 let fields2 = vec![
591 make_constrained_field(
592 "name",
593 TypeConstraint::with_constraint(
594 ValueType::Utf8,
595 Constraint::MaxBytes(MaxBytes::new(255)),
596 ),
597 ),
598 make_constrained_field(
599 "price",
600 TypeConstraint::with_constraint(
601 ValueType::Decimal,
602 Constraint::PrecisionScale(Precision::new(10), Scale::new(4)), ),
604 ),
605 ];
606
607 assert_ne!(
608 compute_fingerprint(&fields1),
609 compute_fingerprint(&fields2),
610 "Shapes differing only in one constraint should have different fingerprints"
611 );
612 }
613
614 #[test]
615 fn test_fingerprint_mixed_constrained_and_unconstrained() {
616 let fields1 = vec![
617 make_field("id", ValueType::Int8),
618 make_constrained_field(
619 "name",
620 TypeConstraint::with_constraint(
621 ValueType::Utf8,
622 Constraint::MaxBytes(MaxBytes::new(100)),
623 ),
624 ),
625 make_field("active", ValueType::Boolean),
626 ];
627
628 let fields2 = vec![
629 make_field("id", ValueType::Int8),
630 make_field("name", ValueType::Utf8), make_field("active", ValueType::Boolean),
632 ];
633
634 assert_ne!(
635 compute_fingerprint(&fields1),
636 compute_fingerprint(&fields2),
637 "Mixed constrained/unconstrained should differ from all unconstrained"
638 );
639 }
640
641 #[test]
642 fn test_fingerprint_max_bytes_edge_values() {
643 let min_value = vec![make_constrained_field(
644 "data",
645 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(1))),
646 )];
647 let max_value = vec![make_constrained_field(
648 "data",
649 TypeConstraint::with_constraint(ValueType::Blob, Constraint::MaxBytes(MaxBytes::new(u32::MAX))),
650 )];
651
652 assert_ne!(
653 compute_fingerprint(&min_value),
654 compute_fingerprint(&max_value),
655 "Blob(1) should differ from Blob(MAX)"
656 );
657 }
658
659 #[test]
660 fn test_fingerprint_decimal_edge_precision_scale() {
661 let min_precision = vec![make_constrained_field(
662 "amount",
663 TypeConstraint::with_constraint(
664 ValueType::Decimal,
665 Constraint::PrecisionScale(Precision::new(1), Scale::new(0)),
666 ),
667 )];
668 let max_precision = vec![make_constrained_field(
669 "amount",
670 TypeConstraint::with_constraint(
671 ValueType::Decimal,
672 Constraint::PrecisionScale(Precision::new(255), Scale::new(255)),
673 ),
674 )];
675
676 assert_ne!(
677 compute_fingerprint(&min_precision),
678 compute_fingerprint(&max_precision),
679 "Decimal(1,0) should differ from Decimal(255,255)"
680 );
681 }
682
683 #[test]
684 fn test_fingerprint_adjacent_max_bytes_values() {
685 let value_99 = vec![make_constrained_field(
687 "text",
688 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(99))),
689 )];
690 let value_100 = vec![make_constrained_field(
691 "text",
692 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(100))),
693 )];
694 let value_101 = vec![make_constrained_field(
695 "text",
696 TypeConstraint::with_constraint(ValueType::Utf8, Constraint::MaxBytes(MaxBytes::new(101))),
697 )];
698
699 let fp_99 = compute_fingerprint(&value_99);
700 let fp_100 = compute_fingerprint(&value_100);
701 let fp_101 = compute_fingerprint(&value_101);
702
703 assert_ne!(fp_99, fp_100, "Utf8(99) should differ from Utf8(100)");
704 assert_ne!(fp_100, fp_101, "Utf8(100) should differ from Utf8(101)");
705 assert_ne!(fp_99, fp_101, "Utf8(99) should differ from Utf8(101)");
706 }
707}