Skip to main content

vitaminc_ore/
order.rs

1//! One order term per scalar, encoded as the index's declared type.
2//!
3//! An order index declares the type of what it holds: an `i32` column,
4//! say, as [`OrderTarget::I32`]. [`order`] turns a physical value and its
5//! optional logical type into the sealed [`OrderPlaintext`] a scheme
6//! encrypts, encoded as that target: bytes whose lexicographic order is
7//! the value's order, and the frozen domain label of the target. The
8//! target, not the value, decides the domain and the width, so values
9//! from every host compare in one space. A JavaScript `BigInt` arrives as
10//! the narrowest integer that holds it, and a Rust field at its own type;
11//! both widen to the target, and a value outside it is refused.
12//!
13//! Where an equality term folds every integer width into one encoding, an
14//! order term keeps the declared width, so block ORE over an `i32` column
15//! costs four blocks, not seventeen. A float, a date, a timestamp, a
16//! decimal and a uuid order by the same bytes they are matched by, so
17//! those come from `vitaminc_normalize` unchanged. Bytes are the equality
18//! encoding behind [`VARIABLE_WIDTH_MARKER`], and text folds further: an
19//! order term drops accents and case, which equality must not. See the
20//! crate docs for the table.
21
22use vitaminc_aead::{Encrypt, Int, Logical, Physical};
23use vitaminc_normalize::{terms_by, Normalize, NormalizeError, Term, TermsError};
24use vitaminc_protected::{Controlled, Protected};
25
26use crate::{fold, OrderPlaintext};
27
28/// The byte every text and bytes order encoding starts with, so that no
29/// order plaintext is ever empty.
30///
31/// An empty string and an empty byte string are values with an order,
32/// and without the marker they encoded to nothing. Both scheme crates
33/// then encrypted them into a term that never touched the key or the
34/// salt: the same term under every key and context, which anyone could
35/// write into any index. A constant leading byte keeps the order of the
36/// encodings, and a fixed-width encoding is never empty, so it goes in
37/// unchanged. The scheme crates added this byte themselves before it
38/// moved here; see the README's Encodings section for why the domain
39/// labels did not change with it.
40pub const VARIABLE_WIDTH_MARKER: u8 = 0x01;
41
42/// Frozen domain labels for order terms, one per [`OrderTarget`].
43pub mod domain {
44    // Retired in 0.7, never to be reused with another meaning:
45    // `vitaminc/ore/value/int8/v1` and `vitaminc/ore/value/uint8/v1`, the
46    // one-byte targets 0.6 published. See `OrderTarget` for why.
47
48    /// An `i16`, two bytes. The narrowest integer target: see
49    /// [`OrderTarget`](super::OrderTarget) for why there is no 8-bit one.
50    pub const INT16: &str = "vitaminc/ore/value/int16/v1";
51    /// A `u16`, two bytes.
52    pub const UINT16: &str = "vitaminc/ore/value/uint16/v1";
53    /// An `i32`, four bytes.
54    pub const INT32: &str = "vitaminc/ore/value/int32/v1";
55    /// A `u32`, four bytes.
56    pub const UINT32: &str = "vitaminc/ore/value/uint32/v1";
57    /// An `i64`, eight bytes.
58    pub const INT64: &str = "vitaminc/ore/value/int64/v1";
59    /// A `u64`, eight bytes.
60    pub const UINT64: &str = "vitaminc/ore/value/uint64/v1";
61    /// An `i128`, sixteen bytes.
62    pub const INT128: &str = "vitaminc/ore/value/int128/v1";
63    /// A `u128`, sixteen bytes.
64    pub const UINT128: &str = "vitaminc/ore/value/uint128/v1";
65    /// An `f64`, eight bytes, zero signs and NaN payloads folded.
66    pub const FLOAT64: &str = "vitaminc/ore/value/float64/v1";
67    /// A date, four bytes, over its day count.
68    pub const DATE: &str = "vitaminc/ore/value/date/v1";
69    /// A timestamp, twelve bytes, truncated to microseconds.
70    pub const TIMESTAMP: &str = "vitaminc/ore/value/timestamp-micros/v1";
71    /// A decimal, fourteen bytes, scale and signed zero normalized.
72    pub const DECIMAL: &str = "vitaminc/ore/value/decimal/v1";
73    /// Text: [`VARIABLE_WIDTH_MARKER`](super::VARIABLE_WIDTH_MARKER), then
74    /// Unicode 16 NFC with accents and case folded.
75    pub const TEXT: &str = "vitaminc/ore/value/text-folded/unicode-16/v1";
76    /// Bytes: [`VARIABLE_WIDTH_MARKER`](super::VARIABLE_WIDTH_MARKER), then
77    /// the bytes as they are.
78    pub const BYTES: &str = "vitaminc/ore/value/bytes/v1";
79    /// A uuid, over its sixteen raw bytes.
80    pub const UUID: &str = "vitaminc/ore/value/uuid/v1";
81
82    // A space that places nulls is a space of its own, with a label of its
83    // own: it admits other values than the plain space and gives a meaning
84    // to a code the plain space leaves unused. One pair per fixed-width
85    // target; text and bytes take no null policy.
86
87    /// An `i16` in a space that places nulls first: see
88    /// [`NullOrder::First`](super::NullOrder::First).
89    pub const INT16_NULLS_FIRST: &str = "vitaminc/ore/value/int16/nulls-first/v1";
90    /// An `i16` in a space that places nulls last: see
91    /// [`NullOrder::Last`](super::NullOrder::Last).
92    pub const INT16_NULLS_LAST: &str = "vitaminc/ore/value/int16/nulls-last/v1";
93    /// A `u16` in a space that places nulls first: see
94    /// [`NullOrder::First`](super::NullOrder::First).
95    pub const UINT16_NULLS_FIRST: &str = "vitaminc/ore/value/uint16/nulls-first/v1";
96    /// A `u16` in a space that places nulls last: see
97    /// [`NullOrder::Last`](super::NullOrder::Last).
98    pub const UINT16_NULLS_LAST: &str = "vitaminc/ore/value/uint16/nulls-last/v1";
99    /// An `i32` in a space that places nulls first: see
100    /// [`NullOrder::First`](super::NullOrder::First).
101    pub const INT32_NULLS_FIRST: &str = "vitaminc/ore/value/int32/nulls-first/v1";
102    /// An `i32` in a space that places nulls last: see
103    /// [`NullOrder::Last`](super::NullOrder::Last).
104    pub const INT32_NULLS_LAST: &str = "vitaminc/ore/value/int32/nulls-last/v1";
105    /// A `u32` in a space that places nulls first: see
106    /// [`NullOrder::First`](super::NullOrder::First).
107    pub const UINT32_NULLS_FIRST: &str = "vitaminc/ore/value/uint32/nulls-first/v1";
108    /// A `u32` in a space that places nulls last: see
109    /// [`NullOrder::Last`](super::NullOrder::Last).
110    pub const UINT32_NULLS_LAST: &str = "vitaminc/ore/value/uint32/nulls-last/v1";
111    /// An `i64` in a space that places nulls first: see
112    /// [`NullOrder::First`](super::NullOrder::First).
113    pub const INT64_NULLS_FIRST: &str = "vitaminc/ore/value/int64/nulls-first/v1";
114    /// An `i64` in a space that places nulls last: see
115    /// [`NullOrder::Last`](super::NullOrder::Last).
116    pub const INT64_NULLS_LAST: &str = "vitaminc/ore/value/int64/nulls-last/v1";
117    /// A `u64` in a space that places nulls first: see
118    /// [`NullOrder::First`](super::NullOrder::First).
119    pub const UINT64_NULLS_FIRST: &str = "vitaminc/ore/value/uint64/nulls-first/v1";
120    /// A `u64` in a space that places nulls last: see
121    /// [`NullOrder::Last`](super::NullOrder::Last).
122    pub const UINT64_NULLS_LAST: &str = "vitaminc/ore/value/uint64/nulls-last/v1";
123    /// An `i128` in a space that places nulls first: see
124    /// [`NullOrder::First`](super::NullOrder::First).
125    pub const INT128_NULLS_FIRST: &str = "vitaminc/ore/value/int128/nulls-first/v1";
126    /// An `i128` in a space that places nulls last: see
127    /// [`NullOrder::Last`](super::NullOrder::Last).
128    pub const INT128_NULLS_LAST: &str = "vitaminc/ore/value/int128/nulls-last/v1";
129    /// A `u128` in a space that places nulls first: see
130    /// [`NullOrder::First`](super::NullOrder::First).
131    pub const UINT128_NULLS_FIRST: &str = "vitaminc/ore/value/uint128/nulls-first/v1";
132    /// A `u128` in a space that places nulls last: see
133    /// [`NullOrder::Last`](super::NullOrder::Last).
134    pub const UINT128_NULLS_LAST: &str = "vitaminc/ore/value/uint128/nulls-last/v1";
135    /// An `f64` in a space that places nulls first: see
136    /// [`NullOrder::First`](super::NullOrder::First).
137    pub const FLOAT64_NULLS_FIRST: &str = "vitaminc/ore/value/float64/nulls-first/v1";
138    /// An `f64` in a space that places nulls last: see
139    /// [`NullOrder::Last`](super::NullOrder::Last).
140    pub const FLOAT64_NULLS_LAST: &str = "vitaminc/ore/value/float64/nulls-last/v1";
141    /// A date in a space that places nulls first: see
142    /// [`NullOrder::First`](super::NullOrder::First).
143    pub const DATE_NULLS_FIRST: &str = "vitaminc/ore/value/date/nulls-first/v1";
144    /// A date in a space that places nulls last: see
145    /// [`NullOrder::Last`](super::NullOrder::Last).
146    pub const DATE_NULLS_LAST: &str = "vitaminc/ore/value/date/nulls-last/v1";
147    /// A timestamp in a space that places nulls first: see
148    /// [`NullOrder::First`](super::NullOrder::First).
149    pub const TIMESTAMP_NULLS_FIRST: &str = "vitaminc/ore/value/timestamp-micros/nulls-first/v1";
150    /// A timestamp in a space that places nulls last: see
151    /// [`NullOrder::Last`](super::NullOrder::Last).
152    pub const TIMESTAMP_NULLS_LAST: &str = "vitaminc/ore/value/timestamp-micros/nulls-last/v1";
153    /// A decimal in a space that places nulls first: see
154    /// [`NullOrder::First`](super::NullOrder::First).
155    pub const DECIMAL_NULLS_FIRST: &str = "vitaminc/ore/value/decimal/nulls-first/v1";
156    /// A decimal in a space that places nulls last: see
157    /// [`NullOrder::Last`](super::NullOrder::Last).
158    pub const DECIMAL_NULLS_LAST: &str = "vitaminc/ore/value/decimal/nulls-last/v1";
159    /// A uuid in a space that places nulls first: see
160    /// [`NullOrder::First`](super::NullOrder::First).
161    pub const UUID_NULLS_FIRST: &str = "vitaminc/ore/value/uuid/nulls-first/v1";
162    /// A uuid in a space that places nulls last: see
163    /// [`NullOrder::Last`](super::NullOrder::Last).
164    pub const UUID_NULLS_LAST: &str = "vitaminc/ore/value/uuid/nulls-last/v1";
165
166    /// Every domain, for a test or an index layer that enumerates them: the
167    /// fifteen plain labels in [`OrderTarget::ALL`](super::OrderTarget::ALL)
168    /// order, then the thirteen nulls-first labels and the thirteen
169    /// nulls-last labels, each in the same order. A slice, so a domain
170    /// added later does not change its type.
171    pub const ALL: &[&str] = &[
172        INT16,
173        UINT16,
174        INT32,
175        UINT32,
176        INT64,
177        UINT64,
178        INT128,
179        UINT128,
180        FLOAT64,
181        DATE,
182        TIMESTAMP,
183        DECIMAL,
184        TEXT,
185        BYTES,
186        UUID,
187        INT16_NULLS_FIRST,
188        UINT16_NULLS_FIRST,
189        INT32_NULLS_FIRST,
190        UINT32_NULLS_FIRST,
191        INT64_NULLS_FIRST,
192        UINT64_NULLS_FIRST,
193        INT128_NULLS_FIRST,
194        UINT128_NULLS_FIRST,
195        FLOAT64_NULLS_FIRST,
196        DATE_NULLS_FIRST,
197        TIMESTAMP_NULLS_FIRST,
198        DECIMAL_NULLS_FIRST,
199        UUID_NULLS_FIRST,
200        INT16_NULLS_LAST,
201        UINT16_NULLS_LAST,
202        INT32_NULLS_LAST,
203        UINT32_NULLS_LAST,
204        INT64_NULLS_LAST,
205        UINT64_NULLS_LAST,
206        INT128_NULLS_LAST,
207        UINT128_NULLS_LAST,
208        FLOAT64_NULLS_LAST,
209        DATE_NULLS_LAST,
210        TIMESTAMP_NULLS_LAST,
211        DECIMAL_NULLS_LAST,
212        UUID_NULLS_LAST,
213    ];
214}
215
216/// Where the nulls of an order space sort: below every value or above
217/// every value, as SQL's `NULLS FIRST` and `NULLS LAST`.
218///
219/// A space takes a policy when it is made, like its target and context,
220/// and every term of it agrees. This is the policy as a value, for a
221/// target chosen while the program runs:
222/// `scheme.target(OrderTarget::I32).nulls(NullOrder::Last)`. On a marker
223/// target the policy is a marker too, [`First`](crate::target::First) or
224/// [`Last`](crate::target::Last), each standing for one of these through
225/// [`NullPolicy`](crate::target::NullPolicy):
226/// `scheme.target(I32).nulls(Last)`. Without one, a null has no order term, as before. With one, every null
227/// in the space, typed or plain, encodes to the space's sentinel: the
228/// lowest code the target's width holds (all `0x00`) for
229/// [`First`](Self::First), the highest (all `0xFF`) for
230/// [`Last`](Self::Last). Only a fixed-width target takes a policy.
231///
232/// For an integer or uuid target the policy costs one value: the target's
233/// maximum, whose code the nulls-last sentinel already is, is refused with
234/// [`NormalizeError::Reserved`]. Under nulls-first every code moves up by
235/// one, so zero, the minimum and the nil uuid stay encodable and the
236/// maximum is again the one refused. A column that places nulls cannot
237/// hold its type's maximum. Float, date, timestamp and decimal codes never
238/// reach either extreme, so those targets shift nothing and refuse
239/// nothing.
240///
241/// Each policy is its own set of domain labels, and the salt binds the
242/// label, so a term of a space that places nulls never compares with a
243/// term of a plain space of the same target.
244///
245/// # What a placed null reveals
246///
247/// In any order index whose stored terms compare with each other, the
248/// nulls are visible as the tied group at one end, and in a sparse domain
249/// as the group that differs from every value at the top of the encoding.
250/// That is never more than the SQL `NULL` it replaces, which needs no
251/// inference to read.
252///
253/// # Examples
254///
255/// ```
256/// use vitaminc_ore::{domain, order_with_nulls, Int, NormalizeError, NullOrder, OrderTarget, Physical};
257/// use vitaminc_protected::Controlled;
258///
259/// let null = order_with_nulls(OrderTarget::U16, NullOrder::First, None, Physical::Null)?;
260/// assert_eq!(null.domain(), domain::UINT16_NULLS_FIRST);
261/// assert_eq!(null.bytes().risky_ref(), &[0x00, 0x00]);
262///
263/// // Zero moves up by one, so it stays above the null.
264/// let zero = order_with_nulls(OrderTarget::U16, NullOrder::First, None, Physical::Int(Int::U16(0)))?;
265/// assert_eq!(zero.bytes().risky_ref(), &[0x00, 0x01]);
266///
267/// // The maximum is the one value refused.
268/// let max = Physical::Int(Int::U16(u16::MAX));
269/// assert_eq!(
270///     order_with_nulls(OrderTarget::U16, NullOrder::First, None, max).err(),
271///     Some(NormalizeError::Reserved)
272/// );
273/// # Ok::<(), NormalizeError>(())
274/// ```
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
276#[non_exhaustive]
277pub enum NullOrder {
278    /// Nulls sort below every value, as `NULLS FIRST`.
279    First,
280    /// Nulls sort above every value, as `NULLS LAST`.
281    Last,
282}
283
284/// The type an order index declares, which every value it holds is
285/// encoded as. A layer above maps its own column types onto these: an
286/// EQL `int` column is [`OrderTarget::I32`], whatever language the value
287/// comes from.
288///
289/// An integer of any width is accepted by an integer target it fits,
290/// and refused with [`NormalizeError::OutOfRange`] otherwise. An `f32` is
291/// widened exactly to [`OrderTarget::F64`]. Every other target takes only
292/// its own kind; anything else is [`NormalizeError::Mismatch`]. An
293/// integer is not a float, a date or a decimal, so none of those take
294/// one.
295///
296/// # What has no target
297///
298/// An order term reveals the order of the values it encrypts. That is
299/// the point of the scheme, and it is safe only when the set of possible
300/// values is large enough that knowing the order of the rows does not
301/// give the values away. A boolean has two values, so an order term over
302/// a boolean column says which rows are `true`: [`order`] refuses one
303/// with [`NormalizeError::NoDomain`]. An 8-bit integer has 256, so an
304/// order index over such a column is a frequency table of the column with
305/// the order on top, and anyone who knows a handful of rows' values, or
306/// the rough distribution of the data (ages, quantities, ratings), reads
307/// the rest off. So the narrowest integer target is sixteen bits. An
308/// `i8` or `u8` **value** still goes into any wider target it fits; what
309/// there is not is a one-byte space.
310///
311/// The floor removes only the case where the *type* guarantees few
312/// values. An `I16` or `I32` column that holds ages or ratings leaks the
313/// same frequency table, since the leak is a property of how many
314/// distinct values the column holds, not of the width. Whether a column
315/// should have an order index at all is a question about its data; a
316/// column of few distinct values wants an equality index.
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
318#[non_exhaustive]
319pub enum OrderTarget {
320    /// An `i16`. The narrowest integer target; see above for why.
321    I16,
322    /// A `u16`.
323    U16,
324    /// An `i32`.
325    I32,
326    /// A `u32`.
327    U32,
328    /// An `i64`.
329    I64,
330    /// A `u64`.
331    U64,
332    /// An `i128`.
333    I128,
334    /// A `u128`.
335    U128,
336    /// An `f64`; an `f32` widens to it exactly.
337    F64,
338    /// A [`Logical::Date`].
339    Date,
340    /// A [`Logical::Timestamp`] in any unit, truncated to microseconds.
341    Timestamp,
342    /// A [`Logical::Decimal`] at any scale.
343    Decimal,
344    /// Text.
345    Text,
346    /// Bytes.
347    Bytes,
348    /// A [`Logical::Uuid`].
349    Uuid,
350}
351
352impl OrderTarget {
353    /// Every target, for a test or an index layer that enumerates them.
354    pub const ALL: &[OrderTarget] = &[
355        Self::I16,
356        Self::U16,
357        Self::I32,
358        Self::U32,
359        Self::I64,
360        Self::U64,
361        Self::I128,
362        Self::U128,
363        Self::F64,
364        Self::Date,
365        Self::Timestamp,
366        Self::Decimal,
367        Self::Text,
368        Self::Bytes,
369        Self::Uuid,
370    ];
371
372    /// The frozen domain label of every term of this target.
373    pub fn domain(self) -> &'static str {
374        match self {
375            Self::I16 => domain::INT16,
376            Self::U16 => domain::UINT16,
377            Self::I32 => domain::INT32,
378            Self::U32 => domain::UINT32,
379            Self::I64 => domain::INT64,
380            Self::U64 => domain::UINT64,
381            Self::I128 => domain::INT128,
382            Self::U128 => domain::UINT128,
383            Self::F64 => domain::FLOAT64,
384            Self::Date => domain::DATE,
385            Self::Timestamp => domain::TIMESTAMP,
386            Self::Decimal => domain::DECIMAL,
387            Self::Text => domain::TEXT,
388            Self::Bytes => domain::BYTES,
389            Self::Uuid => domain::UUID,
390        }
391    }
392
393    /// The frozen domain label of every term of this target in a space
394    /// that places nulls as `nulls` says, or `None` for text and bytes,
395    /// which take no null policy.
396    ///
397    /// ```
398    /// use vitaminc_ore::{domain, NullOrder, OrderTarget};
399    ///
400    /// assert_eq!(
401    ///     OrderTarget::I32.domain_with_nulls(NullOrder::Last),
402    ///     Some(domain::INT32_NULLS_LAST)
403    /// );
404    /// assert_eq!(OrderTarget::Text.domain_with_nulls(NullOrder::Last), None);
405    /// ```
406    pub fn domain_with_nulls(self, nulls: NullOrder) -> Option<&'static str> {
407        Some(match (self, nulls) {
408            (Self::I16, NullOrder::First) => domain::INT16_NULLS_FIRST,
409            (Self::I16, NullOrder::Last) => domain::INT16_NULLS_LAST,
410            (Self::U16, NullOrder::First) => domain::UINT16_NULLS_FIRST,
411            (Self::U16, NullOrder::Last) => domain::UINT16_NULLS_LAST,
412            (Self::I32, NullOrder::First) => domain::INT32_NULLS_FIRST,
413            (Self::I32, NullOrder::Last) => domain::INT32_NULLS_LAST,
414            (Self::U32, NullOrder::First) => domain::UINT32_NULLS_FIRST,
415            (Self::U32, NullOrder::Last) => domain::UINT32_NULLS_LAST,
416            (Self::I64, NullOrder::First) => domain::INT64_NULLS_FIRST,
417            (Self::I64, NullOrder::Last) => domain::INT64_NULLS_LAST,
418            (Self::U64, NullOrder::First) => domain::UINT64_NULLS_FIRST,
419            (Self::U64, NullOrder::Last) => domain::UINT64_NULLS_LAST,
420            (Self::I128, NullOrder::First) => domain::INT128_NULLS_FIRST,
421            (Self::I128, NullOrder::Last) => domain::INT128_NULLS_LAST,
422            (Self::U128, NullOrder::First) => domain::UINT128_NULLS_FIRST,
423            (Self::U128, NullOrder::Last) => domain::UINT128_NULLS_LAST,
424            (Self::F64, NullOrder::First) => domain::FLOAT64_NULLS_FIRST,
425            (Self::F64, NullOrder::Last) => domain::FLOAT64_NULLS_LAST,
426            (Self::Date, NullOrder::First) => domain::DATE_NULLS_FIRST,
427            (Self::Date, NullOrder::Last) => domain::DATE_NULLS_LAST,
428            (Self::Timestamp, NullOrder::First) => domain::TIMESTAMP_NULLS_FIRST,
429            (Self::Timestamp, NullOrder::Last) => domain::TIMESTAMP_NULLS_LAST,
430            (Self::Decimal, NullOrder::First) => domain::DECIMAL_NULLS_FIRST,
431            (Self::Decimal, NullOrder::Last) => domain::DECIMAL_NULLS_LAST,
432            (Self::Uuid, NullOrder::First) => domain::UUID_NULLS_FIRST,
433            (Self::Uuid, NullOrder::Last) => domain::UUID_NULLS_LAST,
434            (Self::Text | Self::Bytes, _) => return None,
435        })
436    }
437
438    /// The width of every term of this target, or `None` for text and
439    /// bytes, whose terms vary in length. Never one byte: see the type's
440    /// docs.
441    pub fn width(self) -> Option<usize> {
442        match self {
443            Self::I16 | Self::U16 => Some(2),
444            Self::I32 | Self::U32 | Self::Date => Some(4),
445            Self::I64 | Self::U64 | Self::F64 => Some(8),
446            Self::Timestamp => Some(12),
447            Self::Decimal => Some(14),
448            Self::I128 | Self::U128 | Self::Uuid => Some(16),
449            Self::Text | Self::Bytes => None,
450        }
451    }
452}
453
454/// Encode one scalar as `target`, sealed with the target's domain. The one
455/// place a value meets the data model.
456///
457/// An integer is converted to the target's width, then encoded
458/// big-endian, with the sign bit flipped when signed, so the bytes sort
459/// as the numbers do. A float, a date, a timestamp, a decimal and a uuid
460/// encode exactly as [`vitaminc_normalize::normalize`] encodes them for
461/// equality. Bytes are the equality encoding behind
462/// [`VARIABLE_WIDTH_MARKER`], and text is the marker, then Unicode 16 NFC
463/// with accents and case folded, so `É` and `e` tie. The marker keeps a
464/// variable-width encoding from ever being empty.
465///
466/// # Examples
467///
468/// ```
469/// use vitaminc_ore::{domain, order, Int, NormalizeError, OrderTarget, Physical};
470///
471/// // Every integer width widens to the target.
472/// let small = order(OrderTarget::I32, None, Physical::Int(Int::I8(100)))?;
473/// let large = order(OrderTarget::I32, None, Physical::Int(Int::U64(300)))?;
474/// assert_eq!(small.domain(), domain::INT32);
475/// assert_eq!(large.domain(), domain::INT32);
476///
477/// // A value the target cannot hold is refused, not truncated.
478/// let big = Physical::Int(Int::I64(1 << 40));
479/// assert_eq!(order(OrderTarget::I32, None, big).err(), Some(NormalizeError::OutOfRange));
480/// assert_eq!(
481///     order(OrderTarget::I32, None, Physical::Text("1")).err(),
482///     Some(NormalizeError::Mismatch)
483/// );
484/// # Ok::<(), NormalizeError>(())
485/// ```
486///
487/// This is the plain space, which places no nulls: a null has no term
488/// here. [`order_with_nulls`] encodes for a space that places them.
489///
490/// # Errors
491///
492/// [`NormalizeError::NoDomain`] for a null, a boolean (see [`OrderTarget`]
493/// for why), an `undefined` or a logical type this build does not know;
494/// [`NormalizeError::Invalid`] for a value that does not fit its own
495/// logical type;
496/// [`NormalizeError::Mismatch`] for a value of another kind than
497/// `target`; [`NormalizeError::OutOfRange`] for an integer outside
498/// `target`'s width; [`NormalizeError::UnassignedCodePoint`] for text
499/// holding a code point Unicode 16 does not assign.
500pub fn order(
501    target: OrderTarget,
502    ty: Option<Logical>,
503    v: Physical<'_>,
504) -> Result<OrderPlaintext, NormalizeError> {
505    encode(target, None, ty, v)
506}
507
508/// Encode one scalar as `target` in a space that places nulls as `nulls`
509/// says, sealed with that space's domain: [`order`] for a space with a
510/// [`NullOrder`].
511///
512/// A null, typed or plain, is the space's sentinel: all `0x00` for
513/// [`NullOrder::First`], all `0xFF` for [`NullOrder::Last`], at the
514/// target's width. A value encodes as in [`order`], except that under
515/// nulls-first an integer's or a uuid's code is one higher, as a
516/// big-endian number over the target's width, so the lowest code is free
517/// for the sentinel. See [`NullOrder`] for the whole rule.
518///
519/// # Examples
520///
521/// ```
522/// use vitaminc_ore::{domain, order_with_nulls, Int, NormalizeError, NullOrder, OrderTarget, Physical};
523/// use vitaminc_protected::Controlled;
524///
525/// let null = order_with_nulls(OrderTarget::I16, NullOrder::Last, None, Physical::Null)?;
526/// assert_eq!(null.domain(), domain::INT16_NULLS_LAST);
527/// assert_eq!(null.bytes().risky_ref(), &[0xFF, 0xFF]);
528/// assert_eq!(
529///     order_with_nulls(OrderTarget::I16, NullOrder::Last, None, Physical::Int(Int::I16(i16::MAX))).err(),
530///     Some(NormalizeError::Reserved)
531/// );
532/// // Text takes no null policy.
533/// assert_eq!(
534///     order_with_nulls(OrderTarget::Text, NullOrder::Last, None, Physical::Text("a")).err(),
535///     Some(NormalizeError::NoDomain)
536/// );
537/// # Ok::<(), NormalizeError>(())
538/// ```
539///
540/// # Errors
541///
542/// As [`order`], except that a null is not refused, and:
543/// [`NormalizeError::NoDomain`] for every value when `target` is text or
544/// bytes, which take no null policy; [`NormalizeError::Reserved`] for the
545/// maximum of an integer or uuid target, whose code is the nulls-last
546/// sentinel or would overflow after the nulls-first shift. An `undefined`
547/// is still [`NormalizeError::NoDomain`]: it is not a value and not a
548/// null.
549pub fn order_with_nulls(
550    target: OrderTarget,
551    nulls: NullOrder,
552    ty: Option<Logical>,
553    v: Physical<'_>,
554) -> Result<OrderPlaintext, NormalizeError> {
555    encode(target, Some(nulls), ty, v)
556}
557
558/// The domain label of a space: the target's own, or the one its null
559/// policy names. `None` for a null policy on text or bytes.
560pub(crate) fn label(target: OrderTarget, nulls: Option<NullOrder>) -> Option<&'static str> {
561    match nulls {
562        None => Some(target.domain()),
563        Some(nulls) => target.domain_with_nulls(nulls),
564    }
565}
566
567/// The sealed sentinel of a space that places nulls: every null of it.
568/// `None` for text and bytes, which take no policy.
569pub(crate) fn null_plaintext(target: OrderTarget, nulls: NullOrder) -> Option<OrderPlaintext> {
570    let domain = target.domain_with_nulls(nulls)?;
571    let width = target.width()?;
572    Some(OrderPlaintext::new(
573        target,
574        Some(nulls),
575        domain,
576        sentinel(width, nulls),
577    ))
578}
579
580/// [`order`] and [`order_with_nulls`]: one scalar as `target`, in the plain
581/// space when `nulls` is `None`.
582pub(crate) fn encode(
583    target: OrderTarget,
584    nulls: Option<NullOrder>,
585    ty: Option<Logical>,
586    v: Physical<'_>,
587) -> Result<OrderPlaintext, NormalizeError> {
588    use OrderTarget as T;
589    // As `normalize`: a value that does not fit its logical type is refused
590    // before the target is looked at.
591    if ty.is_some_and(|ty| !ty.admits(&v)) {
592        return Err(NormalizeError::Invalid);
593    }
594    // A null policy on text or bytes has no label, so no value has a term
595    // there.
596    let domain = label(target, nulls).ok_or(NormalizeError::NoDomain)?;
597    let bytes = match (target, ty, v) {
598        // A null is the sentinel of a space that places nulls, and has no
599        // order in one that does not, where a walk skips it.
600        (_, None, Physical::Null) => {
601            return match nulls.and_then(|nulls| null_plaintext(target, nulls)) {
602                Some(sentinel) => Ok(sentinel),
603                None => Err(NormalizeError::NoDomain),
604            }
605        }
606        // A boolean or an `undefined` has no order, whatever the target or
607        // the null policy, and a walk skips it.
608        (_, None, Physical::Bool(_)) | (_, Some(Logical::Undefined), _) => {
609            return Err(NormalizeError::NoDomain)
610        }
611        (_, Some(Logical::Unknown(_)), _) => return Err(NormalizeError::NoDomain),
612        (target, None, Physical::Int(i)) if target.is_int() => {
613            int(convert(target, i).ok_or(NormalizeError::OutOfRange)?)
614        }
615        (T::F64, None, Physical::Float(f)) => vitaminc_normalize::float(f),
616        (T::Text, None, Physical::Text(s)) => text(s)?,
617        (T::Bytes, None, Physical::Bytes(b)) => variable_width(b),
618        (T::Date, Some(Logical::Date), Physical::Int(days)) => {
619            vitaminc_normalize::date(days).ok_or(NormalizeError::Invalid)?
620        }
621        (T::Timestamp, Some(Logical::Timestamp(unit)), Physical::Int(count)) => {
622            vitaminc_normalize::timestamp(unit, count).ok_or(NormalizeError::Invalid)?
623        }
624        (T::Decimal, Some(Logical::Decimal { scale }), Physical::Int(mantissa)) => {
625            vitaminc_normalize::decimal(scale, mantissa).ok_or(NormalizeError::Invalid)?
626        }
627        (T::Uuid, Some(Logical::Uuid), Physical::Bytes(b)) => {
628            vitaminc_normalize::uuid(b).ok_or(NormalizeError::Invalid)?
629        }
630        // Another kind than the target's, including a logical type added
631        // to the data model later: it has no term here until it is given
632        // a target.
633        _ => return Err(NormalizeError::Mismatch),
634    };
635    let bytes = match nulls {
636        None => bytes,
637        Some(nulls) => make_room(target, nulls, bytes)?,
638    };
639    Ok(OrderPlaintext::new(target, nulls, domain, bytes))
640}
641
642/// The sentinel of a space that places nulls: the extreme code of the
643/// target's width, all `0x00` for nulls-first and all `0xFF` for
644/// nulls-last, in one protected buffer allocated at its final size.
645fn sentinel(width: usize, nulls: NullOrder) -> Protected<Vec<u8>> {
646    let fill = match nulls {
647        NullOrder::First => 0x00,
648        NullOrder::Last => 0xFF,
649    };
650    Protected::new(vec![fill; width])
651}
652
653/// A value's code in a space that places nulls, so that no value is the
654/// sentinel.
655///
656/// An integer or a uuid owns every code of its width, so the maximum,
657/// whose code is all `0xFF`, is refused under both policies, and under
658/// nulls-first every other code moves up by one to free all `0x00`. A
659/// float, a date, a timestamp and a decimal never reach either extreme
660/// (the tests walk each encoder's range), so their codes stay as they
661/// are; a code that did reach the sentinel is refused rather than tied
662/// with the nulls.
663fn make_room(
664    target: OrderTarget,
665    nulls: NullOrder,
666    mut code: Protected<Vec<u8>>,
667) -> Result<Protected<Vec<u8>>, NormalizeError> {
668    if target.owns_every_code() {
669        if every_byte_is(code.risky_ref(), 0xFF) {
670            return Err(NormalizeError::Reserved);
671        }
672        if nulls == NullOrder::First {
673            code.update(|code| increment(code));
674        }
675        return Ok(code);
676    }
677    let reserved = match nulls {
678        NullOrder::First => 0x00,
679        NullOrder::Last => 0xFF,
680    };
681    if every_byte_is(code.risky_ref(), reserved) {
682        return Err(NormalizeError::Reserved);
683    }
684    Ok(code)
685}
686
687/// Whether every byte of `code` is `byte`. Reads every byte whatever it
688/// finds, so the time taken does not depend on how many leading bytes of
689/// a value's code match, as the fixed-width encoders take the same time
690/// for every value. Only the answer, which the caller reports, says
691/// anything about the value.
692fn every_byte_is(code: &[u8], byte: u8) -> bool {
693    code.iter().fold(0u8, |differ, b| differ | (b ^ byte)) == 0
694}
695
696/// Add one to `code` as a big-endian unsigned number, in place. The caller
697/// has refused all `0xFF`, so it never overflows.
698fn increment(code: &mut [u8]) {
699    let mut carry = 1u8;
700    for byte in code.iter_mut().rev() {
701        let (sum, over) = byte.overflowing_add(carry);
702        *byte = sum;
703        carry = u8::from(over);
704    }
705}
706
707impl OrderTarget {
708    /// Whether every code of the target's width is some value's: an
709    /// integer or a uuid. Such a target gives up its maximum to a null
710    /// policy.
711    fn owns_every_code(self) -> bool {
712        self.is_int() || self == Self::Uuid
713    }
714
715    /// Whether this is an integer target.
716    fn is_int(self) -> bool {
717        matches!(
718            self,
719            Self::I16
720                | Self::U16
721                | Self::I32
722                | Self::U32
723                | Self::I64
724                | Self::U64
725                | Self::I128
726                | Self::U128
727        )
728    }
729}
730
731/// `value` at an integer target's width, or `None` if it does not fit.
732/// Every width converts through `i128`, except a `u128` above
733/// `i128::MAX`, which only a `u128` target holds.
734fn convert(target: OrderTarget, value: Int) -> Option<Int> {
735    let Some(v) = value.to_i128() else {
736        return match (target, value) {
737            (OrderTarget::U128, Int::U128(v)) => Some(Int::U128(v)),
738            _ => None,
739        };
740    };
741    match target {
742        OrderTarget::I16 => i16::try_from(v).ok().map(Int::I16),
743        OrderTarget::U16 => u16::try_from(v).ok().map(Int::U16),
744        OrderTarget::I32 => i32::try_from(v).ok().map(Int::I32),
745        OrderTarget::U32 => u32::try_from(v).ok().map(Int::U32),
746        OrderTarget::I64 => i64::try_from(v).ok().map(Int::I64),
747        OrderTarget::U64 => u64::try_from(v).ok().map(Int::U64),
748        OrderTarget::I128 => Some(Int::I128(v)),
749        OrderTarget::U128 => u128::try_from(v).ok().map(Int::U128),
750        _ => None,
751    }
752}
753
754/// An integer at its own width: big-endian, with the sign bit flipped for
755/// a signed width, so two's complement sorts as the numbers do. The same
756/// bytes `orderable-bytes` produces, checked against it in the tests.
757fn int(value: Int) -> Protected<Vec<u8>> {
758    match value {
759        // `convert` widens every 8-bit value to its target, so no
760        // one-byte encoding is ever made: there is no 8-bit target.
761        Int::I8(_) | Int::U8(_) => unreachable!("convert widens every 8-bit value to its target"),
762        Int::I16(v) => fixed(|b| *b = ((v as u16) ^ (1 << 15)).to_be_bytes()),
763        Int::U16(v) => fixed(|b| *b = v.to_be_bytes()),
764        Int::I32(v) => fixed(|b| *b = ((v as u32) ^ (1 << 31)).to_be_bytes()),
765        Int::U32(v) => fixed(|b| *b = v.to_be_bytes()),
766        Int::I64(v) => fixed(|b| *b = ((v as u64) ^ (1 << 63)).to_be_bytes()),
767        Int::U64(v) => fixed(|b| *b = v.to_be_bytes()),
768        Int::I128(v) => fixed(|b| *b = ((v as u128) ^ (1 << 127)).to_be_bytes()),
769        Int::U128(v) => fixed(|b| *b = v.to_be_bytes()),
770    }
771}
772
773/// The text order encoding: [`VARIABLE_WIDTH_MARKER`], then `text` as
774/// Unicode 16 NFC with accents and case folded. See the crate docs for
775/// the exact steps.
776///
777/// # Errors
778///
779/// [`NormalizeError::UnassignedCodePoint`] if `text` holds a code point
780/// Unicode 16 does not assign: its folding is not known.
781pub fn text(text: &str) -> Result<Protected<Vec<u8>>, NormalizeError> {
782    let folded = fold::folded(text)?;
783    Ok(variable_width(folded.risky_ref()))
784    // `folded` drops here and is wiped.
785}
786
787/// A variable-width encoding: the marker, then `bytes`, in one protected
788/// buffer sized up front so it never reallocates and frees an unwiped
789/// partial copy. Never empty.
790fn variable_width(bytes: &[u8]) -> Protected<Vec<u8>> {
791    let mut out = Protected::new(Vec::with_capacity(bytes.len() + 1));
792    out.risky_inner_mut().push(VARIABLE_WIDTH_MARKER);
793    out.risky_inner_mut().extend_from_slice(bytes);
794    out
795}
796
797/// A fixed-width encoding, written by `write` straight into the protected
798/// vector that is returned.
799///
800/// The vector is allocated once at its final size, so it never
801/// reallocates, and `write` fills it in place. No separate array of the
802/// encoding is made and copied, so there is no extra copy to wipe. The
803/// compiler can still make short-lived temporaries while `write` runs, for
804/// example the array that `to_be_bytes` returns, and Rust cannot promise
805/// to wipe those.
806fn fixed<const N: usize>(mut write: impl FnMut(&mut [u8; N])) -> Protected<Vec<u8>> {
807    let mut bytes = Protected::new(vec![0u8; N]);
808    bytes.update(|bytes| {
809        let array = <&mut [u8; N]>::try_from(bytes.as_mut_slice())
810            .expect("the vector was made with exactly N bytes");
811        write(array);
812    });
813    bytes
814}
815
816/// The order normalizer, for [`vitaminc_normalize::Terms::by`]: [`order`]
817/// at one target, whose terms are sealed [`OrderPlaintext`]s. Every scalar
818/// of the walk is encoded as that target, so it suits a value of one kind
819/// throughout, such as an array column; a scalar of another kind stops
820/// the walk with [`NormalizeError::Mismatch`].
821///
822/// Made with [`Order::new`], which places no nulls, so a null has no term
823/// and the walk skips it. [`Order::nulls`] places them, as a space does:
824/// every null of the walk is then the space's sentinel, at its path.
825///
826/// ```
827/// use vitaminc_normalize::terms_by;
828/// use vitaminc_ore::{domain, NullOrder, Order, OrderTarget};
829///
830/// let ages = vec![Some(36i32), None];
831/// let plain = terms_by(Order::new(OrderTarget::I32), &ages)?;
832/// assert_eq!(plain.len(), 1);
833///
834/// let last = Order::new(OrderTarget::I32).nulls(NullOrder::Last).expect("fixed width");
835/// let placed = terms_by(last, &ages)?;
836/// assert_eq!(placed.len(), 2);
837/// assert_eq!(placed[1].normalized.domain(), domain::INT32_NULLS_LAST);
838/// # Ok::<(), vitaminc_ore::TermsError>(())
839/// ```
840#[derive(Debug, Clone, Copy, PartialEq, Eq)]
841pub struct Order {
842    target: OrderTarget,
843    nulls: Option<NullOrder>,
844}
845
846impl Order {
847    /// The normalizer for `target`, placing no nulls.
848    pub const fn new(target: OrderTarget) -> Self {
849        Self {
850            target,
851            nulls: None,
852        }
853    }
854
855    /// This normalizer, placing nulls as `nulls` says.
856    ///
857    /// # Errors
858    ///
859    /// [`NormalizeError::NoDomain`] for a text or bytes target, which takes
860    /// no null policy, rather than a normalizer that refuses every value.
861    pub fn nulls(self, nulls: NullOrder) -> Result<Self, NormalizeError> {
862        self.target
863            .domain_with_nulls(nulls)
864            .map(|_| Self {
865                nulls: Some(nulls),
866                ..self
867            })
868            .ok_or(NormalizeError::NoDomain)
869    }
870
871    /// The target every scalar is encoded as.
872    pub fn target(self) -> OrderTarget {
873        self.target
874    }
875
876    /// Where the normalizer places nulls, or `None` if it gives them no
877    /// term.
878    pub fn null_order(self) -> Option<NullOrder> {
879        self.nulls
880    }
881}
882
883impl Normalize for Order {
884    type Term = OrderPlaintext;
885
886    const INDEX: &'static str = "order";
887
888    fn normalize(
889        &self,
890        ty: Option<Logical>,
891        value: Physical<'_>,
892    ) -> Result<OrderPlaintext, NormalizeError> {
893        encode(self.target, self.nulls, ty, value)
894    }
895}
896
897/// Every order term of `value` as `target`, in walk order, without
898/// encrypting it: [`vitaminc_normalize::terms`] under the [`Order`]
899/// normalizer.
900///
901/// A null, a boolean or an `undefined` produces no term and does not stop
902/// the walk (for nulls placed first or last, walk with [`Order::nulls`]
903/// through [`vitaminc_normalize::terms_by`]); a scalar of another kind
904/// than `target`, or one outside it, stops it, and so does a value
905/// encryption would refuse, for the same reason at the same path.
906/// [`vitaminc_normalize::Terms`] has the rules.
907///
908/// # Examples
909///
910/// ```
911/// use vitaminc_normalize::Step;
912/// use vitaminc_ore::{domain, order_terms, OrderTarget};
913///
914/// let scores: Vec<u16> = vec![7, 9];
915/// let found = order_terms(OrderTarget::I32, &scores)?;
916/// assert_eq!(found[1].path.steps(), [Step::Index(1)]);
917/// assert_eq!(found[1].normalized.domain(), domain::INT32);
918/// # Ok::<(), vitaminc_ore::TermsError>(())
919/// ```
920///
921/// # Errors
922///
923/// [`TermsError`]: what the walk refuses, a value encryption would refuse
924/// included, or the source's own failure.
925pub fn order_terms<P: Encrypt>(
926    target: OrderTarget,
927    value: P,
928) -> Result<Vec<Term<OrderPlaintext>>, TermsError> {
929    terms_by(Order::new(target), value)
930}
931
932#[cfg(test)]
933#[path = "order_tests.rs"]
934mod tests;