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    /// `TIME WITH TIME ZONE`, microseconds since midnight UTC.
70    ///
71    /// An arm of its own rather than a [`Value::Time`] under a type that says the zone, because the
72    /// plan holds a constant's type and its value in two places and checks that the two agree, and
73    /// because printing one is not printing the other: a zoned time carries the offset after it.
74    TimeTz(i64),
75    /// `TIMESTAMP`, microseconds since 1970-01-01 00:00:00.
76    Timestamp(i64),
77    /// `TIMESTAMP WITH TIME ZONE`, microseconds since 1970-01-01 00:00:00 UTC.
78    ///
79    /// The same instant a [`Value::Timestamp`] holds, and what makes it a different value is what a
80    /// reader is entitled to conclude from it. A `TIMESTAMP` is a wall clock reading with no zone
81    /// behind it and this is a point in time, so the one thing this arm knows that the other does
82    /// not is which moment it is.
83    TimestampTz(i64),
84    /// `INTERVAL`, the months, days and microseconds triple.
85    ///
86    /// Three fields rather than one duration because interval arithmetic with months is not
87    /// associative with days, and DuckDB's specific behaviour is what tests assert on. A month is
88    /// not 30 days and this representation is what refuses to pretend otherwise.
89    Interval {
90        /// Whole months.
91        months: i32,
92        /// Whole days.
93        days: i32,
94        /// Microseconds.
95        micros: i64,
96    },
97    /// A list, carrying its element type so that an empty list still knows what it is empty of.
98    List {
99        /// The element type.
100        element: LogicalType,
101        /// The elements.
102        values: Vec<Value>,
103    },
104    /// A struct, in field order.
105    Struct(Vec<(String, Value)>),
106    /// A map, in insertion order, carrying both of its types so that an empty map still knows what it
107    /// is empty of.
108    ///
109    /// Pairs rather than a struct per entry, even though that is how a map is stored underneath and how
110    /// DuckDB stores one. A `Value` is what a result is read out as and what a test asserts on, and an
111    /// assertion about a map should read as an assertion about a map rather than about a list of two
112    /// field structs. The vector is where the other shape lives, and it is the shape that matters for
113    /// the bytes.
114    ///
115    /// Order is kept rather than sorted. DuckDB prints a map in the order it was built in and nothing
116    /// here is entitled to decide that the keys wanted sorting.
117    ///
118    /// The two types are boxed and the list's one is not, which looks inconsistent and is not. A
119    /// `LogicalType` is 32 bytes and this enum is 64, so a list fits its type and its values in an arm
120    /// with room to spare while two types and a vector would need 96 and every `BOOLEAN` in the system
121    /// would get 32 bytes wider to pay for it. [`LogicalType::Map`] boxes them for the same reason, so
122    /// the boxes here are the ones it already has rather than new ones.
123    Map {
124        /// The key type.
125        key: Box<LogicalType>,
126        /// The value type.
127        value: Box<LogicalType>,
128        /// The entries, in order.
129        entries: Vec<(Value, Value)>,
130    },
131}
132
133impl Value {
134    /// A map of these entries, keyed and valued by these types.
135    ///
136    /// Here because [`Value::Map`] holds its two types boxed and a caller should not have to say so.
137    /// Every other arm of this enum is built as a literal and this one would be too if it were not for
138    /// the boxes.
139    #[must_use]
140    pub fn map(key: LogicalType, value: LogicalType, entries: Vec<(Self, Self)>) -> Self {
141        Self::Map { key: Box::new(key), value: Box::new(value), entries }
142    }
143
144    /// How many bytes this value takes, counting what it owns on the heap.
145    ///
146    /// What the memory limit charges for a value held in a buffer. It is the enum itself plus the
147    /// string, the blob, the list or the struct behind it, and it counts capacity rather than
148    /// length, because capacity is what was taken from the allocator and a string built by pushing
149    /// bytes usually has more of it than it needs.
150    ///
151    /// The enum is as wide as its widest arm whatever is in it, so a `BOOLEAN` costs the same as a
152    /// `HUGEINT` here. That is not a rounding error, it is the layout: a row of booleans held as
153    /// values really does cost that.
154    #[must_use]
155    pub fn footprint(&self) -> usize {
156        size_of::<Self>() + self.heap()
157    }
158
159    /// What this value owns beyond its own bytes.
160    fn heap(&self) -> usize {
161        match self {
162            Self::Varchar(text) => text.capacity(),
163            Self::Blob(bytes) => bytes.capacity(),
164            Self::List { values, .. } => {
165                values.capacity() * size_of::<Self>() + values.iter().map(Self::heap).sum::<usize>()
166            }
167            Self::Struct(fields) => {
168                fields.capacity() * size_of::<(String, Self)>()
169                    + fields
170                        .iter()
171                        .map(|(name, value)| name.capacity() + value.heap())
172                        .sum::<usize>()
173            }
174            // The two boxed types are counted here and the list's inline element type is not, which is
175            // not a disagreement about what a type costs. A boxed type is an allocation and an inline
176            // one is already inside `size_of::<Self>()`.
177            Self::Map { entries, .. } => {
178                2 * size_of::<LogicalType>()
179                    + entries.capacity() * size_of::<(Self, Self)>()
180                    + entries.iter().map(|(key, value)| key.heap() + value.heap()).sum::<usize>()
181            }
182            _ => 0,
183        }
184    }
185
186    /// Whether this is `NULL`.
187    #[must_use]
188    pub fn is_null(&self) -> bool {
189        matches!(self, Self::Null)
190    }
191
192    /// The type of this value.
193    #[must_use]
194    pub fn logical_type(&self) -> LogicalType {
195        match self {
196            Self::Null => LogicalType::Null,
197            Self::Boolean(_) => LogicalType::Boolean,
198            Self::TinyInt(_) => LogicalType::TinyInt,
199            Self::SmallInt(_) => LogicalType::SmallInt,
200            Self::Integer(_) => LogicalType::Integer,
201            Self::BigInt(_) => LogicalType::BigInt,
202            Self::HugeInt(_) => LogicalType::HugeInt,
203            Self::UTinyInt(_) => LogicalType::UTinyInt,
204            Self::USmallInt(_) => LogicalType::USmallInt,
205            Self::UInteger(_) => LogicalType::UInteger,
206            Self::UBigInt(_) => LogicalType::UBigInt,
207            Self::UHugeInt(_) => LogicalType::UHugeInt,
208            Self::Float(_) => LogicalType::Float,
209            Self::Double(_) => LogicalType::Double,
210            Self::Decimal { width, scale, .. } => {
211                LogicalType::Decimal { width: *width, scale: *scale }
212            }
213            Self::Varchar(_) => LogicalType::Varchar,
214            Self::Blob(_) => LogicalType::Blob,
215            Self::Date(_) => LogicalType::Date,
216            Self::Time(_) => LogicalType::Time,
217            Self::TimeTz(_) => LogicalType::TimeTz,
218            Self::Timestamp(_) => LogicalType::Timestamp,
219            Self::TimestampTz(_) => LogicalType::TimestampTz,
220            Self::Interval { .. } => LogicalType::Interval,
221            Self::List { element, .. } => LogicalType::list(element.clone()),
222            Self::Struct(fields) => LogicalType::Struct(
223                fields
224                    .iter()
225                    .map(|(name, value)| crate::types::Field::new(name, value.logical_type()))
226                    .collect(),
227            ),
228            Self::Map { key, value, .. } => LogicalType::Map(key.clone(), value.clone()),
229        }
230    }
231
232    /// The value as an `i64`, for the integer types that fit in one.
233    ///
234    /// Used by the planner for the places where a literal has to be a small integer, `LIMIT` and
235    /// `OFFSET` being the obvious ones. Returns `None` rather than saturating, because a `LIMIT`
236    /// that silently became `i64::MAX` is worse than an error.
237    #[must_use]
238    pub fn as_i64(&self) -> Option<i64> {
239        match *self {
240            Self::TinyInt(v) => Some(i64::from(v)),
241            Self::SmallInt(v) => Some(i64::from(v)),
242            Self::Integer(v) => Some(i64::from(v)),
243            Self::BigInt(v) => Some(v),
244            Self::UTinyInt(v) => Some(i64::from(v)),
245            Self::USmallInt(v) => Some(i64::from(v)),
246            Self::UInteger(v) => Some(i64::from(v)),
247            Self::UBigInt(v) => i64::try_from(v).ok(),
248            Self::HugeInt(v) => i64::try_from(v).ok(),
249            Self::UHugeInt(v) => i64::try_from(v).ok(),
250            _ => None,
251        }
252    }
253
254    /// The value as a `bool`, for a `BOOLEAN` and nothing else.
255    #[must_use]
256    pub fn as_bool(&self) -> Option<bool> {
257        match *self {
258            Self::Boolean(v) => Some(v),
259            _ => None,
260        }
261    }
262
263    /// The value as a string slice, for a `VARCHAR` and nothing else.
264    #[must_use]
265    pub fn as_str(&self) -> Option<&str> {
266        match self {
267            Self::Varchar(v) => Some(v),
268            _ => None,
269        }
270    }
271}
272
273impl fmt::Display for Value {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        match self {
276            Self::Null => f.write_str("NULL"),
277            Self::Boolean(v) => f.write_str(if *v { "true" } else { "false" }),
278            Self::TinyInt(v) => write!(f, "{v}"),
279            Self::SmallInt(v) => write!(f, "{v}"),
280            Self::Integer(v) => write!(f, "{v}"),
281            Self::BigInt(v) => write!(f, "{v}"),
282            Self::HugeInt(v) => write!(f, "{v}"),
283            Self::UTinyInt(v) => write!(f, "{v}"),
284            Self::USmallInt(v) => write!(f, "{v}"),
285            Self::UInteger(v) => write!(f, "{v}"),
286            Self::UBigInt(v) => write!(f, "{v}"),
287            Self::UHugeInt(v) => write!(f, "{v}"),
288            Self::Float(v) => write_float(f, *v),
289            Self::Double(v) => write_float(f, *v),
290            Self::Decimal { unscaled, scale, .. } => write_decimal(f, *unscaled, *scale),
291            Self::Varchar(v) => f.write_str(v),
292            Self::Blob(v) => write_blob(f, v),
293            Self::Date(v) => write_date(f, *v),
294            Self::Time(v) => write_time(f, *v),
295            // The unzoned rendering and then the offset, which is what the pin prints and is
296            // `+00` until there is a session time zone to print something else. The offset is not
297            // optional there: a zoned value always ends in one.
298            Self::TimeTz(v) => {
299                write_time(f, *v)?;
300                f.write_str(UTC)
301            }
302            Self::Timestamp(v) => write_timestamp(f, *v),
303            Self::TimestampTz(v) => {
304                write_timestamp(f, *v)?;
305                f.write_str(UTC)
306            }
307            Self::Interval { months, days, micros } => write_interval(f, *months, *days, *micros),
308            Self::List { values, .. } => {
309                f.write_str("[")?;
310                for (index, value) in values.iter().enumerate() {
311                    if index > 0 {
312                        f.write_str(", ")?;
313                    }
314                    write!(f, "{value}")?;
315                }
316                f.write_str("]")
317            }
318            Self::Struct(fields) => {
319                f.write_str("{")?;
320                for (index, (name, value)) in fields.iter().enumerate() {
321                    if index > 0 {
322                        f.write_str(", ")?;
323                    }
324                    write!(f, "'{name}': {value}")?;
325                }
326                f.write_str("}")
327            }
328            // Braces like a struct and nothing else like one. A struct quotes its field name and
329            // separates it with a colon, and a map writes `key=value` with neither, so the two cannot
330            // share a printer however alike their layouts are. Both of these are the pin's.
331            Self::Map { entries, .. } => {
332                f.write_str("{")?;
333                for (index, (key, value)) in entries.iter().enumerate() {
334                    if index > 0 {
335                        f.write_str(", ")?;
336                    }
337                    write!(f, "{key}={value}")?;
338                }
339                f.write_str("}")
340            }
341        }
342    }
343}
344
345impl Value {
346    /// Formats a value in a session offset rather than the UTC fallback used by [`std::fmt::Display`].
347    #[must_use]
348    pub fn to_string_at_offset(&self, offset_seconds: i32) -> String {
349        // The offset is spelled out only in the two arms that print it. Two of the three arms here
350        // do not, and building the text for them was a string allocated and dropped per value.
351        match self {
352            Self::TimestampTz(micros) => {
353                let local = micros.saturating_add(i64::from(offset_seconds) * 1_000_000);
354                format!("{}{}", Self::Timestamp(local), offset_text(offset_seconds))
355            }
356            Self::TimeTz(micros) => {
357                format!("{}{}", Self::Time(*micros), offset_text(offset_seconds))
358            }
359            other => other.to_string(),
360        }
361    }
362}
363
364fn offset_text(seconds: i32) -> String {
365    let sign = if seconds < 0 { '-' } else { '+' };
366    let absolute = seconds.unsigned_abs();
367    let hours = absolute / 3600;
368    let minutes = (absolute / 60) % 60;
369    let remainder = absolute % 60;
370    if remainder != 0 {
371        format!("{sign}{hours:02}:{minutes:02}:{remainder:02}")
372    } else if minutes != 0 {
373        format!("{sign}{hours:02}:{minutes:02}")
374    } else {
375        format!("{sign}{hours:02}")
376    }
377}
378
379/// The two float types, so that one printer can serve both without going through `f64`.
380///
381/// Widening an `f32` to print it is wrong and quietly so: `0.1f32` as an `f64` is
382/// `0.10000000149011612`, and the shortest text that reads back as the same `f32` is `0.1`. DuckDB
383/// prints `0.1`, and it prints it because it formats the `float` rather than a `double` made out of
384/// one.
385trait Real: Copy + fmt::Display + fmt::LowerExp {
386    fn is_nan(self) -> bool;
387    fn is_infinite(self) -> bool;
388    fn is_sign_negative(self) -> bool;
389}
390
391impl Real for f32 {
392    fn is_nan(self) -> bool {
393        Self::is_nan(self)
394    }
395
396    fn is_infinite(self) -> bool {
397        Self::is_infinite(self)
398    }
399
400    fn is_sign_negative(self) -> bool {
401        Self::is_sign_negative(self)
402    }
403}
404
405impl Real for f64 {
406    fn is_nan(self) -> bool {
407        Self::is_nan(self)
408    }
409
410    fn is_infinite(self) -> bool {
411        Self::is_infinite(self)
412    }
413
414    fn is_sign_negative(self) -> bool {
415        Self::is_sign_negative(self)
416    }
417}
418
419/// Floats print the shortest text that reads back as the same value, laid out the way DuckDB lays
420/// it out.
421///
422/// Rust and DuckDB agree on the digits and disagree on everything around them. A float with nothing
423/// after the point keeps its `.0`, so a `DOUBLE` never looks like an integer. Anything with a
424/// decimal exponent outside `-4..16` is written in exponent form with a signed two digit exponent,
425/// so `1e16` is `1e+16` and `0.00001` is `1e-05`, while `1e15` is still written out in full. That
426/// is C's `%g` rule and it is what DuckDB's formatter implements, checked against the binary rather
427/// than read out of its source.
428fn write_float<T: Real>(f: &mut fmt::Formatter<'_>, value: T) -> fmt::Result {
429    // The sign bit and nothing else, because no comparison against a nan says anything about it.
430    // An invalid operation on x86 produces a nan with the bit set and DuckDB prints that as `-nan`,
431    // where the nan a string parses to has the bit clear and prints as `nan`. Rust prints `NaN` for
432    // both.
433    if value.is_nan() {
434        return f.write_str(if value.is_sign_negative() { "-nan" } else { "nan" });
435    }
436    if value.is_infinite() {
437        return f.write_str(if value.is_sign_negative() { "-inf" } else { "inf" });
438    }
439    let scientific = format!("{value:e}");
440    let (mantissa, exponent) = scientific.split_once('e').unwrap_or((scientific.as_str(), "0"));
441    let exponent: i32 = exponent.parse().unwrap_or(0);
442    if (-4..16).contains(&exponent) {
443        let text = format!("{value}");
444        if text.contains('.') {
445            return f.write_str(&text);
446        }
447        return write!(f, "{text}.0");
448    }
449    let sign = if exponent < 0 { '-' } else { '+' };
450    write!(f, "{mantissa}e{sign}{:02}", exponent.abs())
451}
452
453fn write_decimal(f: &mut fmt::Formatter<'_>, unscaled: i128, scale: u8) -> fmt::Result {
454    if scale == 0 {
455        return write!(f, "{unscaled}");
456    }
457    let negative = unscaled < 0;
458    // Widened before the negation so that i128::MIN does not overflow on the way to its digits.
459    let digits = unscaled.unsigned_abs().to_string();
460    let scale = usize::from(scale);
461    let (whole, fraction) = if digits.len() > scale {
462        let split = digits.len() - scale;
463        (digits[..split].to_string(), digits[split..].to_string())
464    } else {
465        ("0".to_string(), format!("{:0>scale$}", digits))
466    };
467    if negative {
468        f.write_str("-")?;
469    }
470    write!(f, "{whole}.{fraction}")
471}
472
473/// A blob prints as printable ASCII with everything else hex escaped, which is DuckDB's rule.
474///
475/// Three printable characters are escaped anyway, and they are the three that would otherwise make
476/// the printed form ambiguous: a backslash because it starts an escape, and the two quotes because
477/// the text this prints into is a string literal often enough. Every byte of all 256 was compared
478/// against DuckDB and these three were the only disagreement.
479fn write_blob(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
480    for &byte in bytes {
481        if (byte.is_ascii_graphic() || byte == b' ') && !matches!(byte, b'\\' | b'\'' | b'"') {
482            write!(f, "{}", byte as char)?;
483        } else {
484            write!(f, "\\x{byte:02X}")?;
485        }
486    }
487    Ok(())
488}
489
490/// Days since the epoch to the civil date, by Howard Hinnant's algorithm.
491///
492/// Written out rather than pulled in from a date library because it is twenty lines, because the
493/// dependency table in `spec/18-package-layout.md` is short on purpose, and because a date library
494/// that disagrees with DuckDB about a date before 1582 is a compatibility bug we would then own
495/// without being able to fix it.
496#[must_use]
497pub fn civil_from_days(days: i32) -> (i32, u32, u32) {
498    let z = i64::from(days) + 719_468;
499    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
500    let day_of_era = z - era * 146_097;
501    let year_of_era =
502        (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
503    let year = year_of_era + era * 400;
504    let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
505    let shifted_month = (5 * day_of_year + 2) / 153;
506    let day = day_of_year - (153 * shifted_month + 2) / 5 + 1;
507    let month = if shifted_month < 10 { shifted_month + 3 } else { shifted_month - 9 };
508    let year = if month <= 2 { year + 1 } else { year };
509    #[expect(clippy::cast_possible_truncation, reason = "the ranges are 1 to 12 and 1 to 31")]
510    (year as i32, month as u32, day as u32)
511}
512
513/// How long an interval is in microseconds, which is the one number two of them are compared as.
514///
515/// The three counts are kept apart because adding a month to a date is not adding thirty days to
516/// it, and an interval that has been flattened cannot tell the difference. Comparing two of them
517/// has to answer with one number all the same, and DuckDB's number is this one, thirty days to a
518/// month and twenty four hours to a day. So `INTERVAL '1 month'` and `INTERVAL '30 days'` are
519/// equal and still print differently, which is upstream's behaviour and not a rounding chosen here.
520///
521/// Ordering, `GROUP BY`, `DISTINCT`, a join key and the min and max aggregates all read this, so
522/// there is one function rather than a comparison in one file and a hash in another that can come
523/// to disagree about which two intervals are the same one.
524///
525/// The answer is an `i128` because the largest interval is the whole of an `i32` of months, which
526/// at thirty days each is six hundred times what an `i64` of microseconds holds.
527#[must_use]
528pub fn interval_micros(months: i32, days: i32, micros: i64) -> i128 {
529    const MICROS_PER_DAY: i128 = 86_400 * 1_000_000;
530    const DAYS_PER_MONTH: i128 = 30;
531    (i128::from(months) * DAYS_PER_MONTH + i128::from(days)) * MICROS_PER_DAY + i128::from(micros)
532}
533
534/// The civil date to days since the epoch, the inverse of [`civil_from_days`].
535#[must_use]
536pub fn days_from_civil(year: i32, month: u32, day: u32) -> i32 {
537    let year = i64::from(year) - i64::from(month <= 2);
538    let era = if year >= 0 { year } else { year - 399 } / 400;
539    let year_of_era = year - era * 400;
540    let month = i64::from(month);
541    let shifted_month = if month > 2 { month - 3 } else { month + 9 };
542    let day_of_year = (153 * shifted_month + 2) / 5 + i64::from(day) - 1;
543    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
544    #[expect(clippy::cast_possible_truncation, reason = "a date in i32 range stays in i32 range")]
545    ((era * 146_097 + day_of_era - 719_468) as i32)
546}
547
548/// The date, with the era on the end of it when the year is not an anno domini one.
549///
550/// The years the arithmetic counts in are astronomical, so there is a year zero and the year
551/// before it is minus one, while the era a date prints in has no year zero and counts backwards
552/// from one. The one is the other with the sign dropped and the number shifted by one, so the
553/// astronomical year zero prints as `0001-01-01 (BC)` and minus 2020 prints as 2021 BC.
554fn write_date(f: &mut fmt::Formatter<'_>, days: i32) -> fmt::Result {
555    let (year, month, day) = civil_from_days(days);
556    if year <= 0 {
557        write!(f, "{:04}-{month:02}-{day:02} (BC)", 1 - year)
558    } else {
559        write!(f, "{year:04}-{month:02}-{day:02}")
560    }
561}
562
563/// The offset a zoned value prints with while the only session time zone rudb has is UTC.
564///
565/// A constant here rather than a formatting rule, because the rule is the time zone box's and this
566/// is the answer that rule gives for the one zone there is. The pin prints the same two characters
567/// after `SET TimeZone='UTC'`, which was measured.
568const UTC: &str = "+00";
569
570fn write_time(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
571    let seconds = micros.div_euclid(1_000_000);
572    let fraction = micros.rem_euclid(1_000_000);
573    let (hours, minutes, seconds) = (seconds / 3600, (seconds / 60) % 60, seconds % 60);
574    write!(f, "{hours:02}:{minutes:02}:{seconds:02}")?;
575    if fraction != 0 {
576        // Trailing zeros are trimmed, so a value on a millisecond boundary prints three digits.
577        let text = format!("{fraction:06}");
578        write!(f, ".{}", text.trim_end_matches('0'))?;
579    }
580    Ok(())
581}
582
583fn write_timestamp(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
584    const MICROS_PER_DAY: i64 = 86_400 * 1_000_000;
585    let days = micros.div_euclid(MICROS_PER_DAY);
586    let within_day = micros.rem_euclid(MICROS_PER_DAY);
587    let Ok(days) = i32::try_from(days) else {
588        return f.write_str("timestamp out of range");
589    };
590    write_date(f, days)?;
591    f.write_str(" ")?;
592    write_time(f, within_day)
593}
594
595fn write_interval(f: &mut fmt::Formatter<'_>, months: i32, days: i32, micros: i64) -> fmt::Result {
596    let mut wrote = false;
597    let space = |f: &mut fmt::Formatter<'_>, wrote: &mut bool| -> fmt::Result {
598        if *wrote {
599            f.write_str(" ")?;
600        }
601        *wrote = true;
602        Ok(())
603    };
604    let (years, rest_months) = (months / 12, months % 12);
605    if years != 0 {
606        space(f, &mut wrote)?;
607        write!(f, "{years} year{}", plural(years))?;
608    }
609    if rest_months != 0 {
610        space(f, &mut wrote)?;
611        write!(f, "{rest_months} month{}", plural(rest_months))?;
612    }
613    if days != 0 {
614        space(f, &mut wrote)?;
615        write!(f, "{days} day{}", plural(days))?;
616    }
617    if micros != 0 || !wrote {
618        space(f, &mut wrote)?;
619        if micros < 0 {
620            f.write_str("-")?;
621        }
622        write_time(f, micros.abs())?;
623    }
624    Ok(())
625}
626
627fn plural(n: i32) -> &'static str {
628    if n == 1 || n == -1 { "" } else { "s" }
629}
630
631#[cfg(test)]
632mod tests {
633    use super::{Value, civil_from_days, days_from_civil};
634    use crate::types::LogicalType;
635
636    #[test]
637    fn a_value_knows_its_own_type() {
638        assert_eq!(Value::Integer(1).logical_type(), LogicalType::Integer);
639        assert_eq!(Value::Null.logical_type(), LogicalType::Null);
640        let list = Value::List { element: LogicalType::Varchar, values: Vec::new() };
641        // The element type is carried rather than inferred, which is why an empty list still
642        // knows what it is empty of.
643        assert_eq!(list.logical_type(), LogicalType::list(LogicalType::Varchar));
644    }
645
646    #[test]
647    fn the_date_conversion_is_its_own_inverse() {
648        // Every day from 1600 to 2400, which covers the Gregorian corrections and both signs of
649        // the era arithmetic. Cheap enough to be exhaustive, so it is exhaustive.
650        for days in days_from_civil(1600, 1, 1)..days_from_civil(2400, 1, 1) {
651            let (year, month, day) = civil_from_days(days);
652            assert_eq!(days_from_civil(year, month, day), days, "{year}-{month}-{day}");
653        }
654    }
655
656    #[test]
657    fn the_epoch_is_where_it_should_be() {
658        assert_eq!(days_from_civil(1970, 1, 1), 0);
659        assert_eq!(civil_from_days(0), (1970, 1, 1));
660        assert_eq!(Value::Date(0).to_string(), "1970-01-01");
661        assert_eq!(Value::Date(19_723).to_string(), "2024-01-01");
662        assert_eq!(Value::Date(19_737).to_string(), "2024-01-15");
663    }
664
665    #[test]
666    fn a_leap_day_is_a_day() {
667        assert_eq!(civil_from_days(days_from_civil(2024, 2, 29)), (2024, 2, 29));
668        // 1900 was not a leap year and 2000 was, which is the pair every naive implementation
669        // gets wrong in one direction or the other.
670        assert_eq!(days_from_civil(1900, 3, 1) - days_from_civil(1900, 2, 28), 1);
671        assert_eq!(days_from_civil(2000, 3, 1) - days_from_civil(2000, 2, 28), 2);
672    }
673
674    #[test]
675    fn a_year_at_or_before_zero_prints_in_the_era_before_christ() {
676        let date = |year, month, day| Value::Date(days_from_civil(year, month, day)).to_string();
677        // The year one is the first anno domini one and the year before it is one BC, so the day
678        // after `0001-12-31 (BC)` is `0001-01-01` with no year zero in between.
679        assert_eq!(date(1, 1, 1), "0001-01-01");
680        assert_eq!(date(0, 1, 1), "0001-01-01 (BC)");
681        assert_eq!(date(0, 12, 31), "0001-12-31 (BC)");
682        assert_eq!(date(-1, 1, 1), "0002-01-01 (BC)");
683        assert_eq!(date(-2020, 3, 4), "2021-03-04 (BC)");
684        let timestamp = |year, month, day| {
685            Value::Timestamp(i64::from(days_from_civil(year, month, day)) * 86_400 * 1_000_000)
686                .to_string()
687        };
688        assert_eq!(timestamp(0, 1, 1), "0001-01-01 (BC) 00:00:00");
689    }
690
691    #[test]
692    fn times_print_with_the_trailing_zeros_trimmed() {
693        assert_eq!(Value::Time(0).to_string(), "00:00:00");
694        assert_eq!(Value::Time(3_723_000_000).to_string(), "01:02:03");
695        assert_eq!(Value::Time(3_723_500_000).to_string(), "01:02:03.5");
696        assert_eq!(Value::Time(3_723_000_001).to_string(), "01:02:03.000001");
697    }
698
699    #[test]
700    fn a_timestamp_before_the_epoch_borrows_from_the_day() {
701        // The whole reason this uses div_euclid rather than a plain divide. A negative microsecond
702        // count is the previous day at a positive time, not the next day at a negative one.
703        assert_eq!(Value::Timestamp(-1).to_string(), "1969-12-31 23:59:59.999999");
704        assert_eq!(Value::Timestamp(0).to_string(), "1970-01-01 00:00:00");
705    }
706
707    #[test]
708    fn a_decimal_prints_at_its_scale() {
709        let d = |unscaled, scale| Value::Decimal { unscaled, width: 18, scale }.to_string();
710        assert_eq!(d(1234, 2), "12.34");
711        assert_eq!(d(-1234, 2), "-12.34");
712        assert_eq!(d(5, 3), "0.005");
713        assert_eq!(d(-5, 3), "-0.005");
714        assert_eq!(d(1234, 0), "1234");
715        assert_eq!(d(1_000_000, 6), "1.000000");
716    }
717
718    #[test]
719    fn a_float_keeps_the_point_that_says_it_is_one() {
720        assert_eq!(Value::Double(1.0).to_string(), "1.0");
721        assert_eq!(Value::Double(-3.0).to_string(), "-3.0");
722        assert_eq!(Value::Double(1.5).to_string(), "1.5");
723        assert_eq!(Value::Double(-0.0).to_string(), "-0.0");
724        assert_eq!(Value::Float(0.5).to_string(), "0.5");
725    }
726
727    #[test]
728    fn a_float_is_printed_from_its_own_width_rather_than_widened_first() {
729        // 0.1f32 as an f64 is 0.10000000149011612, and printing that would be a real bug rather
730        // than a rounding difference, so this is the test that pins it.
731        assert_eq!(Value::Float(0.1).to_string(), "0.1");
732        assert_eq!(Value::Float(1.0).to_string(), "1.0");
733    }
734
735    #[test]
736    fn a_float_switches_to_an_exponent_where_duckdb_switches() {
737        assert_eq!(Value::Double(1e15).to_string(), "1000000000000000.0");
738        assert_eq!(Value::Double(1e16).to_string(), "1e+16");
739        assert_eq!(Value::Double(1e20).to_string(), "1e+20");
740        assert_eq!(Value::Double(1e-4).to_string(), "0.0001");
741        assert_eq!(Value::Double(1e-5).to_string(), "1e-05");
742        assert_eq!(Value::Double(1.234_567_890_123_456_8e17).to_string(), "1.2345678901234568e+17");
743    }
744
745    #[test]
746    fn a_float_that_is_not_a_number_says_so_the_way_duckdb_says_it() {
747        assert_eq!(Value::Double(f64::INFINITY).to_string(), "inf");
748        assert_eq!(Value::Double(f64::NEG_INFINITY).to_string(), "-inf");
749        assert_eq!(Value::Double(f64::NAN).to_string(), "nan");
750        // A nan carries a sign bit and DuckDB prints it, per #266. Written as a negation of a nan
751        // rather than as the nan an invalid operation produces, because which one of those the
752        // hardware hands back is the hardware's business: x86 sets the bit on `0.0 / 0.0` and
753        // aarch64 does not, and this is about the printing.
754        assert_eq!(Value::Double(-f64::NAN).to_string(), "-nan");
755        assert_eq!(Value::Float(-f32::NAN).to_string(), "-nan");
756    }
757
758    #[test]
759    fn an_interval_keeps_months_days_and_micros_apart() {
760        let i = |months, days, micros| Value::Interval { months, days, micros }.to_string();
761        assert_eq!(i(14, 3, 3_723_000_000), "1 year 2 months 3 days 01:02:03");
762        assert_eq!(i(1, 0, 0), "1 month");
763        assert_eq!(i(0, 0, 0), "00:00:00");
764        assert_eq!(i(0, 0, -1_000_000), "-00:00:01");
765    }
766
767    #[test]
768    fn a_blob_escapes_what_is_not_printable() {
769        assert_eq!(Value::Blob(b"ok".to_vec()).to_string(), "ok");
770        assert_eq!(Value::Blob(vec![0, 1, b'a']).to_string(), "\\x00\\x01a");
771        assert_eq!(Value::Blob(vec![0x7f, 0xff]).to_string(), "\\x7F\\xFF");
772        // The three printable ones DuckDB escapes anyway, and the neighbours that it does not.
773        assert_eq!(Value::Blob(br#"'"\"#.to_vec()).to_string(), "\\x27\\x22\\x5C");
774        assert_eq!(Value::Blob(b" &`~".to_vec()).to_string(), " &`~");
775    }
776
777    #[test]
778    fn a_limit_that_does_not_fit_is_none_rather_than_clamped() {
779        assert_eq!(Value::Integer(5).as_i64(), Some(5));
780        assert_eq!(Value::UBigInt(u64::MAX).as_i64(), None);
781        assert_eq!(Value::Varchar("5".into()).as_i64(), None);
782    }
783
784    #[test]
785    fn a_footprint_is_the_value_plus_what_it_owns() {
786        let bare = Value::Integer(1).footprint();
787        assert_eq!(bare, size_of::<Value>(), "a number owns nothing");
788        assert_eq!(
789            Value::Boolean(true).footprint(),
790            bare,
791            "the enum is one width whatever is in it"
792        );
793        let text = "a string long enough to be on the heap in any implementation".to_string();
794        assert_eq!(Value::Varchar(text.clone()).footprint(), bare + text.capacity());
795        let list = Value::List {
796            element: LogicalType::Varchar,
797            values: vec![Value::Varchar(text.clone())],
798        };
799        // The list itself, the one slot in its vector, and the bytes the string in that slot owns.
800        // The slot is counted once: an element does not carry its own enum on top of the slot it
801        // sits in.
802        assert_eq!(list.footprint(), bare + size_of::<Value>() + text.capacity());
803    }
804
805    /// The reason the two types in a map arm are boxed, written as a test so that unboxing them fails
806    /// here rather than showing up as a memory number nobody can account for. Sixty four bytes is what
807    /// the list arm needs and there is no arm that needs more.
808    #[test]
809    fn a_value_is_sixty_four_bytes_and_a_map_did_not_widen_it() {
810        assert_eq!(size_of::<Value>(), 64);
811        let entries = vec![(Value::Varchar("a".to_string()), Value::Varchar("b".to_string()))];
812        let map = Value::map(LogicalType::Varchar, LogicalType::Varchar, entries);
813        // The map itself, the two boxed types, the one pair slot which is two values wide, and the one
814        // byte each of the two strings owns.
815        assert_eq!(
816            map.footprint(),
817            size_of::<Value>() + 2 * size_of::<LogicalType>() + 2 * size_of::<Value>() + 2
818        );
819        assert_eq!(
820            map.logical_type(),
821            LogicalType::map(LogicalType::Varchar, LogicalType::Varchar)
822        );
823    }
824}