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
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) and decimals (their `Display` form).
/// 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)),
_ => 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")),
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}"),
// `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)
}