Skip to main content

alopex_sql/executor/evaluator/
context.rs

1use std::cell::RefCell;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use crate::executor::{EvaluationError, ExecutorError};
5use crate::storage::SqlValue;
6
7thread_local! {
8    static STATEMENT_TIMESTAMPS: RefCell<Vec<i64>> = const { RefCell::new(Vec::new()) };
9}
10
11/// Scope guard for the timestamp fixed at the start of a SQL statement.
12pub(crate) struct StatementTimestampGuard {
13    active: bool,
14}
15
16/// Fix the current UTC timestamp for the duration of the outermost statement.
17///
18/// Nested execution shares the existing timestamp, which keeps subexpressions
19/// and subqueries within one statement consistent.
20pub(crate) fn begin_statement() -> StatementTimestampGuard {
21    let active = STATEMENT_TIMESTAMPS.with(|timestamps| {
22        let mut timestamps = timestamps.borrow_mut();
23        if timestamps.is_empty() {
24            timestamps.push(utc_now_micros());
25            true
26        } else {
27            false
28        }
29    });
30    StatementTimestampGuard { active }
31}
32
33impl Drop for StatementTimestampGuard {
34    fn drop(&mut self) {
35        if self.active {
36            STATEMENT_TIMESTAMPS.with(|timestamps| {
37                timestamps.borrow_mut().pop();
38            });
39        }
40    }
41}
42
43pub(crate) fn current_statement_timestamp() -> i64 {
44    STATEMENT_TIMESTAMPS.with(|timestamps| {
45        timestamps
46            .borrow()
47            .last()
48            .copied()
49            .unwrap_or_else(utc_now_micros)
50    })
51}
52
53fn utc_now_micros() -> i64 {
54    match SystemTime::now().duration_since(UNIX_EPOCH) {
55        Ok(duration) => i64::try_from(duration.as_micros()).unwrap_or(i64::MAX),
56        Err(error) => -i64::try_from(error.duration().as_micros()).unwrap_or(i64::MAX),
57    }
58}
59
60/// Evaluation context holds a borrowed row for zero-copy access.
61pub struct EvalContext<'a> {
62    row: &'a [SqlValue],
63    statement_timestamp: i64,
64}
65
66impl<'a> EvalContext<'a> {
67    /// Create a new evaluation context for the given row slice.
68    pub fn new(row: &'a [SqlValue]) -> Self {
69        Self {
70            row,
71            statement_timestamp: current_statement_timestamp(),
72        }
73    }
74
75    /// Get a column value by index.
76    pub fn get(&self, index: usize) -> Result<&'a SqlValue, ExecutorError> {
77        self.row.get(index).ok_or(ExecutorError::Evaluation(
78            EvaluationError::InvalidColumnRef { index },
79        ))
80    }
81
82    /// Return the UTC timestamp fixed at the start of this statement.
83    pub(crate) fn statement_timestamp(&self) -> i64 {
84        self.statement_timestamp
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn get_existing_column_returns_value() {
94        let row = vec![SqlValue::Integer(1), SqlValue::Text("a".into())];
95        let ctx = EvalContext::new(&row);
96        assert!(matches!(ctx.get(0), Ok(SqlValue::Integer(1))));
97    }
98
99    #[test]
100    fn get_out_of_range_errors() {
101        let row = vec![SqlValue::Integer(1)];
102        let ctx = EvalContext::new(&row);
103        let err = ctx.get(2).unwrap_err();
104        match err {
105            ExecutorError::Evaluation(EvaluationError::InvalidColumnRef { index }) => {
106                assert_eq!(index, 2)
107            }
108            other => panic!("unexpected error {other:?}"),
109        }
110    }
111}