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