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 input context for analysis and worker execution. Guards restore the caller's clock and date order on every exit.
8
9use std::{cell::Cell, marker::PhantomData};
10use uqa_core::TemporalDateOrder;
11
12thread_local! {
13    static DATE_ORDER: Cell<TemporalDateOrder> = const { Cell::new(TemporalDateOrder::MonthDayYear) };
14}
15
16#[must_use]
17pub fn temporal_date_order() -> TemporalDateOrder {
18    DATE_ORDER.with(Cell::get)
19}
20
21/// Restore the calling thread's date order when the input scope ends.
22pub struct DateOrderScope {
23    previous: TemporalDateOrder,
24    thread: PhantomData<std::rc::Rc<()>>,
25}
26
27impl DateOrderScope {
28    #[must_use]
29    pub fn enter(order: TemporalDateOrder) -> Self {
30        Self {
31            previous: DATE_ORDER.with(|current| current.replace(order)),
32            thread: PhantomData,
33        }
34    }
35}
36
37impl Drop for DateOrderScope {
38    fn drop(&mut self) {
39        DATE_ORDER.with(|current| current.set(self.previous));
40    }
41}
42
43/// A copyable context passed from a statement to its workers.
44#[derive(Clone, Copy)]
45pub struct TemporalInputContext {
46    pub transaction_clock_micros: Option<i64>,
47    pub date_order: TemporalDateOrder,
48}
49
50impl TemporalInputContext {
51    #[must_use]
52    pub fn current() -> Self {
53        Self {
54            transaction_clock_micros: super::transaction_clock_micros(),
55            date_order: temporal_date_order(),
56        }
57    }
58
59    #[must_use]
60    pub fn enter(self) -> TemporalInputScope {
61        TemporalInputScope {
62            _clock: self
63                .transaction_clock_micros
64                .map(super::TransactionClockScope::enter),
65            _order: DateOrderScope::enter(self.date_order),
66        }
67    }
68}
69
70pub struct TemporalInputScope {
71    _clock: Option<super::TransactionClockScope>,
72    _order: DateOrderScope,
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use uqa_core::Value;
79
80    #[test]
81    fn nested_input_contexts_restore_clocks_and_orders_after_failure() {
82        let _caller = TemporalInputContext {
83            transaction_clock_micros: Some(10),
84            date_order: TemporalDateOrder::DayMonthYear,
85        }
86        .enter();
87        {
88            let _nested = TemporalInputContext {
89                transaction_clock_micros: Some(20),
90                date_order: TemporalDateOrder::YearMonthDay,
91            }
92            .enter();
93            assert_eq!(super::super::transaction_clock_micros(), Some(20));
94            assert!(crate::expr::cast_value(&Value::Str("13/02/2020".into()), "date").is_err());
95        }
96        assert_eq!(super::super::transaction_clock_micros(), Some(10));
97        assert_eq!(temporal_date_order(), TemporalDateOrder::DayMonthYear);
98        let Value::Temporal(value) =
99            crate::expr::cast_value(&Value::Str("02/03/2020".into()), "date").unwrap()
100        else {
101            panic!("date")
102        };
103        assert_eq!(value.to_sql_string(), "2020-03-02");
104    }
105}