Skip to main content

uqa_sql/expr/
temporal_input.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Temporal context for analysis and worker execution. Guards restore the caller's clock, date order and interval input/output style on every exit.
8
9use 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
22/// Restore the invoking thread's interval input/output style on every exit.
23pub 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
49/// Restore the calling thread's date order when the input scope ends.
50pub 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/// A copyable context passed from a statement to its workers.
72#[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}