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    /// How many bytes this value takes, counting what it owns on the heap.
97    ///
98    /// What the memory limit charges for a value held in a buffer. It is the enum itself plus the
99    /// string, the blob, the list or the struct behind it, and it counts capacity rather than
100    /// length, because capacity is what was taken from the allocator and a string built by pushing
101    /// bytes usually has more of it than it needs.
102    ///
103    /// The enum is as wide as its widest arm whatever is in it, so a `BOOLEAN` costs the same as a
104    /// `HUGEINT` here. That is not a rounding error, it is the layout: a row of booleans held as
105    /// values really does cost that.
106    #[must_use]
107    pub fn footprint(&self) -> usize {
108        size_of::<Self>() + self.heap()
109    }
110
111    /// What this value owns beyond its own bytes.
112    fn heap(&self) -> usize {
113        match self {
114            Self::Varchar(text) => text.capacity(),
115            Self::Blob(bytes) => bytes.capacity(),
116            Self::List { values, .. } => {
117                values.capacity() * size_of::<Self>() + values.iter().map(Self::heap).sum::<usize>()
118            }
119            Self::Struct(fields) => {
120                fields.capacity() * size_of::<(String, Self)>()
121                    + fields
122                        .iter()
123                        .map(|(name, value)| name.capacity() + value.heap())
124                        .sum::<usize>()
125            }
126            _ => 0,
127        }
128    }
129
130    /// Whether this is `NULL`.
131    #[must_use]
132    pub fn is_null(&self) -> bool {
133        matches!(self, Self::Null)
134    }
135
136    /// The type of this value.
137    #[must_use]
138    pub fn logical_type(&self) -> LogicalType {
139        match self {
140            Self::Null => LogicalType::Null,
141            Self::Boolean(_) => LogicalType::Boolean,
142            Self::TinyInt(_) => LogicalType::TinyInt,
143            Self::SmallInt(_) => LogicalType::SmallInt,
144            Self::Integer(_) => LogicalType::Integer,
145            Self::BigInt(_) => LogicalType::BigInt,
146            Self::HugeInt(_) => LogicalType::HugeInt,
147            Self::UTinyInt(_) => LogicalType::UTinyInt,
148            Self::USmallInt(_) => LogicalType::USmallInt,
149            Self::UInteger(_) => LogicalType::UInteger,
150            Self::UBigInt(_) => LogicalType::UBigInt,
151            Self::UHugeInt(_) => LogicalType::UHugeInt,
152            Self::Float(_) => LogicalType::Float,
153            Self::Double(_) => LogicalType::Double,
154            Self::Decimal { width, scale, .. } => {
155                LogicalType::Decimal { width: *width, scale: *scale }
156            }
157            Self::Varchar(_) => LogicalType::Varchar,
158            Self::Blob(_) => LogicalType::Blob,
159            Self::Date(_) => LogicalType::Date,
160            Self::Time(_) => LogicalType::Time,
161            Self::Timestamp(_) => LogicalType::Timestamp,
162            Self::Interval { .. } => LogicalType::Interval,
163            Self::List { element, .. } => LogicalType::list(element.clone()),
164            Self::Struct(fields) => LogicalType::Struct(
165                fields
166                    .iter()
167                    .map(|(name, value)| crate::types::Field::new(name, value.logical_type()))
168                    .collect(),
169            ),
170        }
171    }
172
173    /// The value as an `i64`, for the integer types that fit in one.
174    ///
175    /// Used by the planner for the places where a literal has to be a small integer, `LIMIT` and
176    /// `OFFSET` being the obvious ones. Returns `None` rather than saturating, because a `LIMIT`
177    /// that silently became `i64::MAX` is worse than an error.
178    #[must_use]
179    pub fn as_i64(&self) -> Option<i64> {
180        match *self {
181            Self::TinyInt(v) => Some(i64::from(v)),
182            Self::SmallInt(v) => Some(i64::from(v)),
183            Self::Integer(v) => Some(i64::from(v)),
184            Self::BigInt(v) => Some(v),
185            Self::UTinyInt(v) => Some(i64::from(v)),
186            Self::USmallInt(v) => Some(i64::from(v)),
187            Self::UInteger(v) => Some(i64::from(v)),
188            Self::UBigInt(v) => i64::try_from(v).ok(),
189            Self::HugeInt(v) => i64::try_from(v).ok(),
190            Self::UHugeInt(v) => i64::try_from(v).ok(),
191            _ => None,
192        }
193    }
194
195    /// The value as a `bool`, for a `BOOLEAN` and nothing else.
196    #[must_use]
197    pub fn as_bool(&self) -> Option<bool> {
198        match *self {
199            Self::Boolean(v) => Some(v),
200            _ => None,
201        }
202    }
203
204    /// The value as a string slice, for a `VARCHAR` and nothing else.
205    #[must_use]
206    pub fn as_str(&self) -> Option<&str> {
207        match self {
208            Self::Varchar(v) => Some(v),
209            _ => None,
210        }
211    }
212}
213
214impl fmt::Display for Value {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        match self {
217            Self::Null => f.write_str("NULL"),
218            Self::Boolean(v) => f.write_str(if *v { "true" } else { "false" }),
219            Self::TinyInt(v) => write!(f, "{v}"),
220            Self::SmallInt(v) => write!(f, "{v}"),
221            Self::Integer(v) => write!(f, "{v}"),
222            Self::BigInt(v) => write!(f, "{v}"),
223            Self::HugeInt(v) => write!(f, "{v}"),
224            Self::UTinyInt(v) => write!(f, "{v}"),
225            Self::USmallInt(v) => write!(f, "{v}"),
226            Self::UInteger(v) => write!(f, "{v}"),
227            Self::UBigInt(v) => write!(f, "{v}"),
228            Self::UHugeInt(v) => write!(f, "{v}"),
229            Self::Float(v) => write_float(f, *v),
230            Self::Double(v) => write_float(f, *v),
231            Self::Decimal { unscaled, scale, .. } => write_decimal(f, *unscaled, *scale),
232            Self::Varchar(v) => f.write_str(v),
233            Self::Blob(v) => write_blob(f, v),
234            Self::Date(v) => write_date(f, *v),
235            Self::Time(v) => write_time(f, *v),
236            Self::Timestamp(v) => write_timestamp(f, *v),
237            Self::Interval { months, days, micros } => write_interval(f, *months, *days, *micros),
238            Self::List { values, .. } => {
239                f.write_str("[")?;
240                for (index, value) in values.iter().enumerate() {
241                    if index > 0 {
242                        f.write_str(", ")?;
243                    }
244                    write!(f, "{value}")?;
245                }
246                f.write_str("]")
247            }
248            Self::Struct(fields) => {
249                f.write_str("{")?;
250                for (index, (name, value)) in fields.iter().enumerate() {
251                    if index > 0 {
252                        f.write_str(", ")?;
253                    }
254                    write!(f, "'{name}': {value}")?;
255                }
256                f.write_str("}")
257            }
258        }
259    }
260}
261
262/// The two float types, so that one printer can serve both without going through `f64`.
263///
264/// Widening an `f32` to print it is wrong and quietly so: `0.1f32` as an `f64` is
265/// `0.10000000149011612`, and the shortest text that reads back as the same `f32` is `0.1`. DuckDB
266/// prints `0.1`, and it prints it because it formats the `float` rather than a `double` made out of
267/// one.
268trait Real: Copy + fmt::Display + fmt::LowerExp {
269    fn is_nan(self) -> bool;
270    fn is_infinite(self) -> bool;
271    fn is_sign_negative(self) -> bool;
272}
273
274impl Real for f32 {
275    fn is_nan(self) -> bool {
276        Self::is_nan(self)
277    }
278
279    fn is_infinite(self) -> bool {
280        Self::is_infinite(self)
281    }
282
283    fn is_sign_negative(self) -> bool {
284        Self::is_sign_negative(self)
285    }
286}
287
288impl Real for f64 {
289    fn is_nan(self) -> bool {
290        Self::is_nan(self)
291    }
292
293    fn is_infinite(self) -> bool {
294        Self::is_infinite(self)
295    }
296
297    fn is_sign_negative(self) -> bool {
298        Self::is_sign_negative(self)
299    }
300}
301
302/// Floats print the shortest text that reads back as the same value, laid out the way DuckDB lays
303/// it out.
304///
305/// Rust and DuckDB agree on the digits and disagree on everything around them. A float with nothing
306/// after the point keeps its `.0`, so a `DOUBLE` never looks like an integer. Anything with a
307/// decimal exponent outside `-4..16` is written in exponent form with a signed two digit exponent,
308/// so `1e16` is `1e+16` and `0.00001` is `1e-05`, while `1e15` is still written out in full. That
309/// is C's `%g` rule and it is what DuckDB's formatter implements, checked against the binary rather
310/// than read out of its source.
311fn write_float<T: Real>(f: &mut fmt::Formatter<'_>, value: T) -> fmt::Result {
312    // The sign bit and nothing else, because no comparison against a nan says anything about it.
313    // An invalid operation on x86 produces a nan with the bit set and DuckDB prints that as `-nan`,
314    // where the nan a string parses to has the bit clear and prints as `nan`. Rust prints `NaN` for
315    // both.
316    if value.is_nan() {
317        return f.write_str(if value.is_sign_negative() { "-nan" } else { "nan" });
318    }
319    if value.is_infinite() {
320        return f.write_str(if value.is_sign_negative() { "-inf" } else { "inf" });
321    }
322    let scientific = format!("{value:e}");
323    let (mantissa, exponent) = scientific.split_once('e').unwrap_or((scientific.as_str(), "0"));
324    let exponent: i32 = exponent.parse().unwrap_or(0);
325    if (-4..16).contains(&exponent) {
326        let text = format!("{value}");
327        if text.contains('.') {
328            return f.write_str(&text);
329        }
330        return write!(f, "{text}.0");
331    }
332    let sign = if exponent < 0 { '-' } else { '+' };
333    write!(f, "{mantissa}e{sign}{:02}", exponent.abs())
334}
335
336fn write_decimal(f: &mut fmt::Formatter<'_>, unscaled: i128, scale: u8) -> fmt::Result {
337    if scale == 0 {
338        return write!(f, "{unscaled}");
339    }
340    let negative = unscaled < 0;
341    // Widened before the negation so that i128::MIN does not overflow on the way to its digits.
342    let digits = unscaled.unsigned_abs().to_string();
343    let scale = usize::from(scale);
344    let (whole, fraction) = if digits.len() > scale {
345        let split = digits.len() - scale;
346        (digits[..split].to_string(), digits[split..].to_string())
347    } else {
348        ("0".to_string(), format!("{:0>scale$}", digits))
349    };
350    if negative {
351        f.write_str("-")?;
352    }
353    write!(f, "{whole}.{fraction}")
354}
355
356/// A blob prints as printable ASCII with everything else hex escaped, which is DuckDB's rule.
357///
358/// Three printable characters are escaped anyway, and they are the three that would otherwise make
359/// the printed form ambiguous: a backslash because it starts an escape, and the two quotes because
360/// the text this prints into is a string literal often enough. Every byte of all 256 was compared
361/// against DuckDB and these three were the only disagreement.
362fn write_blob(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
363    for &byte in bytes {
364        if (byte.is_ascii_graphic() || byte == b' ') && !matches!(byte, b'\\' | b'\'' | b'"') {
365            write!(f, "{}", byte as char)?;
366        } else {
367            write!(f, "\\x{byte:02X}")?;
368        }
369    }
370    Ok(())
371}
372
373/// Days since the epoch to the civil date, by Howard Hinnant's algorithm.
374///
375/// Written out rather than pulled in from a date library because it is twenty lines, because the
376/// dependency table in `spec/18-package-layout.md` is short on purpose, and because a date library
377/// that disagrees with DuckDB about a date before 1582 is a compatibility bug we would then own
378/// without being able to fix it.
379#[must_use]
380pub fn civil_from_days(days: i32) -> (i32, u32, u32) {
381    let z = i64::from(days) + 719_468;
382    let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
383    let day_of_era = z - era * 146_097;
384    let year_of_era =
385        (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
386    let year = year_of_era + era * 400;
387    let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
388    let shifted_month = (5 * day_of_year + 2) / 153;
389    let day = day_of_year - (153 * shifted_month + 2) / 5 + 1;
390    let month = if shifted_month < 10 { shifted_month + 3 } else { shifted_month - 9 };
391    let year = if month <= 2 { year + 1 } else { year };
392    #[expect(clippy::cast_possible_truncation, reason = "the ranges are 1 to 12 and 1 to 31")]
393    (year as i32, month as u32, day as u32)
394}
395
396/// The civil date to days since the epoch, the inverse of [`civil_from_days`].
397#[must_use]
398pub fn days_from_civil(year: i32, month: u32, day: u32) -> i32 {
399    let year = i64::from(year) - i64::from(month <= 2);
400    let era = if year >= 0 { year } else { year - 399 } / 400;
401    let year_of_era = year - era * 400;
402    let month = i64::from(month);
403    let shifted_month = if month > 2 { month - 3 } else { month + 9 };
404    let day_of_year = (153 * shifted_month + 2) / 5 + i64::from(day) - 1;
405    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
406    #[expect(clippy::cast_possible_truncation, reason = "a date in i32 range stays in i32 range")]
407    ((era * 146_097 + day_of_era - 719_468) as i32)
408}
409
410fn write_date(f: &mut fmt::Formatter<'_>, days: i32) -> fmt::Result {
411    let (year, month, day) = civil_from_days(days);
412    if year < 0 {
413        write!(f, "{:04}-{month:02}-{day:02} (BC)", -year + 1)
414    } else {
415        write!(f, "{year:04}-{month:02}-{day:02}")
416    }
417}
418
419fn write_time(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
420    let seconds = micros.div_euclid(1_000_000);
421    let fraction = micros.rem_euclid(1_000_000);
422    let (hours, minutes, seconds) = (seconds / 3600, (seconds / 60) % 60, seconds % 60);
423    write!(f, "{hours:02}:{minutes:02}:{seconds:02}")?;
424    if fraction != 0 {
425        // Trailing zeros are trimmed, so a value on a millisecond boundary prints three digits.
426        let text = format!("{fraction:06}");
427        write!(f, ".{}", text.trim_end_matches('0'))?;
428    }
429    Ok(())
430}
431
432fn write_timestamp(f: &mut fmt::Formatter<'_>, micros: i64) -> fmt::Result {
433    const MICROS_PER_DAY: i64 = 86_400 * 1_000_000;
434    let days = micros.div_euclid(MICROS_PER_DAY);
435    let within_day = micros.rem_euclid(MICROS_PER_DAY);
436    let Ok(days) = i32::try_from(days) else {
437        return f.write_str("timestamp out of range");
438    };
439    write_date(f, days)?;
440    f.write_str(" ")?;
441    write_time(f, within_day)
442}
443
444fn write_interval(f: &mut fmt::Formatter<'_>, months: i32, days: i32, micros: i64) -> fmt::Result {
445    let mut wrote = false;
446    let space = |f: &mut fmt::Formatter<'_>, wrote: &mut bool| -> fmt::Result {
447        if *wrote {
448            f.write_str(" ")?;
449        }
450        *wrote = true;
451        Ok(())
452    };
453    let (years, rest_months) = (months / 12, months % 12);
454    if years != 0 {
455        space(f, &mut wrote)?;
456        write!(f, "{years} year{}", plural(years))?;
457    }
458    if rest_months != 0 {
459        space(f, &mut wrote)?;
460        write!(f, "{rest_months} month{}", plural(rest_months))?;
461    }
462    if days != 0 {
463        space(f, &mut wrote)?;
464        write!(f, "{days} day{}", plural(days))?;
465    }
466    if micros != 0 || !wrote {
467        space(f, &mut wrote)?;
468        if micros < 0 {
469            f.write_str("-")?;
470        }
471        write_time(f, micros.abs())?;
472    }
473    Ok(())
474}
475
476fn plural(n: i32) -> &'static str {
477    if n == 1 || n == -1 { "" } else { "s" }
478}
479
480#[cfg(test)]
481mod tests {
482    use super::{Value, civil_from_days, days_from_civil};
483    use crate::types::LogicalType;
484
485    #[test]
486    fn a_value_knows_its_own_type() {
487        assert_eq!(Value::Integer(1).logical_type(), LogicalType::Integer);
488        assert_eq!(Value::Null.logical_type(), LogicalType::Null);
489        let list = Value::List { element: LogicalType::Varchar, values: Vec::new() };
490        // The element type is carried rather than inferred, which is why an empty list still
491        // knows what it is empty of.
492        assert_eq!(list.logical_type(), LogicalType::list(LogicalType::Varchar));
493    }
494
495    #[test]
496    fn the_date_conversion_is_its_own_inverse() {
497        // Every day from 1600 to 2400, which covers the Gregorian corrections and both signs of
498        // the era arithmetic. Cheap enough to be exhaustive, so it is exhaustive.
499        for days in days_from_civil(1600, 1, 1)..days_from_civil(2400, 1, 1) {
500            let (year, month, day) = civil_from_days(days);
501            assert_eq!(days_from_civil(year, month, day), days, "{year}-{month}-{day}");
502        }
503    }
504
505    #[test]
506    fn the_epoch_is_where_it_should_be() {
507        assert_eq!(days_from_civil(1970, 1, 1), 0);
508        assert_eq!(civil_from_days(0), (1970, 1, 1));
509        assert_eq!(Value::Date(0).to_string(), "1970-01-01");
510        assert_eq!(Value::Date(19_723).to_string(), "2024-01-01");
511        assert_eq!(Value::Date(19_737).to_string(), "2024-01-15");
512    }
513
514    #[test]
515    fn a_leap_day_is_a_day() {
516        assert_eq!(civil_from_days(days_from_civil(2024, 2, 29)), (2024, 2, 29));
517        // 1900 was not a leap year and 2000 was, which is the pair every naive implementation
518        // gets wrong in one direction or the other.
519        assert_eq!(days_from_civil(1900, 3, 1) - days_from_civil(1900, 2, 28), 1);
520        assert_eq!(days_from_civil(2000, 3, 1) - days_from_civil(2000, 2, 28), 2);
521    }
522
523    #[test]
524    fn times_print_with_the_trailing_zeros_trimmed() {
525        assert_eq!(Value::Time(0).to_string(), "00:00:00");
526        assert_eq!(Value::Time(3_723_000_000).to_string(), "01:02:03");
527        assert_eq!(Value::Time(3_723_500_000).to_string(), "01:02:03.5");
528        assert_eq!(Value::Time(3_723_000_001).to_string(), "01:02:03.000001");
529    }
530
531    #[test]
532    fn a_timestamp_before_the_epoch_borrows_from_the_day() {
533        // The whole reason this uses div_euclid rather than a plain divide. A negative microsecond
534        // count is the previous day at a positive time, not the next day at a negative one.
535        assert_eq!(Value::Timestamp(-1).to_string(), "1969-12-31 23:59:59.999999");
536        assert_eq!(Value::Timestamp(0).to_string(), "1970-01-01 00:00:00");
537    }
538
539    #[test]
540    fn a_decimal_prints_at_its_scale() {
541        let d = |unscaled, scale| Value::Decimal { unscaled, width: 18, scale }.to_string();
542        assert_eq!(d(1234, 2), "12.34");
543        assert_eq!(d(-1234, 2), "-12.34");
544        assert_eq!(d(5, 3), "0.005");
545        assert_eq!(d(-5, 3), "-0.005");
546        assert_eq!(d(1234, 0), "1234");
547        assert_eq!(d(1_000_000, 6), "1.000000");
548    }
549
550    #[test]
551    fn a_float_keeps_the_point_that_says_it_is_one() {
552        assert_eq!(Value::Double(1.0).to_string(), "1.0");
553        assert_eq!(Value::Double(-3.0).to_string(), "-3.0");
554        assert_eq!(Value::Double(1.5).to_string(), "1.5");
555        assert_eq!(Value::Double(-0.0).to_string(), "-0.0");
556        assert_eq!(Value::Float(0.5).to_string(), "0.5");
557    }
558
559    #[test]
560    fn a_float_is_printed_from_its_own_width_rather_than_widened_first() {
561        // 0.1f32 as an f64 is 0.10000000149011612, and printing that would be a real bug rather
562        // than a rounding difference, so this is the test that pins it.
563        assert_eq!(Value::Float(0.1).to_string(), "0.1");
564        assert_eq!(Value::Float(1.0).to_string(), "1.0");
565    }
566
567    #[test]
568    fn a_float_switches_to_an_exponent_where_duckdb_switches() {
569        assert_eq!(Value::Double(1e15).to_string(), "1000000000000000.0");
570        assert_eq!(Value::Double(1e16).to_string(), "1e+16");
571        assert_eq!(Value::Double(1e20).to_string(), "1e+20");
572        assert_eq!(Value::Double(1e-4).to_string(), "0.0001");
573        assert_eq!(Value::Double(1e-5).to_string(), "1e-05");
574        assert_eq!(Value::Double(1.234_567_890_123_456_8e17).to_string(), "1.2345678901234568e+17");
575    }
576
577    #[test]
578    fn a_float_that_is_not_a_number_says_so_the_way_duckdb_says_it() {
579        assert_eq!(Value::Double(f64::INFINITY).to_string(), "inf");
580        assert_eq!(Value::Double(f64::NEG_INFINITY).to_string(), "-inf");
581        assert_eq!(Value::Double(f64::NAN).to_string(), "nan");
582        // A nan carries a sign bit and DuckDB prints it, per #266. Written as a negation of a nan
583        // rather than as the nan an invalid operation produces, because which one of those the
584        // hardware hands back is the hardware's business: x86 sets the bit on `0.0 / 0.0` and
585        // aarch64 does not, and this is about the printing.
586        assert_eq!(Value::Double(-f64::NAN).to_string(), "-nan");
587        assert_eq!(Value::Float(-f32::NAN).to_string(), "-nan");
588    }
589
590    #[test]
591    fn an_interval_keeps_months_days_and_micros_apart() {
592        let i = |months, days, micros| Value::Interval { months, days, micros }.to_string();
593        assert_eq!(i(14, 3, 3_723_000_000), "1 year 2 months 3 days 01:02:03");
594        assert_eq!(i(1, 0, 0), "1 month");
595        assert_eq!(i(0, 0, 0), "00:00:00");
596        assert_eq!(i(0, 0, -1_000_000), "-00:00:01");
597    }
598
599    #[test]
600    fn a_blob_escapes_what_is_not_printable() {
601        assert_eq!(Value::Blob(b"ok".to_vec()).to_string(), "ok");
602        assert_eq!(Value::Blob(vec![0, 1, b'a']).to_string(), "\\x00\\x01a");
603        assert_eq!(Value::Blob(vec![0x7f, 0xff]).to_string(), "\\x7F\\xFF");
604        // The three printable ones DuckDB escapes anyway, and the neighbours that it does not.
605        assert_eq!(Value::Blob(br#"'"\"#.to_vec()).to_string(), "\\x27\\x22\\x5C");
606        assert_eq!(Value::Blob(b" &`~".to_vec()).to_string(), " &`~");
607    }
608
609    #[test]
610    fn a_limit_that_does_not_fit_is_none_rather_than_clamped() {
611        assert_eq!(Value::Integer(5).as_i64(), Some(5));
612        assert_eq!(Value::UBigInt(u64::MAX).as_i64(), None);
613        assert_eq!(Value::Varchar("5".into()).as_i64(), None);
614    }
615
616    #[test]
617    fn a_footprint_is_the_value_plus_what_it_owns() {
618        let bare = Value::Integer(1).footprint();
619        assert_eq!(bare, size_of::<Value>(), "a number owns nothing");
620        assert_eq!(
621            Value::Boolean(true).footprint(),
622            bare,
623            "the enum is one width whatever is in it"
624        );
625        let text = "a string long enough to be on the heap in any implementation".to_string();
626        assert_eq!(Value::Varchar(text.clone()).footprint(), bare + text.capacity());
627        let list = Value::List {
628            element: LogicalType::Varchar,
629            values: vec![Value::Varchar(text.clone())],
630        };
631        // The list itself, the one slot in its vector, and the bytes the string in that slot owns.
632        // The slot is counted once: an element does not carry its own enum on top of the slot it
633        // sits in.
634        assert_eq!(list.footprint(), bare + size_of::<Value>() + text.capacity());
635    }
636}