Skip to main content

vitaminc_aead/plaintext/
kinds.rs

1//! Physical kinds and logical types.
2//!
3//! A scalar is a [`Physical`] value, optionally annotated with the
4//! [`Logical`] type it represents. The split is Arrow's and Parquet's:
5//!
6//! - **Physical kinds are a small closed set**: null, bool, int, float, text,
7//!   bytes. `Physical` is an exhaustive enum, so adding one is a breaking
8//!   change that fails to compile in every sink. That is deliberate: a new
9//!   physical kind changes what every consumer has to handle.
10//! - **Logical types are open.** A timestamp is an int with a unit, a date
11//!   is an int, a uuid is sixteen bytes. `Logical` is `#[non_exhaustive]`,
12//!   so a sink outside this crate has to say what it does with a logical
13//!   type it does not know. Structural sinks (encryption, frames, host
14//!   builders) carry the annotation through. Index sinks refuse it.
15//!
16//! The int's width ([`Int`]'s variant) is a host-fidelity hint, not a kind:
17//! every width shares one equality domain, and only the leaf, the frame and
18//! the host builders look at it.
19//!
20//! The validity rules here ([`Logical::admits`]) are self-contained, so the
21//! core model has no dependency on chrono or `rust_decimal`. The conversions
22//! to and from those crates' types sit behind their features.
23//!
24//! # Adding a logical type
25//!
26//! A logical type is a meaning layered on a physical kind that already
27//! exists. Never add a physical kind for one. The steps, in this module
28//! unless said otherwise:
29//!
30//! 1. **Choose the physical kind** it rides on (an int, sixteen bytes, a
31//!    string) and its parameter, if it has one. The parameter is a single
32//!    byte carried with every value, like a decimal's scale. A type that
33//!    needs more than a byte needs a different design; the `0xF4` tag is
34//!    reserved for a length-prefixed envelope for that day.
35//! 2. **Add the variant** to [`Logical`] and a constant to [`id`], the next
36//!    value after [`id::LAST_KNOWN`], then move `LAST_KNOWN` up to it. Ids
37//!    are a cross-language commitment: never renumber or reuse one.
38//! 3. **Name it without its parameter**: add the matching variant to
39//!    [`KnownLogical`], with its id and the one kind it is stored as in
40//!    [`KnownLogical::id`], [`KnownLogical::kind`] and
41//!    [`KnownLogical::from_id`]. This is what a schema binds for a column
42//!    (see [`LeafTypeAad`](crate::LeafTypeAad)), so the kind is part of the
43//!    cross-language commitment too.
44//! 4. **Map it on the wire** in [`Logical::to_wire`] and
45//!    [`Logical::from_wire`]. `from_wire` must return `None` for a
46//!    parameter the type cannot take, so a malformed envelope is refused
47//!    rather than read as some other value.
48//! 5. **State its validity rule** in [`Logical::admits`]: which physical
49//!    kind it accepts, and the range. Keep the rule self-contained, as the
50//!    date and timestamp ranges are (constants pinned by a test, not a
51//!    dependency), so the core model stays dependency-free.
52//! 6. **Name it** in the `Display` impl.
53//! 7. **Add a source**: an `Encrypt` impl for the Rust type that calls
54//!    `sink.scalar(Some(Logical::YourType), Physical::...)`. If the type
55//!    comes from another crate, put the impl behind a feature named after
56//!    that crate, as the chrono, `rust_decimal` and uuid sources are.
57//! 8. **Extend the tests here**: the variant goes into
58//!    `every_known_logical_type_round_trips_through_the_wire`, its pairing
59//!    with the [`KnownLogical`] variant into the `known` helper, a parameter
60//!    it refuses into `a_known_id_with_a_bad_parameter_is_refused`, and its
61//!    range edges into `admits_checks_the_range`.
62//!
63//! Outside this module, each consumer that interprets logical types opts
64//! in on its own: a typed decode target for the Rust type, an equality
65//! domain and normalization in the index layer, and a conversion in each
66//! host binding. Structural sinks need nothing. Encryption, the frame and
67//! the leaf codec carry any logical type through the generic envelope, so
68//! a new type gets no tag of its own, and a sink built before the type
69//! existed still passes it through as [`Logical::Unknown`].
70//!
71//! The compiler finds most of a missed step: `to_wire`, `Display` and the
72//! tests' `known` helper match exhaustively, so a new variant fails to
73//! build until they are updated, and `KnownLogical::id` and
74//! `KnownLogical::kind` do the same for a new `KnownLogical` variant.
75//! `from_wire`, `KnownLogical::from_id` and `admits` have fallbacks, so a
76//! type missed there is refused rather than reported; the round-trip tests
77//! are what catch it.
78
79use core::fmt;
80
81use vitaminc_protected::{Choice, OpaqueDebug, TimingSafeEq};
82use zeroize::Zeroize;
83
84/// One scalar value, as stored. Borrowed, so a source can hand over text
85/// and bytes without copying.
86///
87/// A scalar is plaintext, so `Debug` redacts the value and `==` compares
88/// in constant time (see [`TimingSafeEq`]). Equality is structural: an
89/// int's width and a float's bit pattern both count. It is for tests and
90/// sinks that compare what a source said, not for comparing values across
91/// widths (see [`Int::sign_and_word`]).
92///
93/// `Physical` is a view: the text and bytes it borrows are owned, and
94/// wiped, by the source. It therefore does not implement `Zeroize`
95/// itself; [`Int`] and [`Float`], which are owned, do.
96#[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/// A physical kind, named without a value: what a schema expects a column
107/// to hold. Closed, like [`Physical`], and with a wire byte of its own for
108/// the places a kind is bound rather than carried, such as
109/// [`LeafTypeAad`](crate::LeafTypeAad).
110#[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    /// The kind's wire byte. A cross-language commitment: never renumber.
122    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    /// The kind of this value.
136    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/// An integer, with the width it came from.
149///
150/// `==` compares the width as well as the value, so `I8(34) != U64(34)`.
151/// Code that compares values across widths goes through [`to_i128`],
152/// [`to_u128`] or [`sign_and_word`].
153///
154/// [`to_i128`]: Int::to_i128
155/// [`to_u128`]: Int::to_u128
156/// [`sign_and_word`]: Int::sign_and_word
157#[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    /// The value as an `i128`, unless it is a `u128` above `i128::MAX`.
173    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    /// The value as a `u128`, unless it is negative.
189    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    /// The value as a sign and a two's-complement 128-bit word: one layout
197    /// covering `i128::MIN` to `u128::MAX`, for equality encodings.
198    pub fn sign_and_word(self) -> (bool, u128) {
199        let (sign, word) = self.sign_bit_and_word();
200        (sign == 1, word)
201    }
202
203    /// [`sign_and_word`](Int::sign_and_word) with the sign as a bit, for
204    /// constant-time equality.
205    ///
206    /// Only the width is branched on. The word is the value sign-extended
207    /// (or zero-extended) to 128 bits, and a signed value's sign is that
208    /// word's top bit, taken by shift rather than by comparing the value
209    /// with zero, so no branch depends on the value. This also covers a
210    /// `u128` above `i128::MAX` without a second path.
211    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/// A float, with the width it came from. Compared and carried by bit
246/// pattern, so `NaN` payloads and `-0.0` survive, and `-0.0 != 0.0`.
247#[derive(Clone, Copy, OpaqueDebug, TimingSafeEq, Zeroize)]
248pub enum Float {
249    F32(f32),
250    F64(f64),
251}
252
253/// The physical type of a fixed-width scalar whose leaf has a payload: an
254/// int or a float, with its width, or sixteen bytes. What a [`FixedType`]
255/// rides on.
256///
257/// `bool` is fixed width too, but its leaf is the tag alone, one byte, the
258/// same length as the plain null leaf, so it has no width here. Bytes have
259/// a fixed width only where a logical type fixes it: [`Width::Bytes16`] is
260/// for a uuid.
261#[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    /// Sixteen bytes.
276    Bytes16,
277}
278
279impl Width {
280    /// The physical kind: [`Kind::Int`], [`Kind::Float`] or [`Kind::Bytes`].
281    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    /// A zero of this physical type: a value whose leaf has the header and
290    /// the length every value of the type has.
291    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    /// The width of a value of this physical type, or `None` for a value
310    /// with no [`Width`]: null, bool, text, and bytes of any length but
311    /// sixteen.
312    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/// A fixed-width scalar type, named without a value: what an absent value
335/// of that type stands in for.
336///
337/// AES-GCM keeps the plaintext's length, so a plain null leaf (one byte)
338/// sealed in place of a date (five) shows which values are absent to
339/// anyone who can see the ciphertexts. A null of a `FixedType` is sealed as
340/// a **typed null** padded to the type's own leaf length, so a sealed
341/// `None` and a sealed `Some` are the same length. See
342/// [`Sink::typed_null`](crate::plaintext::Sink::typed_null) and
343/// [`tags::TYPED_NULL`](crate::tags::TYPED_NULL).
344///
345/// A `FixedType` is normalised to how its leaf is written, so two are equal
346/// exactly when their typed nulls are the same bytes. A logical type with a
347/// tag of its own fixes its width: a date is written as an `i32` day count
348/// and a nanosecond timestamp and a decimal as 128-bit words, whatever
349/// width the value came with. A decimal's scale travels inside its payload
350/// rather than in its tag, so every decimal has the one typed null, of
351/// scale 0.
352#[derive(Clone, Copy, Debug, PartialEq, Eq)]
353pub struct FixedType {
354    ty: Option<Logical>,
355    width: Width,
356}
357
358impl FixedType {
359    /// A plain int or float, with no logical type, or `None` for
360    /// [`Width::Bytes16`]: plain bytes are variable width.
361    pub const fn plain(width: Width) -> Option<Self> {
362        Self::new(None, width)
363    }
364
365    /// `ty` over `width`, normalised, or `None` if the pair has no fixed
366    /// width: undefined (over null), a date, timestamp or decimal over a
367    /// float, a uuid over anything but sixteen bytes, and sixteen bytes
368    /// under anything but a uuid (plain or unknown bytes are variable
369    /// width, so a reader could not tell where their typed null ends).
370    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    /// The type of `v` under `ty`, or `None` if it has none (see
392    /// [`new`](FixedType::new)): what a host that knows the value's type
393    /// but holds no value passes to
394    /// [`Sink::typed_null`](crate::plaintext::Sink::typed_null).
395    pub fn of(ty: Option<Logical>, v: &Physical<'_>) -> Option<Self> {
396        Self::new(ty, Width::of(v)?)
397    }
398
399    /// The logical type, if any.
400    pub const fn logical(self) -> Option<Logical> {
401        self.ty
402    }
403
404    /// The physical width.
405    pub const fn width(self) -> Width {
406        self.width
407    }
408}
409
410// `Physical` and `Float` derive their constant-time equality: the derive
411// compares same-variant payloads with `ts_eq` and treats different
412// variants as unequal, which is the structural equality wanted here. The
413// float impls in `vitaminc_protected` compare by bit pattern.
414//
415// `Int` is written by hand so its equality is one comparison over the
416// sign and the 128-bit word rather than a ten-arm match. The width (the
417// variant) is a host-fidelity hint, not a secret, so it may be compared
418// by branch; the sign and the word are derived without branching on the
419// value (see `Int::sign_bit_and_word`).
420
421impl 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/// What a physical value means. Carried per value, so a parameter such as
437/// a decimal's scale can differ from one value to the next.
438///
439/// Each variant says which physical kind it rides on. A value that does not
440/// fit is refused by [`admits`](Logical::admits).
441#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442#[non_exhaustive]
443pub enum Logical {
444    /// JavaScript's `undefined`. Over null.
445    Undefined,
446    /// Days since 1970-01-01, within chrono's date range. Over int.
447    Date,
448    /// A count of `TimeUnit`s since the Unix epoch, UTC, with no leap
449    /// seconds, within chrono's range. Over int.
450    ///
451    /// chrono's range (years -262143 to 262142) is narrower than a
452    /// JavaScript `Date`'s (about ±275,000 years). It stays the bound
453    /// because today's `TIMESTAMP` decoder is chrono-bound, so a wider
454    /// timestamp could be sealed but not read back there. A host refuses
455    /// a `Date` outside it.
456    Timestamp(TimeUnit),
457    /// `int × 10^-scale`, scale at most 28 and `|int| < 2^96`. Over int.
458    Decimal { scale: u8 },
459    /// Sixteen bytes. Over bytes.
460    Uuid,
461    /// A logical type this build does not know. Structural sinks carry it
462    /// through unchanged; index sinks refuse it.
463    Unknown(UnknownLogical),
464}
465
466/// The unit of a [`Logical::Timestamp`].
467#[derive(Clone, Copy, Debug, PartialEq, Eq)]
468pub enum TimeUnit {
469    Seconds,
470    Millis,
471    Micros,
472    Nanos,
473}
474
475impl TimeUnit {
476    /// Nanoseconds per unit.
477    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/// An id and parameter from the wire that this build has no meaning for.
499#[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
515/// The largest decimal scale `rust_decimal` represents.
516pub const MAX_DECIMAL_SCALE: u8 = 28;
517
518/// Decimal mantissas are 96-bit: `|mantissa| < 2^96`.
519const DECIMAL_MANTISSA_LIMIT: i128 = 1 << 96;
520
521/// chrono's `NaiveDate::MIN` (year -262143) and `NaiveDate::MAX` (year
522/// 262142) as days since 1970-01-01. Pinned against chrono in a test.
523pub const MIN_DATE_DAYS: i32 = -96_465_292;
524pub const MAX_DATE_DAYS: i32 = 95_026_236;
525
526/// chrono's `NaiveDateTime::MIN` and `NaiveDateTime::MAX` as nanoseconds
527/// since the Unix epoch. Pinned against chrono in a test.
528pub 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
533/// The wire ids of the logical types. These are the cross-language
534/// commitment for the logical envelope and must never change; a new type
535/// takes the next free id.
536pub mod id {
537    /// No logical type.
538    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    /// The highest id this build knows. Any id above it is
545    /// [`Logical::Unknown`](super::Logical::Unknown).
546    pub const LAST_KNOWN: u8 = UUID;
547}
548
549/// A logical type this build knows, named without its parameter: what a
550/// schema says a column holds. A [`Logical`] says "a decimal of scale 2"
551/// about one value; a `KnownLogical` says "decimals" about a column, whose
552/// values may each have a scale of their own.
553///
554/// Each one is stored as exactly one [`Kind`], named by
555/// [`kind`](KnownLogical::kind), so
556/// [`LeafTypeAad::logical`](crate::LeafTypeAad::logical) derives the kind
557/// from it and a column with a logical type has one binding. A logical
558/// type this build does not know is an [`UnknownLogicalId`] instead.
559#[derive(Clone, Copy, Debug, PartialEq, Eq)]
560#[non_exhaustive]
561pub enum KnownLogical {
562    /// [`Logical::Undefined`]. Over null.
563    Undefined,
564    /// [`Logical::Date`]. Over int.
565    Date,
566    /// [`Logical::Timestamp`], of any unit. Over int.
567    Timestamp,
568    /// [`Logical::Decimal`], of any scale. Over int.
569    Decimal,
570    /// [`Logical::Uuid`]. Over bytes.
571    Uuid,
572}
573
574impl KnownLogical {
575    /// The logical type's wire id, one of the constants in [`id`].
576    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    /// The physical kind the logical type is stored as, the one kind
587    /// [`Logical::admits`] accepts it over. A cross-language commitment,
588    /// since a schema binds it: never change one.
589    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    /// The known logical type with wire id `id`, or `None` for
598    /// [`id::NONE`] and for an id above [`id::LAST_KNOWN`]. For a schema
599    /// that stores a column's logical type as its id.
600    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/// A logical id above [`id::LAST_KNOWN`]: a logical type from a newer
613/// build, which this one has no meaning for and so cannot say what kind it
614/// is stored as. See [`LeafTypeAad::unknown`](crate::LeafTypeAad::unknown).
615#[derive(Clone, Copy, Debug, PartialEq, Eq)]
616pub struct UnknownLogicalId(u8);
617
618impl UnknownLogicalId {
619    /// `id` as an unknown logical id, or `None` for [`id::NONE`] and for an
620    /// id this build knows, which is a [`KnownLogical`].
621    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    /// The wire id.
630    pub const fn get(self) -> u8 {
631        self.0
632    }
633}
634
635impl Logical {
636    /// The wire id and parameter, for the generic logical envelope.
637    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    /// The logical type for a wire id and parameter. `None` for
649    /// [`id::NONE`], which means "no logical type", and for a known id with
650    /// a parameter it cannot take.
651    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    /// Whether `v` is a valid value of this logical type: the right physical
667    /// kind, and in range.
668    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
687/// A timestamp's count of `unit`s as nanoseconds since the epoch, if it is
688/// in range.
689pub 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/// Conversions between the chrono types and the ints they are stored as.
704#[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    /// The date for a day count, if it is one chrono can represent.
712    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    /// A date's day count since 1970-01-01.
718    pub fn date_days(date: NaiveDate) -> i32 {
719        date.num_days_from_ce() - EPOCH_DAYS_FROM_CE
720    }
721
722    /// The instant for a count of `unit`s, if it is one chrono can
723    /// represent.
724    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    /// An instant as nanoseconds since the epoch. `None` for a leap second,
731    /// which has no count.
732    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    /// Reduce a nanosecond count to `unit`, if it is exact.
738    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/// Conversions between `rust_decimal::Decimal` and its scale and mantissa.
832#[cfg(feature = "rust_decimal")]
833pub mod decimal {
834    use super::Int;
835    use rust_decimal::Decimal;
836
837    /// The decimal for a scale and mantissa, if `rust_decimal` can hold it.
838    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    /// A decimal's scale and mantissa. A negative zero comes out as zero,
843    /// with its scale kept.
844    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        // A tag of its own fixes the width, and a decimal's scale.
912        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        // Through the envelope, the width is the value's own.
924        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        // Plain bytes, even sixteen of them, are variable width.
934        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    /// The `KnownLogical` a logical type is named by. Matches exhaustively,
1016    /// so a new `Logical` variant fails to build until it is paired here.
1017    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    /// `KnownLogical` and `Logical` name the same types by the same ids,
1029    /// and every id this build knows is a `KnownLogical`, so the two cannot
1030    /// drift apart.
1031    #[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            // Every logical type with this id, whatever its parameter, is
1042            // named by the same `KnownLogical`.
1043            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    /// `kind` names the one kind `admits` accepts a known logical type
1056    /// over, so a binding built from it agrees with what opens.
1057    #[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        // The other two edges of the chrono ranges.
1111        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        // Only the variant name is the derive's; the type path `type_name`
1186        // prints before it differs between compiler versions.
1187        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        // `new Date(8.64e15)`: valid in JavaScript, past chrono's range.
1204        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        // A signed -1 sign-extends to every bit set; an unsigned maximum
1224        // zero-extends. Either way only a signed value can be negative.
1225        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        // The top bit is the sign only for a signed value.
1245        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        // Same word, different sign: never equal.
1252        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}