Skip to main content

reifydb_routine/procedure/clock/
advance.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::sync::LazyLock;
5
6use reifydb_core::value::column::columns::Columns;
7use reifydb_runtime::context::clock::Clock;
8use reifydb_type::{
9	fragment::Fragment,
10	params::Params,
11	value::{Value, datetime::DateTime, r#type::Type},
12};
13
14use super::set::extract_millis;
15use crate::routine::{Routine, RoutineInfo, context::ProcedureContext, error::RoutineError};
16
17static INFO: LazyLock<RoutineInfo> = LazyLock::new(|| RoutineInfo::new("clock::advance"));
18
19/// Native procedure that advances the mock clock by a duration or number of milliseconds.
20///
21/// Accepts 1 positional argument: a Duration or integer milliseconds.
22pub struct ClockAdvanceProcedure;
23
24impl Default for ClockAdvanceProcedure {
25	fn default() -> Self {
26		Self::new()
27	}
28}
29
30impl ClockAdvanceProcedure {
31	pub fn new() -> Self {
32		Self
33	}
34}
35
36impl<'a, 'tx> Routine<ProcedureContext<'a, 'tx>> for ClockAdvanceProcedure {
37	fn info(&self) -> &RoutineInfo {
38		&INFO
39	}
40
41	fn return_type(&self, _input_types: &[Type]) -> Type {
42		Type::DateTime
43	}
44
45	fn execute(&self, ctx: &mut ProcedureContext<'a, 'tx>, _args: &Columns) -> Result<Columns, RoutineError> {
46		let arg = match ctx.params {
47			Params::Positional(args) if args.len() == 1 => &args[0],
48			Params::Positional(args) => {
49				return Err(RoutineError::ProcedureArityMismatch {
50					procedure: Fragment::internal("clock::advance"),
51					expected: 1,
52					actual: args.len(),
53				});
54			}
55			_ => {
56				return Err(RoutineError::ProcedureArityMismatch {
57					procedure: Fragment::internal("clock::advance"),
58					expected: 1,
59					actual: 0,
60				});
61			}
62		};
63
64		match &ctx.runtime_context.clock {
65			Clock::Mock(mock) => {
66				match arg {
67					Value::Duration(dur) => {
68						if dur.get_months() == 0 && dur.get_days() == 0 {
69							// Pure nanos-only duration: advance directly
70							let nanos = dur.get_nanos();
71							if nanos >= 0 {
72								mock.advance_nanos(nanos as u64);
73							} else {
74								let current = mock.now_nanos();
75								let abs_nanos = nanos.unsigned_abs();
76								if abs_nanos > current {
77									return Err(RoutineError::ProcedureExecutionFailed {
78										procedure: Fragment::internal("clock::advance"),
79										reason: "clock cannot be set before Unix epoch".to_string(),
80									});
81								}
82								mock.set_nanos(current - abs_nanos);
83							}
84						} else {
85							// Calendar-aware: go through DateTime arithmetic
86							let current_nanos = mock.now_nanos();
87							let current_dt = DateTime::from_nanos(current_nanos);
88							let new_dt = current_dt.add_duration(dur)?;
89							mock.set_nanos(new_dt.to_nanos());
90						}
91					}
92					other => {
93						let millis = extract_millis(other).ok_or_else(|| {
94							RoutineError::ProcedureInvalidArgumentType {
95								procedure: Fragment::internal("clock::advance"),
96								argument_index: 0,
97								expected: EXPECTED_ADVANCE_TYPES.to_vec(),
98								actual: other.get_type(),
99							}
100						})?;
101						mock.advance_millis(millis);
102					}
103				}
104				let current_nanos = mock.now_nanos();
105				let dt = DateTime::from_nanos(current_nanos);
106				Ok(Columns::single_row([("clock", Value::DateTime(dt))]))
107			}
108			Clock::Real => Err(RoutineError::ProcedureExecutionFailed {
109				procedure: Fragment::internal("clock::advance"),
110				reason: "clock::advance can only be used with a mock clock".to_string(),
111			}),
112		}
113	}
114}
115
116const EXPECTED_ADVANCE_TYPES: &[Type] = &[
117	Type::Duration,
118	Type::Int1,
119	Type::Int2,
120	Type::Int4,
121	Type::Int8,
122	Type::Int16,
123	Type::Uint1,
124	Type::Uint2,
125	Type::Uint4,
126	Type::Uint8,
127	Type::Uint16,
128];