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