toasty_core/stmt/document_storage_text.rs
1use std::fmt;
2
3use crate::stmt::Value;
4
5impl Value {
6 /// The text form this value takes when rendered for document storage, or
7 /// `None` if the value has no document text form.
8 ///
9 /// Values that are stored as JSON strings inside a `#[document]` column
10 /// take this form: jiff temporal values (truncated to microseconds — the
11 /// precision the SQL temporal types hold — and formatted with *fixed*
12 /// six-digit subsecond precision), decimals, and network addresses (their
13 /// `Display` forms).
14 /// Fixed temporal precision matters on backends that compare document
15 /// leaves as plain text (SQLite has no native temporal types, so
16 /// `json_extract` comparisons are text comparisons): uniform-precision
17 /// ISO 8601 strings sort lexicographically in chronological order, while
18 /// trimmed subseconds do not (`...T00:00:00Z` sorts *after*
19 /// `...T00:00:00.000001Z`).
20 ///
21 /// Both the JSON document codec (`toasty-sql`) and the engine's document
22 /// lowering (which rewrites comparison operands to text on those
23 /// backends) render document text through this one method, so the stored
24 /// form and a bound comparison operand cannot drift apart.
25 ///
26 /// `Zoned` has no document text form: its RFC 9557 `[IANA]` annotation is
27 /// rejected at schema build.
28 pub fn document_storage_text(&self) -> Option<DocumentStorageText<'_>> {
29 match self {
30 #[cfg(feature = "jiff")]
31 Value::Timestamp(_) | Value::Date(_) | Value::Time(_) | Value::DateTime(_) => {
32 Some(DocumentStorageText(self))
33 }
34 #[cfg(feature = "rust_decimal")]
35 Value::Decimal(_) => Some(DocumentStorageText(self)),
36 #[cfg(feature = "bigdecimal")]
37 Value::BigDecimal(_) => Some(DocumentStorageText(self)),
38 #[cfg(feature = "net")]
39 Value::Cidr(_) | Value::Inet(_) | Value::MacAddr(_) | Value::MacAddr8(_) => {
40 Some(DocumentStorageText(self))
41 }
42 _ => None,
43 }
44 }
45}
46
47/// Helper struct for rendering a [`Value`]'s document storage text form.
48///
49/// Returned by [`Value::document_storage_text`]; see its documentation for
50/// the format contract. Like [`std::path::Display`], this is an opaque
51/// adapter — the only way to obtain one is the method that guarantees the
52/// value has a document text form.
53#[derive(Debug)]
54pub struct DocumentStorageText<'a>(&'a Value);
55
56impl fmt::Display for DocumentStorageText<'_> {
57 // With none of the temporal or decimal features enabled, every arm below
58 // is compiled out except the unreachable one, leaving `f` unused.
59 #[cfg_attr(
60 not(any(
61 feature = "jiff",
62 feature = "rust_decimal",
63 feature = "bigdecimal",
64 feature = "net"
65 )),
66 allow(unused_variables)
67 )]
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self.0 {
70 #[cfg(feature = "jiff")]
71 Value::Timestamp(v) => write!(f, "{:.6}", trunc_timestamp_us(*v)),
72 #[cfg(feature = "jiff")]
73 Value::Date(v) => write!(f, "{v}"),
74 #[cfg(feature = "jiff")]
75 Value::Time(v) => write!(f, "{:.6}", trunc_time_us(*v)),
76 #[cfg(feature = "jiff")]
77 Value::DateTime(v) => write!(f, "{:.6}", trunc_datetime_us(*v)),
78 #[cfg(feature = "rust_decimal")]
79 Value::Decimal(v) => write!(f, "{v}"),
80 #[cfg(feature = "bigdecimal")]
81 Value::BigDecimal(v) => write!(f, "{v}"),
82 #[cfg(feature = "net")]
83 Value::Cidr(v) => write!(f, "{v}"),
84 #[cfg(feature = "net")]
85 Value::Inet(v) => write!(f, "{v}"),
86 #[cfg(feature = "net")]
87 Value::MacAddr(v) => write!(f, "{v}"),
88 #[cfg(feature = "net")]
89 Value::MacAddr8(v) => write!(f, "{v}"),
90 // `document_storage_text` only constructs the adapter for the
91 // variants above.
92 _ => unreachable!(),
93 }
94 }
95}
96
97/// Truncate a timestamp to microsecond precision, toward zero, dropping any
98/// sub-microsecond nanoseconds. Rounding can only fail at the extreme ends of
99/// the representable range; fall back to the original value there rather than
100/// failing the whole encode.
101#[cfg(feature = "jiff")]
102fn trunc_timestamp_us(v: jiff::Timestamp) -> jiff::Timestamp {
103 v.round(
104 jiff::TimestampRound::new()
105 .smallest(jiff::Unit::Microsecond)
106 .mode(jiff::RoundMode::Trunc),
107 )
108 .unwrap_or(v)
109}
110
111/// Truncate a civil time to microsecond precision, toward zero. See
112/// [`trunc_timestamp_us`].
113#[cfg(feature = "jiff")]
114fn trunc_time_us(v: jiff::civil::Time) -> jiff::civil::Time {
115 v.round(
116 jiff::civil::TimeRound::new()
117 .smallest(jiff::Unit::Microsecond)
118 .mode(jiff::RoundMode::Trunc),
119 )
120 .unwrap_or(v)
121}
122
123/// Truncate a civil datetime to microsecond precision, toward zero. See
124/// [`trunc_timestamp_us`].
125#[cfg(feature = "jiff")]
126fn trunc_datetime_us(v: jiff::civil::DateTime) -> jiff::civil::DateTime {
127 v.round(
128 jiff::civil::DateTimeRound::new()
129 .smallest(jiff::Unit::Microsecond)
130 .mode(jiff::RoundMode::Trunc),
131 )
132 .unwrap_or(v)
133}