Skip to main content

substrait_explain/
precision.rs

1//! Sub-second precision for literal values, shared by the parser and textifier.
2
3use std::fmt;
4
5/// A sub-second precision that names a unit: the decimal exponent Substrait
6/// stores, and the duration suffix that writes it.
7///
8/// Substrait allows any precision from 0 to 12 on a *type*, but a literal
9/// *value* has to be written down, and only these five have a unit to write it
10/// in. Constructing a `SupportedPrecision` checks that once, so code holding one
11/// can convert without re-checking.
12///
13/// Not every literal supports every variant: `chrono`-backed literals
14/// (timestamp, time) top out at nanoseconds, so they reject `Picoseconds`.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub(crate) enum SupportedPrecision {
17    Seconds,      // 0
18    Milliseconds, // 3
19    Microseconds, // 6
20    Nanoseconds,  // 9
21    Picoseconds,  // 12
22}
23
24impl SupportedPrecision {
25    /// The Substrait precision unit exponent (`0`, `3`, `6`, `9`, or `12`).
26    pub fn units(self) -> i32 {
27        match self {
28            SupportedPrecision::Seconds => 0,
29            SupportedPrecision::Milliseconds => 3,
30            SupportedPrecision::Microseconds => 6,
31            SupportedPrecision::Nanoseconds => 9,
32            SupportedPrecision::Picoseconds => 12,
33        }
34    }
35
36    /// Returns `None` for a precision with no unit to write it in.
37    pub fn from_units(units: i32) -> Option<Self> {
38        match units {
39            0 => Some(SupportedPrecision::Seconds),
40            3 => Some(SupportedPrecision::Milliseconds),
41            6 => Some(SupportedPrecision::Microseconds),
42            9 => Some(SupportedPrecision::Nanoseconds),
43            12 => Some(SupportedPrecision::Picoseconds),
44            _ => None,
45        }
46    }
47
48    /// The duration-string suffix for sub-seconds at this precision.
49    ///
50    /// `Seconds` has none: at precision 0 there is no sub-second component.
51    pub fn subsecond_unit(self) -> Option<&'static str> {
52        match self {
53            SupportedPrecision::Seconds => None,
54            SupportedPrecision::Milliseconds => Some("ms"),
55            SupportedPrecision::Microseconds => Some("us"),
56            SupportedPrecision::Nanoseconds => Some("ns"),
57            SupportedPrecision::Picoseconds => Some("ps"),
58        }
59    }
60
61    /// The precision implied by a duration-string sub-second suffix.
62    pub fn from_subsecond_unit(unit: &str) -> Option<Self> {
63        match unit {
64            "ms" => Some(SupportedPrecision::Milliseconds),
65            "us" => Some(SupportedPrecision::Microseconds),
66            "ns" => Some(SupportedPrecision::Nanoseconds),
67            "ps" => Some(SupportedPrecision::Picoseconds),
68            _ => None,
69        }
70    }
71}
72
73impl fmt::Display for SupportedPrecision {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "{}", self.units())
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_units_roundtrip() {
85        for units in [0, 3, 6, 9, 12] {
86            let precision = SupportedPrecision::from_units(units).unwrap();
87            assert_eq!(precision.units(), units);
88        }
89        // In range for a type, but with no unit to write a value in.
90        assert_eq!(SupportedPrecision::from_units(4), None);
91        assert_eq!(SupportedPrecision::from_units(13), None);
92        assert_eq!(SupportedPrecision::from_units(-1), None);
93    }
94
95    #[test]
96    fn test_subsecond_unit_roundtrip() {
97        for unit in ["ms", "us", "ns", "ps"] {
98            let precision = SupportedPrecision::from_subsecond_unit(unit).unwrap();
99            assert_eq!(precision.subsecond_unit(), Some(unit));
100        }
101        // Precision 0 has no sub-second component, and "s" is not one.
102        assert_eq!(SupportedPrecision::Seconds.subsecond_unit(), None);
103        assert_eq!(SupportedPrecision::from_subsecond_unit("s"), None);
104    }
105}