uqa_sql/expr/
current_time.rs1use std::cell::Cell;
10use std::marker::PhantomData;
11use std::rc::Rc;
12
13use super::{age_between, coerce_temporal, EvalContext, Result, SQLError, TemporalValue, Value};
14
15thread_local! {
16 static TRANSACTION_CLOCK: Cell<Option<i64>> = const { Cell::new(None) };
18}
19
20#[must_use]
22pub fn clock_timestamp_micros() -> i64 {
23 chrono::Utc::now().timestamp_micros()
24}
25
26#[must_use]
28pub fn transaction_clock_micros() -> Option<i64> {
29 TRANSACTION_CLOCK.get()
30}
31
32#[must_use]
34pub(crate) fn transaction_timestamp_or_clock() -> i64 {
35 transaction_clock_micros().unwrap_or_else(clock_timestamp_micros)
36}
37
38pub struct TransactionClockScope {
40 previous: Option<i64>,
41 _thread: PhantomData<Rc<()>>,
42}
43
44impl TransactionClockScope {
45 #[must_use]
46 pub fn enter(micros: i64) -> Self {
47 Self {
48 previous: TRANSACTION_CLOCK.replace(Some(micros)),
49 _thread: PhantomData,
50 }
51 }
52}
53
54impl Drop for TransactionClockScope {
55 fn drop(&mut self) {
56 TRANSACTION_CLOCK.set(self.previous);
57 }
58}
59
60pub(super) fn eval_current_time(
61 name: &str,
62 args: &[Value],
63 context: Option<&EvalContext<'_>>,
64) -> Option<Result<Value>> {
65 if !(matches!(
66 name,
67 "now"
68 | "transaction_timestamp"
69 | "statement_timestamp"
70 | "current_timestamp"
71 | "current_date"
72 | "current_time"
73 | "localtime"
74 | "localtimestamp"
75 ) || name == "age" && args.len() == 1)
76 {
77 return None;
78 }
79 Some((|| {
80 if name == "age" && matches!(args, [Value::Null]) {
81 return Ok(Value::Null);
82 }
83 if name != "age" && !args.is_empty() {
84 return Err(SQLError::BadArity {
85 name: name.into(),
86 expected: "0".into(),
87 actual: args.len(),
88 });
89 }
90 let statement = name == "statement_timestamp";
91 let micros = context
92 .and_then(|context| context.engine)
93 .and_then(|engine| {
94 if statement {
95 engine.statement_timestamp_micros()
96 } else {
97 engine.transaction_timestamp_micros()
98 }
99 })
100 .or_else(|| (!statement).then(transaction_clock_micros).flatten())
101 .unwrap_or_else(clock_timestamp_micros);
102 const MICROS_PER_DAY: i64 = 86_400_000_000;
103 let value = match name {
104 "current_date" => TemporalValue::Date {
105 days: i32::try_from(micros.div_euclid(MICROS_PER_DAY)).map_err(|_| {
106 SQLError::Internal("current date exceeds its day carrier".into())
107 })?,
108 },
109 "current_time" => TemporalValue::TimeTz {
110 micros: micros.rem_euclid(MICROS_PER_DAY),
111 offset_minutes: 0,
112 },
113 "localtime" => TemporalValue::Time {
114 micros: micros.rem_euclid(MICROS_PER_DAY),
115 },
116 "localtimestamp" => TemporalValue::Timestamp { micros },
117 "age" => {
118 return age_between(
119 &TemporalValue::Timestamp {
120 micros: micros.div_euclid(MICROS_PER_DAY) * MICROS_PER_DAY,
121 },
122 &coerce_temporal(&args[0])?,
123 );
124 }
125 _ => TemporalValue::TimestampTz { micros },
126 };
127 Ok(Value::Temporal(value))
128 })())
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn temporal_coercion_uses_the_transaction_clock() {
137 let _clock = TransactionClockScope::enter(90_123_456_789);
138 for (input, micros) in [
139 ("now", 90_123_456_789),
140 ("today", 86_400_000_000),
141 ("tomorrow", 172_800_000_000),
142 ("yesterday", 0),
143 ] {
144 assert_eq!(
145 coerce_temporal(&Value::Str(input.into())).unwrap(),
146 TemporalValue::Timestamp { micros },
147 "{input}"
148 );
149 }
150 }
151
152 #[test]
153 fn temporal_comparisons_use_the_transaction_clock() {
154 use crate::expr::{compare_with_control, values_equal_with_control};
155 use uqa_core::memory::ProductionControl;
156 let _clock = TransactionClockScope::enter(90_123_456_789);
157 let control = ProductionControl::uncontrolled();
158 let now = Value::Str("now".into());
159 for temporal in [
160 TemporalValue::Date { days: 1 },
161 TemporalValue::Time {
162 micros: 3_723_456_789,
163 },
164 TemporalValue::TimeTz {
165 micros: 3_723_456_789,
166 offset_minutes: 0,
167 },
168 TemporalValue::Timestamp {
169 micros: 90_123_456_789,
170 },
171 TemporalValue::TimestampTz {
172 micros: 90_123_456_789,
173 },
174 ] {
175 let value = Value::Temporal(temporal);
176 for (left, right) in [(&value, &now), (&now, &value)] {
177 assert!(values_equal_with_control(left, right, &control).unwrap());
178 assert_eq!(
179 compare_with_control(left, right, &control).unwrap(),
180 std::cmp::Ordering::Equal
181 );
182 }
183 }
184 }
185
186 #[test]
187 fn temporal_range_bounds_use_the_transaction_clock() {
188 use crate::ast::RangeSubtype;
189 use crate::expr::parse_range;
190 let _clock = TransactionClockScope::enter(90_123_456_789);
191 for (kind, expected) in [
192 (
193 RangeSubtype::Timestamp,
194 TemporalValue::Timestamp {
195 micros: 90_123_456_789,
196 },
197 ),
198 (
199 RangeSubtype::TimestampTz,
200 TemporalValue::TimestampTz {
201 micros: 90_123_456_789,
202 },
203 ),
204 ] {
205 let range = parse_range("[now,now]", kind).unwrap();
206 assert_eq!(range.lower(), Some(&Value::Temporal(expected.clone())));
207 assert_eq!(range.upper(), Some(&Value::Temporal(expected)));
208 assert!(!range.is_empty());
209 }
210 let range = parse_range("[today,tomorrow)", RangeSubtype::Date).unwrap();
211 assert_eq!(
212 range.lower(),
213 Some(&Value::Temporal(TemporalValue::Date { days: 1 }))
214 );
215 assert_eq!(
216 range.upper(),
217 Some(&Value::Temporal(TemporalValue::Date { days: 2 }))
218 );
219 }
220
221 #[test]
222 fn transaction_clock_scopes_nest_and_restore() {
223 assert_eq!(transaction_clock_micros(), None);
224 {
225 let _outer = TransactionClockScope::enter(10);
226 assert_eq!(transaction_clock_micros(), Some(10));
227 assert_eq!(transaction_timestamp_or_clock(), 10);
228 {
229 let _inner = TransactionClockScope::enter(20);
230 assert_eq!(transaction_clock_micros(), Some(20));
231 }
232 assert_eq!(transaction_clock_micros(), Some(10));
233 }
234 assert_eq!(transaction_clock_micros(), None);
235 assert!(transaction_timestamp_or_clock() > 1_700_000_000_000_000);
236 }
237}