Skip to main content

iceberg/spec/values/
primitive.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Primitive literal types
19
20use std::cmp::Ordering;
21
22use ordered_float::{FloatCore, OrderedFloat};
23
24use crate::spec::PrimitiveType;
25
26/// Values present in iceberg type
27///
28/// `Float` and `Double` compare as Java's `Float.compare` and `Double.compare` do: `-0.0` is
29/// less than `0.0`, and every NaN is the same value, greater than all others. iceberg-java
30/// compares partition values this way, so `-0.0` and `0.0` are different partitions. The
31/// [spec](https://iceberg.apache.org/spec/#scan-planning) states the same rule: floating point
32/// partition values are equal if their IEEE 754 bit layouts are equal, with NaNs normalized.
33// The derived `Hash` gives `-0.0` and `0.0` the same hash. That is coarser than `eq`, which is
34// allowed: equal values still hash alike.
35#[allow(clippy::derived_hash_with_manual_eq)]
36#[derive(Clone, Debug, Hash, Eq)]
37pub enum PrimitiveLiteral {
38    /// 0x00 for false, non-zero byte for true
39    Boolean(bool),
40    /// Stored as 4-byte little-endian
41    Int(i32),
42    /// Stored as 8-byte little-endian
43    Long(i64),
44    /// Stored as 4-byte little-endian
45    Float(OrderedFloat<f32>),
46    /// Stored as 8-byte little-endian
47    Double(OrderedFloat<f64>),
48    /// UTF-8 bytes (without length)
49    String(String),
50    /// Binary value (without length)
51    Binary(Vec<u8>),
52    /// Stored as 16-byte big-endian
53    Int128(i128),
54    /// Stored as 16-byte big-endian
55    UInt128(u128),
56    /// When a number is larger than it can hold
57    AboveMax,
58    /// When a number is smaller than it can hold
59    BelowMin,
60}
61
62impl PartialEq for PrimitiveLiteral {
63    fn eq(&self, other: &Self) -> bool {
64        self.partial_cmp(other) == Some(Ordering::Equal)
65    }
66}
67
68impl PartialOrd for PrimitiveLiteral {
69    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
70        match (self, other) {
71            (Self::Boolean(a), Self::Boolean(b)) => a.partial_cmp(b),
72            (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
73            (Self::Long(a), Self::Long(b)) => a.partial_cmp(b),
74            (Self::Float(a), Self::Float(b)) => Some(float_cmp(a, b)),
75            (Self::Double(a), Self::Double(b)) => Some(float_cmp(a, b)),
76            (Self::String(a), Self::String(b)) => a.partial_cmp(b),
77            (Self::Binary(a), Self::Binary(b)) => a.partial_cmp(b),
78            (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
79            (Self::UInt128(a), Self::UInt128(b)) => a.partial_cmp(b),
80            (Self::AboveMax, Self::AboveMax) | (Self::BelowMin, Self::BelowMin) => {
81                Some(Ordering::Equal)
82            }
83            // Different variants order by declaration, as the derived impl did. A variant that
84            // lacks an arm above lands here against itself; fail closed instead of calling the
85            // two values equal.
86            _ => {
87                debug_assert_ne!(std::mem::discriminant(self), std::mem::discriminant(other));
88                match self.variant_index().cmp(&other.variant_index()) {
89                    Ordering::Equal => None,
90                    ordering => Some(ordering),
91                }
92            }
93        }
94    }
95}
96
97/// Compares floats as Java's `Float.compare` and `Double.compare` do. `OrderedFloat` already
98/// treats every NaN as one value above all others; it only lacks `-0.0` before `0.0`.
99fn float_cmp<T: FloatCore>(a: &OrderedFloat<T>, b: &OrderedFloat<T>) -> Ordering {
100    a.cmp(b).then_with(|| {
101        if a.is_nan() {
102            Ordering::Equal
103        } else {
104            b.is_sign_negative().cmp(&a.is_sign_negative())
105        }
106    })
107}
108
109impl PrimitiveLiteral {
110    /// Widens this literal to its promotion ceiling (`int` -> `long`, `float` ->
111    /// `double`), returning `None` for any other literal. Used when the target type
112    /// is unknown (the partition source column was dropped) so a value and its
113    /// deletes still normalize to the same type.
114    pub(crate) fn widen(&self) -> Option<PrimitiveLiteral> {
115        match self {
116            PrimitiveLiteral::Int(value) => Some(PrimitiveLiteral::Long(i64::from(*value))),
117            PrimitiveLiteral::Float(value) => {
118                Some(PrimitiveLiteral::Double(OrderedFloat(f64::from(value.0))))
119            }
120            _ => None,
121        }
122    }
123
124    /// Widens this literal to `target` for the value promotions `int` -> `long`
125    /// and `float` -> `double`, returning `None` when `target` does not promote it.
126    pub(crate) fn promote_to(&self, target: &PrimitiveType) -> Option<PrimitiveLiteral> {
127        match (self, target) {
128            (PrimitiveLiteral::Int(_), PrimitiveType::Long)
129            | (PrimitiveLiteral::Float(_), PrimitiveType::Double) => self.widen(),
130            _ => None,
131        }
132    }
133
134    /// Must follow the declaration order of the variants: `partial_cmp` uses it to order
135    /// different variants the way the derived `PartialOrd` did.
136    fn variant_index(&self) -> u8 {
137        match self {
138            Self::Boolean(_) => 0,
139            Self::Int(_) => 1,
140            Self::Long(_) => 2,
141            Self::Float(_) => 3,
142            Self::Double(_) => 4,
143            Self::String(_) => 5,
144            Self::Binary(_) => 6,
145            Self::Int128(_) => 7,
146            Self::UInt128(_) => 8,
147            Self::AboveMax => 9,
148            Self::BelowMin => 10,
149        }
150    }
151
152    /// Returns true if the Literal represents a primitive type
153    /// that can be a NaN, and that it's value is NaN
154    pub fn is_nan(&self) -> bool {
155        match self {
156            PrimitiveLiteral::Double(val) => val.is_nan(),
157            PrimitiveLiteral::Float(val) => val.is_nan(),
158            _ => false,
159        }
160    }
161}