Skip to main content

rudb_common/
value.rs

1//! Single values.
2//!
3//! A `Value` is one SQL value, boxed up on its own. It is what a literal parses into, what a
4//! constant folds to, and what a result set is read out as one cell at a time. It is deliberately
5//! not what execution runs on: `spec/07-execution.md` says the unit of data is a vector of 1024,
6//! and an operator that touches a `Value` per row is an operator that has already lost.
7//!
8//! The formatting here is DuckDB's, because a shell that prints `2024-01-15` where DuckDB prints
9//! `2024-01-15` is a shell whose output can be diffed against DuckDB's in `tamnd/rudb-compat`.
10
11use std::fmt;
12
13use crate::types::LogicalType;
14
15/// A single SQL value.
16///
17/// `PartialEq` here is Rust equality and not SQL equality. Two nulls compare equal and two NaNs
18/// compare equal, both of which SQL disagrees with. That is the right behaviour for a test
19/// assertion and the wrong behaviour for a `WHERE` clause, and the `WHERE` clause gets its
20/// comparison from the kernels rather than from here.
21#[derive(Debug, Clone, PartialEq)]
22#[non_exhaustive]
23pub enum Value {
24    /// `NULL`, of no particular type.
25    Null,
26    /// `BOOLEAN`.
27    Boolean(bool),
28    /// `TINYINT`.
29    TinyInt(i8),
30    /// `SMALLINT`.
31    SmallInt(i16),
32    /// `INTEGER`.
33    Integer(i32),
34    /// `BIGINT`.
35    BigInt(i64),
36    /// `HUGEINT`.
37    HugeInt(i128),
38    /// `UTINYINT`.
39    UTinyInt(u8),
40    /// `USMALLINT`.
41    USmallInt(u16),
42    /// `UINTEGER`.
43    UInteger(u32),
44    /// `UBIGINT`.
45    UBigInt(u64),
46    /// `UHUGEINT`.
47    UHugeInt(u128),
48    /// `FLOAT`.
49    Float(f32),
50    /// `DOUBLE`.
51    Double(f64),
52    /// `DECIMAL(width, scale)`, carrying the unscaled integer.
53    Decimal {
54        /// The unscaled value, so 12.34 at scale 2 is 1234.
55        unscaled: i128,
56        /// Total digits.
57        width: u8,
58        /// Digits right of the point.
59        scale: u8,
60    },
61    /// `VARCHAR`.
62    Varchar(String),
63    /// `BLOB`.
64    Blob(Vec<u8>),
65    /// `DATE`, days since 1970-01-01.
66    Date(i32),
67    /// `TIME`, microseconds since midnight.
68    Time(i64),
69    /// `TIMESTAMP`, microseconds since 1970-01-01 00:00:00.
70    Timestamp(i64),
71    /// `INTERVAL`, the months, days and microseconds triple.
72    ///
73    /// Three fields rather than one duration because interval arithmetic with months is not
74    /// associative with days, and DuckDB's specific behaviour is what tests assert on. A month is
75    /// not 30 days and this representation is what refuses to pretend otherwise.
76    Interval {
77        /// Whole months.
78        months: i32,
79        /// Whole days.
80        days: i32,
81        /// Microseconds.
82        micros: i64,
83    },
84    /// A list, carrying its element type so that an empty list still knows what it is empty of.
85    List {
86        /// The element type.
87        element: LogicalType,
88        /// The elements.
89        values: Vec<Value>,
90    },
91    /// A struct, in field order.
92    Struct(Vec<(String, Value)>),
93}
94
95impl Value {
96    /// Whether this is `NULL`.
97    #[must_use]
98    pub fn is_null(&self) -> bool {
99        matches!(self, Self::Null)
100    }
101
102    /// The type of this value.
103    #[must_use]
104    pub fn logical_type(&self) -> LogicalType {
105        match self {
106            Self::Null => LogicalType::Null,
107            Self::Boolean(_) => LogicalType::Boolean,
108            Self::TinyInt(_) => LogicalType::TinyInt,
109            Self::SmallInt(_) => LogicalType::SmallInt,
110            Self::Integer(_) => LogicalType::Integer,
111            Self::BigInt(_) => LogicalType::BigInt,
112            Self::HugeInt(_) => LogicalType::HugeInt,
113            Self::UTinyInt(_) => LogicalType::UTinyInt,
114            Self::USmallInt(_) => LogicalType::USmallInt,
115            Self::UInteger(_) => LogicalType::UInteger,
116            Self::UBigInt(_) => LogicalType::UBigInt,
117            Self::UHugeInt(_) => LogicalType::UHugeInt,
118            Self::Float(_) => LogicalType::Float,
119            Self::Double(_) => LogicalType::Double,
120            Self::Decimal { width, scale, .. } => {
121                LogicalType::Decimal { width: *width, scale: *scale }
122            }
123            Self::Varchar(_) => LogicalType::Varchar,
124            Self::Blob(_) => LogicalType::Blob,
125            Self::Date(_) => LogicalType::Date,
126            Self::Time(_) => LogicalType::Time,
127            Self::Timestamp(_) => LogicalType::Timestamp,
128            Self::Interval { .. } => LogicalType::Interval,
129            Self::List { element, .. } => LogicalType::list(element.clone()),
130            Self::Struct(fields) => LogicalType::Struct(
131                fields
132                    .iter()
133                    .map(|(name, value)| crate::types::Field::new(name, value.logical_type()))
134                    .collect(),
135            ),
136        }
137    }
138
139    /// The value as an `i64`, for the integer types that fit in one.
140    ///
141    /// Used by the planner for the places where a literal has to be a small integer, `LIMIT` and
142    /// `OFFSET` being the obvious ones. Returns `None` rather than saturating, because a `LIMIT`
143    /// that silently became `i64::MAX` is worse than an error.
144    #[must_use]
145    pub fn as_i64(&self) -> Option<i64> {
146        match *self {
147            Self::TinyInt(v) => Some(i64::from(v)),
148            Self::SmallInt(v) => Some(i64::from(v)),
149            Self::Integer(v) => Some(i64::from(v)),
150            Self::BigInt(v) => Some(v),
151            Self::UTinyInt(v) => Some(i64::from(v)),
152            Self::USmallInt(v) => Some(i64::from(v)),
153            Self::UInteger(v) => Some(i64::from(v)),
154            Self::UBigInt(v) => i64::try_from(v).ok(),
155            Self::HugeInt(v) => i64::try_from(v).ok(),
156            Self::UHugeInt(v) => i64::try_from(v).ok(),
157            _ => None,
158        }
159    }
160
161    /// The value as a `bool`, for a `BOOLEAN` and nothing else.
162    #[must_use]
163    pub fn as_bool(&self) -> Option<bool> {
164        match *self {
165            Self::Boolean(v) => Some(v),
166            _ => None,
167        }
168    }
169
170    /// The value as a string slice, for a `VARCHAR` and nothing else.
171    #[must_use]
172    pub fn as_str(&self) -> Option<&str> {
173        match self {
174            Self::Varchar(v) => Some(v),
175            _ => None,
176        }
177    }
178}
179
180impl fmt::Display for Value {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        match self {
183            Self::Null => f.write_str("NULL"),
184            Self::Boolean(v) => f.write_str(if *v { "true" } else { "false" }),
185            Self::TinyInt(v) => write!(f, "{v}"),
186            Self::SmallInt(v) => write!(f, "{v}"),
187            Self::Integer(v) => write!(f, "{v}"),
188            Self::BigInt(v) => write!(f, "{v}"),
189            Self::HugeInt(v) => write!(f, "{v}"),
190            Self::UTinyInt(v) => write!(f, "{v}"),
191            Self::USmallInt(v) => write!(f, "{v}"),
192            Self::UInteger(v) => write!(f, "{v}"),
193            Self::UBigInt(v) => write!(f, "{v}"),
194            Self::UHugeInt(v) => write!(f, "{v}"),
195            Self::Float(v) => write_float(f, f64::from(*v)),
196            Self::Double(v) => write_float(f, *v),
197            Self::Decimal { unscaled, scale, .. } => write_decimal(f, *unscaled, *scale),
198            Self::Varchar(v) => f.write_str(v),
199            Self::Blob(v) => write_blob(f, v),
200            Self::Date(v) => write_date(f, *v),
201            Self::Time(v) => write_time(f, *v),
202            Self::Timestamp(v) => write_timestamp(f, *v),
203            Self::Interval { months, days, micros } => write_interval(f, *months, *days, *micros),
204            Self::List { values, .. } => {
205                f.write_str("[")?;
206                for (index, value) in values.iter().enumerate() {
207                    if index > 0 {
208                        f.write_str(", ")?;
209                    }
210                    write!(f, "{value}")?;
211                }
212                f.write_str("]")
213            }
214            Self::Struct(fields) => {
215                f.write_str("{")?;
216                for (index, (name, value)) in fields.iter().enumerate() {
217                    if index > 0 {
218                        f.write_str(", ")?;
219                    }
220                    write!(f, "'{name}': {value}")?;
221                }
222                f.write_str("}")
223            }
224        }
225    }
226}
227
228/// Floats print the shortest text that reads back as the same value, which is what Rust's own
229/// formatter does, with one exception: an integral float prints a trailing `.0` in Rust and does
230/// not in DuckDB.
231fn write_float(f: &mut fmt::Formatter<'_>, value: f64) -> fmt::Result {
232    if value.is_nan() {
233        return f.write_str("nan");
234    }
235    if value.is_infinite() {
236        return f.write_str(if value > 0.0 { "inf" } else { "-inf" });
237    }
238    let text = format!("{value}");
239    f.write_str(text.strip_suffix(".0").unwrap_or(&text))
240}
241
242fn write_decimal(f: &mut fmt::Formatter<'_>, unscaled: i128, scale: u8) -> fmt::Result {
243    if scale == 0 {
244        return write!(f, "{unscaled}");
245    }
246    let negative = unscaled < 0;
247    // Widened before the negation so that i128::MIN does not overflow on the way to its digits.
248    let digits = unscaled.unsigned_abs().to_string();
249    let scale = usize::from(scale);
250    let (whole, fraction) = if digits.len() > scale {
251        let split = digits.len() - scale;
252        (digits[..split].to_string(), digits[split..].to_string())
253    } else {
254        ("0".to_string(), format!("{:0>scale$}", digits))
255    };
256    if negative {
257        f.write_str("-")?;
258    }
259    write!(f, "{whole}.{fraction}")
260}
261
262/// A blob prints as printable ASCII with everything else hex escaped, which is DuckDB's rule.
263fn write_blob(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
264    for &byte in bytes {
265        if byte.is_ascii_graphic() || byte == b' ' {
266            write!(f, "{}", byte as char)?;
267        } else {
268            write!(f, "\\x{byte:02X}")?;
269        }
270    }
271    Ok(())
272}
273
274/// Days since the epoch to the civil date, by Howard Hinnant's algorithm.
275///
276/// Written out rather than pulled in from a date library because it is twenty lines, because the
277/// dependency table in `spec/18-package-layout.md` is short on purpose, and because a date library
278/// that disagrees with DuckDB about a date before 1582 is a compatibility bug we would then own
279/// without being able to fix it.
280#[must_use]
281pub fn civil_from_days(days: i32) -> (i32, u32, u32) {
282    let z = i64::from(days) + 719_468;
283    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
284    let day_of_era = z - era * 146_097;
285    let year_of_era =
286        (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
287    let year = year_of_era + era * 400;
288    let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
289    let shifted_month = (5 * day_of_year + 2) / 153;
290    let day = day_of_year - (153 * shifted_month + 2) / 5 + 1;
291    let month = if shifted_month < 10 { shifted_month + 3 } else { shifted_month - 9 };
292    let year = if month <= 2 { year + 1 } else { year };
293    #[expect(clippy::cast_possible_truncation, reason = "the ranges are 1 to 12 and 1 to 31")]
294    (year as i32, month as u32, day as u32)
295}
296
297/// The civil date to days since the epoch, the inverse of [`civil_from_days`].
298#[must_use]
299pub fn days_from_civil(year: i32, month: u32, day: u32) -> i32 {
300    let year = i64::from(year) - i64::from(month <= 2);
301    let era = if year >= 0 { year } else { year - 399 } / 400;
302    let year_of_era = year - era * 400;
303    let month = i64::from(month);
304    let shifted_month = if month > 2 { month - 3 } else { month + 9 };
305    let day_of_year = (153 * shifted_month + 2) / 5 + i64::from(day) - 1;
306    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
307    #[expect(clippy::cast_possible_truncation, reason = "a date in i32 range stays in i32 range")]
308    ((era * 146_097 + day_of_era - 719_468) as i32)
309}
310
311fn write_date(f: &mut fmt::Formatter<'_>, days: i32) -> fmt::Result {
312    let (year, month, day) = civil_from_days(days);
313    if year < 0 {
314        write!(f, "{:04}-{month:02}-{day:02} (BC)", -year + 1)
315    } else {
316        write!(f, "{year:04}-{month:02}-{day:02}")
317    }
318}
319
320fn write_time(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
321    let seconds = micros.div_euclid(1_000_000);
322    let fraction = micros.rem_euclid(1_000_000);
323    let (hours, minutes, seconds) = (seconds / 3600, (seconds / 60) % 60, seconds % 60);
324    write!(f, "{hours:02}:{minutes:02}:{seconds:02}")?;
325    if fraction != 0 {
326        // Trailing zeros are trimmed, so a value on a millisecond boundary prints three digits.
327        let text = format!("{fraction:06}");
328        write!(f, ".{}", text.trim_end_matches('0'))?;
329    }
330    Ok(())
331}
332
333fn write_timestamp(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
334    const MICROS_PER_DAY: i64 = 86_400 * 1_000_000;
335    let days = micros.div_euclid(MICROS_PER_DAY);
336    let within_day = micros.rem_euclid(MICROS_PER_DAY);
337    let Ok(days) = i32::try_from(days) else {
338        return f.write_str("timestamp out of range");
339    };
340    write_date(f, days)?;
341    f.write_str(" ")?;
342    write_time(f, within_day)
343}
344
345fn write_interval(f: &mut fmt::Formatter<'_>, months: i32, days: i32, micros: i64) -> fmt::Result {
346    let mut wrote = false;
347    let space = |f: &mut fmt::Formatter<'_>, wrote: &mut bool| -> fmt::Result {
348        if *wrote {
349            f.write_str(" ")?;
350        }
351        *wrote = true;
352        Ok(())
353    };
354    let (years, rest_months) = (months / 12, months % 12);
355    if years != 0 {
356        space(f, &mut wrote)?;
357        write!(f, "{years} year{}", plural(years))?;
358    }
359    if rest_months != 0 {
360        space(f, &mut wrote)?;
361        write!(f, "{rest_months} month{}", plural(rest_months))?;
362    }
363    if days != 0 {
364        space(f, &mut wrote)?;
365        write!(f, "{days} day{}", plural(days))?;
366    }
367    if micros != 0 || !wrote {
368        space(f, &mut wrote)?;
369        if micros < 0 {
370            f.write_str("-")?;
371        }
372        write_time(f, micros.abs())?;
373    }
374    Ok(())
375}
376
377fn plural(n: i32) -> &'static str {
378    if n == 1 || n == -1 { "" } else { "s" }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::{Value, civil_from_days, days_from_civil};
384    use crate::types::LogicalType;
385
386    #[test]
387    fn a_value_knows_its_own_type() {
388        assert_eq!(Value::Integer(1).logical_type(), LogicalType::Integer);
389        assert_eq!(Value::Null.logical_type(), LogicalType::Null);
390        let list = Value::List { element: LogicalType::Varchar, values: Vec::new() };
391        // The element type is carried rather than inferred, which is why an empty list still
392        // knows what it is empty of.
393        assert_eq!(list.logical_type(), LogicalType::list(LogicalType::Varchar));
394    }
395
396    #[test]
397    fn the_date_conversion_is_its_own_inverse() {
398        // Every day from 1600 to 2400, which covers the Gregorian corrections and both signs of
399        // the era arithmetic. Cheap enough to be exhaustive, so it is exhaustive.
400        for days in days_from_civil(1600, 1, 1)..days_from_civil(2400, 1, 1) {
401            let (year, month, day) = civil_from_days(days);
402            assert_eq!(days_from_civil(year, month, day), days, "{year}-{month}-{day}");
403        }
404    }
405
406    #[test]
407    fn the_epoch_is_where_it_should_be() {
408        assert_eq!(days_from_civil(1970, 1, 1), 0);
409        assert_eq!(civil_from_days(0), (1970, 1, 1));
410        assert_eq!(Value::Date(0).to_string(), "1970-01-01");
411        assert_eq!(Value::Date(19_723).to_string(), "2024-01-01");
412        assert_eq!(Value::Date(19_737).to_string(), "2024-01-15");
413    }
414
415    #[test]
416    fn a_leap_day_is_a_day() {
417        assert_eq!(civil_from_days(days_from_civil(2024, 2, 29)), (2024, 2, 29));
418        // 1900 was not a leap year and 2000 was, which is the pair every naive implementation
419        // gets wrong in one direction or the other.
420        assert_eq!(days_from_civil(1900, 3, 1) - days_from_civil(1900, 2, 28), 1);
421        assert_eq!(days_from_civil(2000, 3, 1) - days_from_civil(2000, 2, 28), 2);
422    }
423
424    #[test]
425    fn times_print_with_the_trailing_zeros_trimmed() {
426        assert_eq!(Value::Time(0).to_string(), "00:00:00");
427        assert_eq!(Value::Time(3_723_000_000).to_string(), "01:02:03");
428        assert_eq!(Value::Time(3_723_500_000).to_string(), "01:02:03.5");
429        assert_eq!(Value::Time(3_723_000_001).to_string(), "01:02:03.000001");
430    }
431
432    #[test]
433    fn a_timestamp_before_the_epoch_borrows_from_the_day() {
434        // The whole reason this uses div_euclid rather than a plain divide. A negative microsecond
435        // count is the previous day at a positive time, not the next day at a negative one.
436        assert_eq!(Value::Timestamp(-1).to_string(), "1969-12-31 23:59:59.999999");
437        assert_eq!(Value::Timestamp(0).to_string(), "1970-01-01 00:00:00");
438    }
439
440    #[test]
441    fn a_decimal_prints_at_its_scale() {
442        let d = |unscaled, scale| Value::Decimal { unscaled, width: 18, scale }.to_string();
443        assert_eq!(d(1234, 2), "12.34");
444        assert_eq!(d(-1234, 2), "-12.34");
445        assert_eq!(d(5, 3), "0.005");
446        assert_eq!(d(-5, 3), "-0.005");
447        assert_eq!(d(1234, 0), "1234");
448        assert_eq!(d(1_000_000, 6), "1.000000");
449    }
450
451    #[test]
452    fn an_integral_float_prints_without_the_rust_trailing_zero() {
453        assert_eq!(Value::Double(1.0).to_string(), "1");
454        assert_eq!(Value::Double(1.5).to_string(), "1.5");
455        assert_eq!(Value::Double(f64::INFINITY).to_string(), "inf");
456        assert_eq!(Value::Float(0.5).to_string(), "0.5");
457    }
458
459    #[test]
460    fn an_interval_keeps_months_days_and_micros_apart() {
461        let i = |months, days, micros| Value::Interval { months, days, micros }.to_string();
462        assert_eq!(i(14, 3, 3_723_000_000), "1 year 2 months 3 days 01:02:03");
463        assert_eq!(i(1, 0, 0), "1 month");
464        assert_eq!(i(0, 0, 0), "00:00:00");
465        assert_eq!(i(0, 0, -1_000_000), "-00:00:01");
466    }
467
468    #[test]
469    fn a_blob_escapes_what_is_not_printable() {
470        assert_eq!(Value::Blob(b"ok".to_vec()).to_string(), "ok");
471        assert_eq!(Value::Blob(vec![0, 1, b'a']).to_string(), "\\x00\\x01a");
472    }
473
474    #[test]
475    fn a_limit_that_does_not_fit_is_none_rather_than_clamped() {
476        assert_eq!(Value::Integer(5).as_i64(), Some(5));
477        assert_eq!(Value::UBigInt(u64::MAX).as_i64(), None);
478        assert_eq!(Value::Varchar("5".into()).as_i64(), None);
479    }
480}