Skip to main content

nodedb_sql/
temporal.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Bitemporal scope types threaded through the SQL planner.
4//!
5//! A `TemporalScope` captures the user-facing `FOR SYSTEM_TIME AS OF <ms>` /
6//! `FOR VALID_TIME CONTAINS <ms>` / `FOR VALID_TIME FROM <ms> TO <ms>`
7//! qualifiers. It is extracted from raw SQL by
8//! [`crate::parser::preprocess::temporal`] before sqlparser-rs sees the
9//! statement, threaded through `plan_sql` into each `SqlPlan::Scan`, and
10//! finally honored by the engine's Data Plane handler.
11//!
12//! All timestamps are **milliseconds since Unix epoch**. The edge store
13//! converts to HLC ordinal via `nodedb_types::ms_to_ordinal_upper`. A
14//! `TemporalScope::default()` value is equivalent to "current state"
15//! (no temporal qualifier) — every scan site must construct one even when
16//! the query is not temporal.
17
18use nodedb_types::SystemTimeScope;
19
20/// Valid-time qualifier attached to a scan.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum ValidTime {
23    /// No `FOR VALID_TIME` clause — every version qualifies.
24    #[default]
25    Any,
26    /// `FOR VALID_TIME CONTAINS <ms>` — version must satisfy
27    /// `valid_from_ms <= ms < valid_until_ms`.
28    At(i64),
29    /// `FOR VALID_TIME FROM <lo_ms> TO <hi_ms>` — version's valid
30    /// interval must overlap `[lo_ms, hi_ms)`.
31    Range(i64, i64),
32}
33
34/// Combined bitemporal qualifier for a scan.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub struct TemporalScope {
37    /// System-time selection. `Current` = latest committed version;
38    /// `AsOf(ms)` = point-in-time; `AllVersions` = every system-time version
39    /// (`AS OF SYSTEM TIME NULL`, audit-log semantics).
40    pub system_time: SystemTimeScope,
41    /// Valid-time qualifier.
42    pub valid_time: ValidTime,
43}
44
45impl TemporalScope {
46    /// True when either temporal axis is qualified.
47    pub fn is_temporal(&self) -> bool {
48        self.system_time.is_temporal() || !matches!(self.valid_time, ValidTime::Any)
49    }
50}