uqa_sql/expr/
temporal_input.rs1use std::{cell::Cell, marker::PhantomData};
10use uqa_core::{TemporalDateOrder, TemporalIntervalStyle};
11
12thread_local! {
13 static DATE_ORDER: Cell<TemporalDateOrder> = const { Cell::new(TemporalDateOrder::MonthDayYear) };
14 static INTERVAL_STYLE: Cell<TemporalIntervalStyle> = const { Cell::new(TemporalIntervalStyle::Postgres) };
15}
16
17#[must_use]
18pub fn temporal_interval_style() -> TemporalIntervalStyle {
19 INTERVAL_STYLE.with(Cell::get)
20}
21
22pub struct IntervalStyleScope {
24 previous: TemporalIntervalStyle,
25 thread: PhantomData<std::rc::Rc<()>>,
26}
27
28impl IntervalStyleScope {
29 #[must_use]
30 pub fn enter(style: TemporalIntervalStyle) -> Self {
31 Self {
32 previous: INTERVAL_STYLE.with(|current| current.replace(style)),
33 thread: PhantomData,
34 }
35 }
36}
37
38impl Drop for IntervalStyleScope {
39 fn drop(&mut self) {
40 INTERVAL_STYLE.with(|current| current.set(self.previous));
41 }
42}
43
44#[must_use]
45pub fn temporal_date_order() -> TemporalDateOrder {
46 DATE_ORDER.with(Cell::get)
47}
48
49pub struct DateOrderScope {
51 previous: TemporalDateOrder,
52 thread: PhantomData<std::rc::Rc<()>>,
53}
54
55impl DateOrderScope {
56 #[must_use]
57 pub fn enter(order: TemporalDateOrder) -> Self {
58 Self {
59 previous: DATE_ORDER.with(|current| current.replace(order)),
60 thread: PhantomData,
61 }
62 }
63}
64
65impl Drop for DateOrderScope {
66 fn drop(&mut self) {
67 DATE_ORDER.with(|current| current.set(self.previous));
68 }
69}
70
71#[derive(Clone, Copy)]
73pub struct TemporalInputContext {
74 pub transaction_clock_micros: Option<i64>,
75 pub date_order: TemporalDateOrder,
76 pub interval_style: TemporalIntervalStyle,
77}
78
79impl TemporalInputContext {
80 #[must_use]
81 pub fn current() -> Self {
82 Self {
83 transaction_clock_micros: super::transaction_clock_micros(),
84 date_order: temporal_date_order(),
85 interval_style: temporal_interval_style(),
86 }
87 }
88
89 #[must_use]
90 pub fn enter(self) -> TemporalInputScope {
91 TemporalInputScope {
92 _clock: self
93 .transaction_clock_micros
94 .map(super::TransactionClockScope::enter),
95 _order: DateOrderScope::enter(self.date_order),
96 _interval: IntervalStyleScope::enter(self.interval_style),
97 }
98 }
99}
100
101pub struct TemporalInputScope {
102 _clock: Option<super::TransactionClockScope>,
103 _order: DateOrderScope,
104 _interval: IntervalStyleScope,
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use uqa_core::Value;
111
112 #[test]
113 fn nested_input_contexts_restore_clocks_and_orders_after_failure() {
114 let _caller = TemporalInputContext {
115 transaction_clock_micros: Some(10),
116 date_order: TemporalDateOrder::DayMonthYear,
117 interval_style: TemporalIntervalStyle::SqlStandard,
118 }
119 .enter();
120 {
121 let _nested = TemporalInputContext {
122 transaction_clock_micros: Some(20),
123 date_order: TemporalDateOrder::YearMonthDay,
124 interval_style: TemporalIntervalStyle::Iso8601,
125 }
126 .enter();
127 assert_eq!(super::super::transaction_clock_micros(), Some(20));
128 assert_eq!(temporal_interval_style(), TemporalIntervalStyle::Iso8601);
129 assert!(crate::expr::cast_value(&Value::Str("13/02/2020".into()), "date").is_err());
130 }
131 assert_eq!(super::super::transaction_clock_micros(), Some(10));
132 assert_eq!(temporal_date_order(), TemporalDateOrder::DayMonthYear);
133 assert_eq!(
134 temporal_interval_style(),
135 TemporalIntervalStyle::SqlStandard
136 );
137 let Value::Temporal(value) =
138 crate::expr::cast_value(&Value::Str("02/03/2020".into()), "date").unwrap()
139 else {
140 panic!("date")
141 };
142 assert_eq!(value.to_sql_string(), "2020-03-02");
143 }
144}