1use core::fmt;
80
81use vitaminc_protected::{Choice, OpaqueDebug, TimingSafeEq};
82use zeroize::Zeroize;
83
84#[derive(Clone, Copy, OpaqueDebug, TimingSafeEq)]
97pub enum Physical<'a> {
98 Null,
99 Bool(bool),
100 Int(Int),
101 Float(Float),
102 Text(&'a str),
103 Bytes(&'a [u8]),
104}
105
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub enum Kind {
112 Null,
113 Bool,
114 Int,
115 Float,
116 Text,
117 Bytes,
118}
119
120impl Kind {
121 pub const fn to_wire(self) -> u8 {
123 match self {
124 Kind::Null => 0,
125 Kind::Bool => 1,
126 Kind::Int => 2,
127 Kind::Float => 3,
128 Kind::Text => 4,
129 Kind::Bytes => 5,
130 }
131 }
132}
133
134impl Physical<'_> {
135 pub fn kind(&self) -> Kind {
137 match self {
138 Physical::Null => Kind::Null,
139 Physical::Bool(_) => Kind::Bool,
140 Physical::Int(_) => Kind::Int,
141 Physical::Float(_) => Kind::Float,
142 Physical::Text(_) => Kind::Text,
143 Physical::Bytes(_) => Kind::Bytes,
144 }
145 }
146}
147
148#[derive(Clone, Copy, OpaqueDebug, Eq, Zeroize)]
158pub enum Int {
159 I8(i8),
160 I16(i16),
161 I32(i32),
162 I64(i64),
163 I128(i128),
164 U8(u8),
165 U16(u16),
166 U32(u32),
167 U64(u64),
168 U128(u128),
169}
170
171impl Int {
172 pub fn to_i128(self) -> Option<i128> {
174 match self {
175 Int::I8(v) => Some(v.into()),
176 Int::I16(v) => Some(v.into()),
177 Int::I32(v) => Some(v.into()),
178 Int::I64(v) => Some(v.into()),
179 Int::I128(v) => Some(v),
180 Int::U8(v) => Some(v.into()),
181 Int::U16(v) => Some(v.into()),
182 Int::U32(v) => Some(v.into()),
183 Int::U64(v) => Some(v.into()),
184 Int::U128(v) => i128::try_from(v).ok(),
185 }
186 }
187
188 pub fn to_u128(self) -> Option<u128> {
190 match self {
191 Int::U128(v) => Some(v),
192 other => other.to_i128().and_then(|v| u128::try_from(v).ok()),
193 }
194 }
195
196 pub fn sign_and_word(self) -> (bool, u128) {
199 let (sign, word) = self.sign_bit_and_word();
200 (sign == 1, word)
201 }
202
203 fn sign_bit_and_word(self) -> (u8, u128) {
212 let (word, signed) = match self {
213 Int::I8(v) => (i128::from(v) as u128, 1),
214 Int::I16(v) => (i128::from(v) as u128, 1),
215 Int::I32(v) => (i128::from(v) as u128, 1),
216 Int::I64(v) => (i128::from(v) as u128, 1),
217 Int::I128(v) => (v as u128, 1),
218 Int::U8(v) => (u128::from(v), 0),
219 Int::U16(v) => (u128::from(v), 0),
220 Int::U32(v) => (u128::from(v), 0),
221 Int::U64(v) => (u128::from(v), 0),
222 Int::U128(v) => (v, 0),
223 };
224 (((word >> 127) as u8) & signed, word)
225 }
226}
227
228macro_rules! int_from {
229 ($($ty:ty => $variant:ident),* $(,)?) => {
230 $(
231 impl From<$ty> for Int {
232 fn from(v: $ty) -> Self {
233 Int::$variant(v)
234 }
235 }
236 )*
237 };
238}
239
240int_from! {
241 i8 => I8, i16 => I16, i32 => I32, i64 => I64, i128 => I128,
242 u8 => U8, u16 => U16, u32 => U32, u64 => U64, u128 => U128,
243}
244
245#[derive(Clone, Copy, OpaqueDebug, TimingSafeEq, Zeroize)]
248pub enum Float {
249 F32(f32),
250 F64(f64),
251}
252
253#[derive(Clone, Copy, Debug, PartialEq, Eq)]
262pub enum Width {
263 I8,
264 I16,
265 I32,
266 I64,
267 I128,
268 U8,
269 U16,
270 U32,
271 U64,
272 U128,
273 F32,
274 F64,
275 Bytes16,
277}
278
279impl Width {
280 pub const fn kind(self) -> Kind {
282 match self {
283 Width::F32 | Width::F64 => Kind::Float,
284 Width::Bytes16 => Kind::Bytes,
285 _ => Kind::Int,
286 }
287 }
288
289 pub const fn zero(self) -> Physical<'static> {
292 match self {
293 Width::I8 => Physical::Int(Int::I8(0)),
294 Width::I16 => Physical::Int(Int::I16(0)),
295 Width::I32 => Physical::Int(Int::I32(0)),
296 Width::I64 => Physical::Int(Int::I64(0)),
297 Width::I128 => Physical::Int(Int::I128(0)),
298 Width::U8 => Physical::Int(Int::U8(0)),
299 Width::U16 => Physical::Int(Int::U16(0)),
300 Width::U32 => Physical::Int(Int::U32(0)),
301 Width::U64 => Physical::Int(Int::U64(0)),
302 Width::U128 => Physical::Int(Int::U128(0)),
303 Width::F32 => Physical::Float(Float::F32(0.0)),
304 Width::F64 => Physical::Float(Float::F64(0.0)),
305 Width::Bytes16 => Physical::Bytes(&[0; 16]),
306 }
307 }
308
309 pub fn of(v: &Physical<'_>) -> Option<Self> {
313 Some(match v {
314 Physical::Int(Int::I8(_)) => Width::I8,
315 Physical::Int(Int::I16(_)) => Width::I16,
316 Physical::Int(Int::I32(_)) => Width::I32,
317 Physical::Int(Int::I64(_)) => Width::I64,
318 Physical::Int(Int::I128(_)) => Width::I128,
319 Physical::Int(Int::U8(_)) => Width::U8,
320 Physical::Int(Int::U16(_)) => Width::U16,
321 Physical::Int(Int::U32(_)) => Width::U32,
322 Physical::Int(Int::U64(_)) => Width::U64,
323 Physical::Int(Int::U128(_)) => Width::U128,
324 Physical::Float(Float::F32(_)) => Width::F32,
325 Physical::Float(Float::F64(_)) => Width::F64,
326 Physical::Bytes(b) if b.len() == 16 => Width::Bytes16,
327 Physical::Null | Physical::Bool(_) | Physical::Text(_) | Physical::Bytes(_) => {
328 return None
329 }
330 })
331 }
332}
333
334#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub struct FixedType {
354 ty: Option<Logical>,
355 width: Width,
356}
357
358impl FixedType {
359 pub const fn plain(width: Width) -> Option<Self> {
362 Self::new(None, width)
363 }
364
365 pub const fn new(ty: Option<Logical>, width: Width) -> Option<Self> {
371 let float = matches!(width, Width::F32 | Width::F64);
372 let bytes = matches!(width, Width::Bytes16);
373 let (ty, width) = match ty {
374 Some(Logical::Uuid) if bytes => (Some(Logical::Uuid), width),
375 Some(Logical::Uuid) => return None,
376 _ if bytes => return None,
377 None => (None, width),
378 Some(Logical::Unknown(u)) => (Some(Logical::Unknown(u)), width),
379 Some(Logical::Undefined) => return None,
380 Some(_) if float => return None,
381 Some(Logical::Date) => (Some(Logical::Date), Width::I32),
382 Some(Logical::Timestamp(TimeUnit::Nanos)) => {
383 (Some(Logical::Timestamp(TimeUnit::Nanos)), Width::I128)
384 }
385 Some(Logical::Decimal { .. }) => (Some(Logical::Decimal { scale: 0 }), Width::I128),
386 Some(Logical::Timestamp(unit)) => (Some(Logical::Timestamp(unit)), width),
387 };
388 Some(Self { ty, width })
389 }
390
391 pub fn of(ty: Option<Logical>, v: &Physical<'_>) -> Option<Self> {
396 Self::new(ty, Width::of(v)?)
397 }
398
399 pub const fn logical(self) -> Option<Logical> {
401 self.ty
402 }
403
404 pub const fn width(self) -> Width {
406 self.width
407 }
408}
409
410impl TimingSafeEq for Int {
422 fn ts_eq(&self, other: &Self) -> Choice {
423 let (sign_a, word_a) = self.sign_bit_and_word();
424 let (sign_b, word_b) = other.sign_bit_and_word();
425 let same_width = core::mem::discriminant(self) == core::mem::discriminant(other);
426 Choice::from(u8::from(same_width)) & sign_a.ts_eq(&sign_b) & word_a.ts_eq(&word_b)
427 }
428}
429
430impl PartialEq for Int {
431 fn eq(&self, other: &Self) -> bool {
432 self.ts_eq(other).into()
433 }
434}
435
436#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442#[non_exhaustive]
443pub enum Logical {
444 Undefined,
446 Date,
448 Timestamp(TimeUnit),
457 Decimal { scale: u8 },
459 Uuid,
461 Unknown(UnknownLogical),
464}
465
466#[derive(Clone, Copy, Debug, PartialEq, Eq)]
468pub enum TimeUnit {
469 Seconds,
470 Millis,
471 Micros,
472 Nanos,
473}
474
475impl TimeUnit {
476 pub fn nanos(self) -> i128 {
478 match self {
479 TimeUnit::Seconds => 1_000_000_000,
480 TimeUnit::Millis => 1_000_000,
481 TimeUnit::Micros => 1_000,
482 TimeUnit::Nanos => 1,
483 }
484 }
485
486 fn from_wire(param: u8) -> Option<Self> {
487 [
488 TimeUnit::Seconds,
489 TimeUnit::Millis,
490 TimeUnit::Micros,
491 TimeUnit::Nanos,
492 ]
493 .get(usize::from(param))
494 .copied()
495 }
496}
497
498#[derive(Clone, Copy, Debug, PartialEq, Eq)]
500pub struct UnknownLogical {
501 id: u8,
502 param: u8,
503}
504
505impl UnknownLogical {
506 pub fn id(self) -> u8 {
507 self.id
508 }
509
510 pub fn param(self) -> u8 {
511 self.param
512 }
513}
514
515pub const MAX_DECIMAL_SCALE: u8 = 28;
517
518const DECIMAL_MANTISSA_LIMIT: i128 = 1 << 96;
520
521pub const MIN_DATE_DAYS: i32 = -96_465_292;
524pub const MAX_DATE_DAYS: i32 = 95_026_236;
525
526pub const MIN_TIMESTAMP_NANOS: i128 = MIN_DATE_DAYS as i128 * NANOS_PER_DAY;
529pub const MAX_TIMESTAMP_NANOS: i128 = (MAX_DATE_DAYS as i128 + 1) * NANOS_PER_DAY - 1;
530
531const NANOS_PER_DAY: i128 = 86_400 * 1_000_000_000;
532
533pub mod id {
537 pub const NONE: u8 = 0;
539 pub const UNDEFINED: u8 = 1;
540 pub const DATE: u8 = 2;
541 pub const TIMESTAMP: u8 = 3;
542 pub const DECIMAL: u8 = 4;
543 pub const UUID: u8 = 5;
544 pub const LAST_KNOWN: u8 = UUID;
547}
548
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
560#[non_exhaustive]
561pub enum KnownLogical {
562 Undefined,
564 Date,
566 Timestamp,
568 Decimal,
570 Uuid,
572}
573
574impl KnownLogical {
575 pub const fn id(self) -> u8 {
577 match self {
578 KnownLogical::Undefined => id::UNDEFINED,
579 KnownLogical::Date => id::DATE,
580 KnownLogical::Timestamp => id::TIMESTAMP,
581 KnownLogical::Decimal => id::DECIMAL,
582 KnownLogical::Uuid => id::UUID,
583 }
584 }
585
586 pub const fn kind(self) -> Kind {
590 match self {
591 KnownLogical::Undefined => Kind::Null,
592 KnownLogical::Date | KnownLogical::Timestamp | KnownLogical::Decimal => Kind::Int,
593 KnownLogical::Uuid => Kind::Bytes,
594 }
595 }
596
597 pub const fn from_id(id: u8) -> Option<Self> {
601 match id {
602 id::UNDEFINED => Some(KnownLogical::Undefined),
603 id::DATE => Some(KnownLogical::Date),
604 id::TIMESTAMP => Some(KnownLogical::Timestamp),
605 id::DECIMAL => Some(KnownLogical::Decimal),
606 id::UUID => Some(KnownLogical::Uuid),
607 _ => None,
608 }
609 }
610}
611
612#[derive(Clone, Copy, Debug, PartialEq, Eq)]
616pub struct UnknownLogicalId(u8);
617
618impl UnknownLogicalId {
619 pub const fn new(id: u8) -> Option<Self> {
622 if id > id::LAST_KNOWN {
623 Some(Self(id))
624 } else {
625 None
626 }
627 }
628
629 pub const fn get(self) -> u8 {
631 self.0
632 }
633}
634
635impl Logical {
636 pub fn to_wire(self) -> (u8, u8) {
638 match self {
639 Logical::Undefined => (id::UNDEFINED, 0),
640 Logical::Date => (id::DATE, 0),
641 Logical::Timestamp(unit) => (id::TIMESTAMP, unit as u8),
642 Logical::Decimal { scale } => (id::DECIMAL, scale),
643 Logical::Uuid => (id::UUID, 0),
644 Logical::Unknown(u) => (u.id, u.param),
645 }
646 }
647
648 pub fn from_wire(id: u8, param: u8) -> Option<Self> {
652 match (id, param) {
653 (id::NONE, _) => None,
654 (id::UNDEFINED, 0) => Some(Logical::Undefined),
655 (id::DATE, 0) => Some(Logical::Date),
656 (id::TIMESTAMP, p) => TimeUnit::from_wire(p).map(Logical::Timestamp),
657 (id::DECIMAL, scale) => {
658 (scale <= MAX_DECIMAL_SCALE).then_some(Logical::Decimal { scale })
659 }
660 (id::UUID, 0) => Some(Logical::Uuid),
661 (id::UNDEFINED..=id::LAST_KNOWN, _) => None,
662 (id, param) => Some(Logical::Unknown(UnknownLogical { id, param })),
663 }
664 }
665
666 pub fn admits(self, v: &Physical<'_>) -> bool {
669 match (self, *v) {
670 (Logical::Undefined, Physical::Null) => true,
671 (Logical::Date, Physical::Int(i)) => date_in_range(i),
672 (Logical::Timestamp(unit), Physical::Int(i)) => timestamp_nanos(unit, i).is_some(),
673 (Logical::Decimal { scale }, Physical::Int(i)) => decimal_in_range(scale, i),
674 (Logical::Uuid, Physical::Bytes(b)) => b.len() == 16,
675 (Logical::Unknown(_), _) => true,
676 _ => false,
677 }
678 }
679}
680
681fn date_in_range(days: Int) -> bool {
682 days.to_i128()
683 .and_then(|d| i32::try_from(d).ok())
684 .is_some_and(|d| (MIN_DATE_DAYS..=MAX_DATE_DAYS).contains(&d))
685}
686
687pub fn timestamp_nanos(unit: TimeUnit, count: Int) -> Option<i128> {
690 let nanos = count.to_i128()?.checked_mul(unit.nanos())?;
691 (MIN_TIMESTAMP_NANOS..=MAX_TIMESTAMP_NANOS)
692 .contains(&nanos)
693 .then_some(nanos)
694}
695
696fn decimal_in_range(scale: u8, mantissa: Int) -> bool {
697 scale <= MAX_DECIMAL_SCALE
698 && mantissa
699 .to_i128()
700 .is_some_and(|m| m > -DECIMAL_MANTISSA_LIMIT && m < DECIMAL_MANTISSA_LIMIT)
701}
702
703#[cfg(feature = "chrono")]
705pub mod chrono {
706 use super::{Int, TimeUnit};
707 use ::chrono::{DateTime, Datelike, NaiveDate, Utc};
708
709 const EPOCH_DAYS_FROM_CE: i32 = 719_163;
710
711 pub fn date(days: Int) -> Option<NaiveDate> {
713 let days = i32::try_from(days.to_i128()?).ok()?;
714 NaiveDate::from_num_days_from_ce_opt(days.checked_add(EPOCH_DAYS_FROM_CE)?)
715 }
716
717 pub fn date_days(date: NaiveDate) -> i32 {
719 date.num_days_from_ce() - EPOCH_DAYS_FROM_CE
720 }
721
722 pub fn timestamp(unit: TimeUnit, count: Int) -> Option<DateTime<Utc>> {
725 let nanos = super::timestamp_nanos(unit, count)?;
726 let seconds = i64::try_from(nanos.div_euclid(1_000_000_000)).ok()?;
727 DateTime::from_timestamp(seconds, nanos.rem_euclid(1_000_000_000) as u32)
728 }
729
730 pub fn timestamp_nanos(ts: DateTime<Utc>) -> Option<i128> {
733 let sub = ts.timestamp_subsec_nanos();
734 (sub < 1_000_000_000).then(|| i128::from(ts.timestamp()) * 1_000_000_000 + i128::from(sub))
735 }
736
737 pub fn timestamp_count(unit: TimeUnit, nanos: i128) -> Option<i128> {
739 (nanos % unit.nanos() == 0).then(|| nanos / unit.nanos())
740 }
741
742 #[cfg(test)]
743 mod tests {
744 use super::super::{
745 MAX_DATE_DAYS, MAX_TIMESTAMP_NANOS, MIN_DATE_DAYS, MIN_TIMESTAMP_NANOS,
746 };
747 use super::*;
748
749 #[test]
750 fn range_constants_match_chrono() {
751 assert_eq!(date_days(NaiveDate::MIN), MIN_DATE_DAYS);
752 assert_eq!(date_days(NaiveDate::MAX), MAX_DATE_DAYS);
753 assert_eq!(
754 timestamp_nanos(::chrono::NaiveDateTime::MIN.and_utc()),
755 Some(MIN_TIMESTAMP_NANOS)
756 );
757 assert_eq!(
758 timestamp_nanos(::chrono::NaiveDateTime::MAX.and_utc()),
759 Some(MAX_TIMESTAMP_NANOS)
760 );
761 assert_eq!(
762 date_days(NaiveDate::from_ymd_opt(1970, 1, 1).expect("date")),
763 0
764 );
765 assert_eq!(date(Int::I32(0)), NaiveDate::from_ymd_opt(1970, 1, 1));
766 }
767
768 #[test]
769 fn out_of_range_counts_have_no_chrono_value() {
770 assert!(date(Int::I32(MIN_DATE_DAYS - 1)).is_none());
771 assert!(date(Int::I32(MAX_DATE_DAYS + 1)).is_none());
772 assert!(date(Int::I64(i64::MAX)).is_none());
773 assert!(timestamp(TimeUnit::Nanos, Int::I128(MAX_TIMESTAMP_NANOS + 1)).is_none());
774 assert!(timestamp(TimeUnit::Seconds, Int::I64(i64::MAX)).is_none());
775 assert!(timestamp(TimeUnit::Nanos, Int::I128(MAX_TIMESTAMP_NANOS)).is_some());
776 }
777
778 #[test]
779 fn leap_second_has_no_count() {
780 let leap = NaiveDate::from_ymd_opt(2016, 12, 31)
781 .and_then(|d| d.and_hms_nano_opt(23, 59, 59, 1_500_000_000))
782 .expect("leap second")
783 .and_utc();
784 assert!(timestamp_nanos(leap).is_none());
785
786 let first_leap_nano = NaiveDate::from_ymd_opt(2016, 12, 31)
787 .and_then(|d| d.and_hms_nano_opt(23, 59, 59, 1_000_000_000))
788 .expect("leap second")
789 .and_utc();
790 assert!(timestamp_nanos(first_leap_nano).is_none());
791
792 let last_ordinary_nano = NaiveDate::from_ymd_opt(2016, 12, 31)
793 .and_then(|d| d.and_hms_nano_opt(23, 59, 59, 999_999_999))
794 .expect("instant")
795 .and_utc();
796 assert_eq!(
797 timestamp_nanos(last_ordinary_nano),
798 Some(1_483_228_799_999_999_999)
799 );
800 }
801
802 #[test]
803 fn a_count_before_the_epoch_is_the_right_instant() {
804 let one_ns_before = timestamp(TimeUnit::Nanos, Int::I128(-1)).expect("instant");
805 assert_eq!(
806 one_ns_before,
807 DateTime::from_timestamp(-1, 999_999_999).expect("instant")
808 );
809 assert_eq!(timestamp_nanos(one_ns_before), Some(-1));
810
811 let ms = timestamp(TimeUnit::Millis, Int::I64(-1_500)).expect("instant");
812 assert_eq!(
813 ms,
814 DateTime::from_timestamp(-2, 500_000_000).expect("instant")
815 );
816 assert_eq!(timestamp_nanos(ms), Some(-1_500_000_000));
817 }
818
819 #[test]
820 fn a_count_is_reduced_only_when_exact() {
821 assert_eq!(timestamp_count(TimeUnit::Nanos, 7), Some(7));
822 assert_eq!(timestamp_count(TimeUnit::Millis, 3_000_000), Some(3));
823 assert_eq!(timestamp_count(TimeUnit::Millis, -3_000_000), Some(-3));
824 assert_eq!(timestamp_count(TimeUnit::Seconds, 2_000_000_000), Some(2));
825 assert_eq!(timestamp_count(TimeUnit::Millis, 3_000_001), None);
826 assert_eq!(timestamp_count(TimeUnit::Seconds, 1_500_000_000), None);
827 }
828 }
829}
830
831#[cfg(feature = "rust_decimal")]
833pub mod decimal {
834 use super::Int;
835 use rust_decimal::Decimal;
836
837 pub fn decimal(scale: u8, mantissa: Int) -> Option<Decimal> {
839 Decimal::try_from_i128_with_scale(mantissa.to_i128()?, scale.into()).ok()
840 }
841
842 pub fn parts(d: Decimal) -> (u8, i128) {
845 (d.scale() as u8, d.mantissa())
846 }
847
848 #[cfg(test)]
849 mod tests {
850 use super::*;
851 use core::str::FromStr;
852
853 #[test]
854 fn negative_zero_normalizes_to_zero_with_its_scale() {
855 let mut neg = Decimal::new(0, 2);
856 neg.set_sign_negative(true);
857 assert!(neg.is_sign_negative());
858 assert_eq!(parts(neg), (2, 0));
859 let back = decimal(2, Int::I128(0)).expect("decimal");
860 assert!(!back.is_sign_negative());
861 assert_eq!(back.to_string(), "0.00");
862 }
863
864 #[test]
865 fn scale_and_mantissa_round_trip() {
866 let d = Decimal::from_str("-1234.5678").expect("decimal");
867 let (scale, mantissa) = parts(d);
868 assert_eq!((scale, mantissa), (4, -12_345_678));
869 assert_eq!(decimal(scale, Int::I128(mantissa)), Some(d));
870 }
871
872 #[test]
873 fn what_rust_decimal_cannot_hold_has_no_decimal() {
874 assert_eq!(decimal(28, Int::I128(1)).map(|d| d.scale()), Some(28));
875 assert_eq!(decimal(29, Int::I128(1)), None);
876 assert_eq!(decimal(0, Int::I128(1 << 96)), None);
877 assert_eq!(decimal(0, Int::I128(-(1 << 96))), None);
878 assert_eq!(decimal(0, Int::U128(u128::MAX)), None);
879 }
880 }
881}
882
883impl fmt::Display for Logical {
884 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
885 match self {
886 Logical::Undefined => f.write_str("undefined"),
887 Logical::Date => f.write_str("date"),
888 Logical::Timestamp(unit) => {
889 let unit = match unit {
890 TimeUnit::Seconds => "s",
891 TimeUnit::Millis => "ms",
892 TimeUnit::Micros => "us",
893 TimeUnit::Nanos => "ns",
894 };
895 write!(f, "timestamp({unit})")
896 }
897 Logical::Decimal { scale } => write!(f, "decimal({scale})"),
898 Logical::Uuid => f.write_str("uuid"),
899 Logical::Unknown(u) => write!(f, "logical({}, {})", u.id, u.param),
900 }
901 }
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907
908 #[test]
909 fn a_fixed_type_is_normalised_to_how_its_leaf_is_written() {
910 let fixed = |ty, width| FixedType::new(ty, width).expect("fixed width");
911 assert_eq!(
913 fixed(Some(Logical::Date), Width::I64),
914 fixed(Some(Logical::Date), Width::I32)
915 );
916 assert_eq!(fixed(Some(Logical::Date), Width::U8).width(), Width::I32);
917 let ns = Some(Logical::Timestamp(TimeUnit::Nanos));
918 assert_eq!(fixed(ns, Width::I64).width(), Width::I128);
919 assert_eq!(
920 fixed(Some(Logical::Decimal { scale: 2 }), Width::I64),
921 fixed(Some(Logical::Decimal { scale: 0 }), Width::I128)
922 );
923 let ms = Some(Logical::Timestamp(TimeUnit::Millis));
925 assert_eq!(fixed(ms, Width::I64).width(), Width::I64);
926 assert_ne!(fixed(ms, Width::I64), fixed(ms, Width::I32));
927 let unknown = Logical::from_wire(200, 9);
928 assert_eq!(fixed(unknown, Width::F32).logical(), unknown);
929 assert_eq!(
930 FixedType::plain(Width::U8).map(FixedType::logical),
931 Some(None)
932 );
933 assert_eq!(FixedType::plain(Width::Bytes16), None);
935 assert_eq!(
936 fixed(Some(Logical::Uuid), Width::Bytes16).width(),
937 Width::Bytes16
938 );
939 }
940
941 #[test]
942 fn a_type_not_over_a_fixed_width_physical_type_has_no_fixed_type() {
943 assert_eq!(FixedType::new(Some(Logical::Uuid), Width::U128), None);
944 let unknown = Logical::from_wire(200, 0);
945 assert_eq!(FixedType::new(unknown, Width::Bytes16), None);
946 assert_eq!(FixedType::new(Some(Logical::Date), Width::Bytes16), None);
947 assert_eq!(FixedType::new(Some(Logical::Undefined), Width::U8), None);
948 assert_eq!(FixedType::new(Some(Logical::Date), Width::F64), None);
949 assert_eq!(
950 FixedType::new(Some(Logical::Timestamp(TimeUnit::Millis)), Width::F64),
951 None
952 );
953 assert_eq!(
954 FixedType::new(Some(Logical::Decimal { scale: 2 }), Width::F32),
955 None
956 );
957 assert_eq!(Width::of(&Physical::Bytes(&[0; 16])), Some(Width::Bytes16));
958 assert_eq!(Width::Bytes16.kind(), Kind::Bytes);
959 for v in [
960 Physical::Null,
961 Physical::Bool(true),
962 Physical::Text("x"),
963 Physical::Bytes(&[1]),
964 ] {
965 assert_eq!(Width::of(&v), None);
966 assert_eq!(FixedType::of(None, &v), None);
967 }
968 assert_eq!(Width::of(&Physical::Int(Int::U16(1))), Some(Width::U16));
969 assert_eq!(Width::F32.kind(), Kind::Float);
970 assert_eq!(Width::I128.kind(), Kind::Int);
971 }
972
973 #[test]
974 fn every_known_logical_type_round_trips_through_the_wire() {
975 let known = [
976 Logical::Undefined,
977 Logical::Date,
978 Logical::Timestamp(TimeUnit::Seconds),
979 Logical::Timestamp(TimeUnit::Millis),
980 Logical::Timestamp(TimeUnit::Micros),
981 Logical::Timestamp(TimeUnit::Nanos),
982 Logical::Decimal { scale: 0 },
983 Logical::Decimal { scale: 28 },
984 Logical::Uuid,
985 ];
986 for ty in known {
987 let (id, param) = ty.to_wire();
988 assert_eq!(Logical::from_wire(id, param), Some(ty), "{ty}");
989 }
990 }
991
992 #[test]
993 fn id_zero_is_no_logical_type() {
994 assert_eq!(Logical::from_wire(0, 0), None);
995 assert_eq!(Logical::from_wire(0, 7), None);
996 }
997
998 #[test]
999 fn a_known_id_with_a_bad_parameter_is_refused() {
1000 assert_eq!(Logical::from_wire(1, 1), None);
1001 assert_eq!(Logical::from_wire(2, 1), None);
1002 assert_eq!(Logical::from_wire(3, 4), None);
1003 assert_eq!(Logical::from_wire(4, 29), None);
1004 assert_eq!(Logical::from_wire(5, 1), None);
1005 }
1006
1007 #[test]
1008 fn an_unknown_id_is_carried_through() {
1009 let ty = Logical::from_wire(200, 9).expect("unknown");
1010 assert!(matches!(ty, Logical::Unknown(u) if u.id() == 200 && u.param() == 9));
1011 assert_eq!(ty.to_wire(), (200, 9));
1012 assert!(ty.admits(&Physical::Text("anything")));
1013 }
1014
1015 fn known(logical: Logical) -> Option<KnownLogical> {
1018 match logical {
1019 Logical::Undefined => Some(KnownLogical::Undefined),
1020 Logical::Date => Some(KnownLogical::Date),
1021 Logical::Timestamp(_) => Some(KnownLogical::Timestamp),
1022 Logical::Decimal { .. } => Some(KnownLogical::Decimal),
1023 Logical::Uuid => Some(KnownLogical::Uuid),
1024 Logical::Unknown(_) => None,
1025 }
1026 }
1027
1028 #[test]
1032 fn known_logical_agrees_with_logical_on_every_id() {
1033 for id in 0..=u8::MAX {
1034 let known_id = KnownLogical::from_id(id);
1035 assert_eq!(
1036 known_id.is_some(),
1037 (id::UNDEFINED..=id::LAST_KNOWN).contains(&id),
1038 "id {id}"
1039 );
1040 assert_eq!(known_id.map(KnownLogical::id), known_id.map(|_| id));
1041 for param in 0..=u8::MAX {
1044 if let Some(logical) = Logical::from_wire(id, param) {
1045 assert_eq!(known(logical), known_id, "{logical:?}");
1046 }
1047 }
1048 let unknown = UnknownLogicalId::new(id);
1049 assert_eq!(unknown.is_some(), id > id::LAST_KNOWN, "id {id}");
1050 assert_eq!(unknown.map(UnknownLogicalId::get), unknown.map(|_| id));
1051 }
1052 assert_eq!(known(Logical::from_wire(200, 0).expect("unknown")), None);
1053 }
1054
1055 #[test]
1058 fn kind_is_the_kind_admits_accepts() {
1059 let samples = [
1060 Physical::Null,
1061 Physical::Bool(true),
1062 Physical::Int(Int::I64(0)),
1063 Physical::Float(Float::F64(0.0)),
1064 Physical::Text(""),
1065 Physical::Bytes(&[0; 16]),
1066 ];
1067 let logicals = [
1068 Logical::Undefined,
1069 Logical::Date,
1070 Logical::Timestamp(TimeUnit::Millis),
1071 Logical::Decimal { scale: 2 },
1072 Logical::Uuid,
1073 ];
1074 for logical in logicals {
1075 for sample in &samples {
1076 let kind = known(logical).expect("known").kind();
1077 assert_eq!(
1078 logical.admits(sample),
1079 kind == sample.kind(),
1080 "{logical:?} over {:?}",
1081 sample.kind()
1082 );
1083 }
1084 }
1085 }
1086
1087 #[test]
1088 fn admits_checks_the_physical_kind() {
1089 assert!(Logical::Undefined.admits(&Physical::Null));
1090 assert!(!Logical::Undefined.admits(&Physical::Bool(false)));
1091 assert!(Logical::Date.admits(&Physical::Int(Int::I32(0))));
1092 assert!(!Logical::Date.admits(&Physical::Text("2024-01-01")));
1093 assert!(Logical::Uuid.admits(&Physical::Bytes(&[0; 16])));
1094 assert!(!Logical::Uuid.admits(&Physical::Bytes(&[0; 15])));
1095 assert!(!Logical::Uuid.admits(&Physical::Text("")));
1096 }
1097
1098 #[test]
1099 fn admits_checks_the_range() {
1100 assert!(Logical::Date.admits(&Physical::Int(Int::I64(MIN_DATE_DAYS.into()))));
1101 assert!(!Logical::Date.admits(&Physical::Int(Int::I64(i64::from(MIN_DATE_DAYS) - 1))));
1102 assert!(!Logical::Date.admits(&Physical::Int(Int::U128(u128::MAX))));
1103 let ns = Logical::Timestamp(TimeUnit::Nanos);
1104 assert!(ns.admits(&Physical::Int(Int::I128(MAX_TIMESTAMP_NANOS))));
1105 assert!(!ns.admits(&Physical::Int(Int::I128(MAX_TIMESTAMP_NANOS + 1))));
1106 assert!(!Logical::Timestamp(TimeUnit::Seconds).admits(&Physical::Int(Int::I128(i128::MAX))));
1107 assert!(Logical::Decimal { scale: 28 }.admits(&Physical::Int(Int::I128((1 << 96) - 1))));
1108 assert!(!Logical::Decimal { scale: 28 }.admits(&Physical::Int(Int::I128(1 << 96))));
1109 assert!(!Logical::Decimal { scale: 29 }.admits(&Physical::Int(Int::I8(1))));
1110 assert!(Logical::Date.admits(&Physical::Int(Int::I32(MAX_DATE_DAYS))));
1112 assert!(!Logical::Date.admits(&Physical::Int(Int::I64(i64::from(MAX_DATE_DAYS) + 1))));
1113 assert!(ns.admits(&Physical::Int(Int::I128(MIN_TIMESTAMP_NANOS))));
1114 assert!(!ns.admits(&Physical::Int(Int::I128(MIN_TIMESTAMP_NANOS - 1))));
1115 }
1116
1117 #[test]
1118 fn floats_compare_by_bit_pattern() {
1119 assert_ne!(Float::F64(-0.0), Float::F64(0.0));
1120 assert_eq!(Float::F64(f64::NAN), Float::F64(f64::NAN));
1121 assert_eq!(Float::F32(1.5), Float::F32(1.5));
1122 assert_ne!(Float::F32(-0.0), Float::F32(0.0));
1123 assert_ne!(Float::F32(1.0), Float::F64(1.0));
1124 assert_ne!(Physical::Int(Int::I8(34)), Physical::Int(Int::U64(34)));
1125 }
1126
1127 #[test]
1128 fn ints_compare_by_width_sign_and_value() {
1129 assert_eq!(Int::I8(-1), Int::I8(-1));
1130 assert_ne!(Int::I8(-1), Int::I8(1));
1131 assert_ne!(Int::I8(-1), Int::I128(-1));
1132 assert_ne!(Int::I8(-1), Int::U8(255));
1133 assert_eq!(Int::U128(u128::MAX), Int::U128(u128::MAX));
1134 assert_ne!(Int::U128(u128::MAX), Int::I128(-1));
1135 }
1136
1137 #[test]
1138 fn every_physical_kind_compares_to_itself_and_nothing_else() {
1139 let kinds = [
1140 Physical::Null,
1141 Physical::Bool(true),
1142 Physical::Int(Int::I32(1)),
1143 Physical::Float(Float::F64(1.0)),
1144 Physical::Text("a"),
1145 Physical::Bytes(b"a"),
1146 ];
1147 for (i, a) in kinds.iter().enumerate() {
1148 for (j, b) in kinds.iter().enumerate() {
1149 assert_eq!(a == b, i == j, "{a:?} {b:?}");
1150 }
1151 }
1152 assert_ne!(Physical::Bool(true), Physical::Bool(false));
1153 assert_ne!(Physical::Text("a"), Physical::Text("b"));
1154 assert_ne!(Physical::Text("a"), Physical::Text("ab"));
1155 assert_ne!(Physical::Bytes(b"a"), Physical::Bytes(b"b"));
1156 assert_ne!(Physical::Bytes(b"a"), Physical::Bytes(b"ab"));
1157 }
1158
1159 #[test]
1160 fn debug_output_is_redacted() {
1161 for v in [
1162 format!("{:?}", Physical::Text("secret")),
1163 format!("{:?}", Physical::Bytes(b"secret")),
1164 format!("{:?}", Physical::Int(Int::I64(1_234_567))),
1165 format!("{:?}", Physical::Float(Float::F64(1_234_567.0))),
1166 format!("{:?}", Physical::Bool(true)),
1167 format!("{:?}", Int::U8(123)),
1168 ] {
1169 assert!(
1170 !v.contains("secret")
1171 && !v.contains("1234567")
1172 && !v.contains("true")
1173 && !v.contains("123"),
1174 "{v}"
1175 );
1176 assert!(v.contains("***"), "{v}");
1177 }
1178 assert!(format!("{:?}", crate::plaintext::Bytes(b"secret")).contains("***"));
1179 assert!(!format!("{:?}", crate::plaintext::Bytes(b"secret")).contains("secret"));
1180 let raw = format!(
1181 "{:?}",
1182 crate::plaintext::Raw(None, Physical::Text("secret"))
1183 );
1184 assert!(raw.contains("***") && !raw.contains("secret"), "{raw}");
1185 let null = format!("{:?}", Physical::Null);
1188 assert!(null.ends_with("::Null"), "{null}");
1189 }
1190
1191 #[test]
1192 fn ints_and_floats_zeroize_in_place() {
1193 let mut i = Int::I64(7);
1194 i.zeroize();
1195 assert_eq!(i, Int::I64(0));
1196 let mut f = Float::F32(1.5);
1197 f.zeroize();
1198 assert_eq!(f, Float::F32(0.0));
1199 }
1200
1201 #[test]
1202 fn a_javascript_date_beyond_chrono_is_refused() {
1203 let millis = Logical::Timestamp(TimeUnit::Millis);
1205 assert!(!millis.admits(&Physical::Int(Int::I64(8_640_000_000_000_000))));
1206 assert!(millis.admits(&Physical::Int(Int::I64(8_000_000_000_000_000))));
1207 }
1208
1209 #[test]
1210 fn int_conversions() {
1211 assert_eq!(Int::U128(u128::MAX).to_i128(), None);
1212 assert_eq!(Int::I8(-1).to_u128(), None);
1213 assert_eq!(Int::U128(u128::MAX).to_u128(), Some(u128::MAX));
1214 assert_eq!(Int::I8(5).to_u128(), Some(5));
1215 assert_eq!(Int::I8(-1).sign_and_word(), (true, u128::MAX));
1216 assert_eq!(Int::U128(u128::MAX).sign_and_word(), (false, u128::MAX));
1217 assert_eq!(Int::U64(34).sign_and_word(), (false, 34));
1218 assert_eq!(Int::I8(0).sign_and_word(), (false, 0));
1219 }
1220
1221 #[test]
1222 fn every_width_has_one_sign_and_word() {
1223 for v in [
1226 Int::I8(-1),
1227 Int::I16(-1),
1228 Int::I32(-1),
1229 Int::I64(-1),
1230 Int::I128(-1),
1231 ] {
1232 assert_eq!(v.sign_and_word(), (true, u128::MAX));
1233 }
1234 let unsigned = [
1235 (Int::U8(u8::MAX), u128::from(u8::MAX)),
1236 (Int::U16(u16::MAX), u128::from(u16::MAX)),
1237 (Int::U32(u32::MAX), u128::from(u32::MAX)),
1238 (Int::U64(u64::MAX), u128::from(u64::MAX)),
1239 (Int::U128(u128::MAX), u128::MAX),
1240 ];
1241 for (v, word) in unsigned {
1242 assert_eq!(v.sign_and_word(), (false, word));
1243 }
1244 assert_eq!(Int::I128(i128::MIN).sign_and_word(), (true, 1 << 127));
1246 assert_eq!(Int::U128(1 << 127).sign_and_word(), (false, 1 << 127));
1247 assert_eq!(
1248 Int::I64(i64::MAX).sign_and_word(),
1249 (false, i64::MAX as u128)
1250 );
1251 assert_ne!(Int::I128(i128::MIN), Int::I128(i128::MAX));
1253 }
1254
1255 #[test]
1256 fn a_decimal_mantissa_is_bounded_on_both_sides() {
1257 let ty = Logical::Decimal { scale: 0 };
1258 assert!(ty.admits(&Physical::Int(Int::I128(1 - (1 << 96)))));
1259 assert!(!ty.admits(&Physical::Int(Int::I128(-(1 << 96)))));
1260 }
1261
1262 #[test]
1263 fn logical_displays_its_type() {
1264 let logical = [
1265 (Logical::Undefined, "undefined"),
1266 (Logical::Date, "date"),
1267 (Logical::Timestamp(TimeUnit::Seconds), "timestamp(s)"),
1268 (Logical::Timestamp(TimeUnit::Millis), "timestamp(ms)"),
1269 (Logical::Timestamp(TimeUnit::Micros), "timestamp(us)"),
1270 (Logical::Timestamp(TimeUnit::Nanos), "timestamp(ns)"),
1271 (Logical::Decimal { scale: 4 }, "decimal(4)"),
1272 (Logical::Uuid, "uuid"),
1273 (
1274 Logical::from_wire(200, 9).expect("unknown"),
1275 "logical(200, 9)",
1276 ),
1277 ];
1278 for (v, s) in logical {
1279 assert_eq!(v.to_string(), s);
1280 }
1281 }
1282
1283 #[test]
1284 fn every_physical_value_names_its_kind_and_kinds_have_fixed_wire_bytes() {
1285 let pairs = [
1286 (Physical::Null, Kind::Null, 0),
1287 (Physical::Bool(true), Kind::Bool, 1),
1288 (Physical::Int(Int::I8(1)), Kind::Int, 2),
1289 (Physical::Float(Float::F32(1.0)), Kind::Float, 3),
1290 (Physical::Text("a"), Kind::Text, 4),
1291 (Physical::Bytes(&[1]), Kind::Bytes, 5),
1292 ];
1293 for (value, kind, wire) in pairs {
1294 assert_eq!(value.kind(), kind);
1295 assert_eq!(kind.to_wire(), wire, "{kind:?}");
1296 }
1297 }
1298}