Skip to main content

reifydb_function/time/
add.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::value::column::data::ColumnData;
5use reifydb_type::value::{container::temporal::TemporalContainer, time::Time, r#type::Type};
6
7use crate::{
8	ScalarFunction, ScalarFunctionContext,
9	error::{ScalarFunctionError, ScalarFunctionResult},
10	propagate_options,
11};
12
13pub struct TimeAdd;
14
15impl TimeAdd {
16	pub fn new() -> Self {
17		Self
18	}
19}
20
21const NANOS_PER_DAY: i64 = 86_400_000_000_000;
22
23impl ScalarFunction for TimeAdd {
24	fn scalar(&self, ctx: ScalarFunctionContext) -> ScalarFunctionResult<ColumnData> {
25		if let Some(result) = propagate_options(self, &ctx) {
26			return result;
27		}
28		let columns = ctx.columns;
29		let row_count = ctx.row_count;
30
31		if columns.len() != 2 {
32			return Err(ScalarFunctionError::ArityMismatch {
33				function: ctx.fragment.clone(),
34				expected: 2,
35				actual: columns.len(),
36			});
37		}
38
39		let time_col = columns.get(0).unwrap();
40		let dur_col = columns.get(1).unwrap();
41
42		match (time_col.data(), dur_col.data()) {
43			(ColumnData::Time(time_container), ColumnData::Duration(dur_container)) => {
44				let mut container = TemporalContainer::with_capacity(row_count);
45
46				for i in 0..row_count {
47					match (time_container.get(i), dur_container.get(i)) {
48						(Some(time), Some(dur)) => {
49							let time_nanos = time.to_nanos_since_midnight() as i64;
50							let dur_nanos =
51								dur.get_nanos() + dur.get_days() as i64 * NANOS_PER_DAY;
52
53							let result_nanos =
54								(time_nanos + dur_nanos).rem_euclid(NANOS_PER_DAY);
55							match Time::from_nanos_since_midnight(result_nanos as u64) {
56								Some(result) => container.push(result),
57								None => container.push_default(),
58							}
59						}
60						_ => container.push_default(),
61					}
62				}
63
64				Ok(ColumnData::Time(container))
65			}
66			(ColumnData::Time(_), other) => Err(ScalarFunctionError::InvalidArgumentType {
67				function: ctx.fragment.clone(),
68				argument_index: 1,
69				expected: vec![Type::Duration],
70				actual: other.get_type(),
71			}),
72			(other, _) => Err(ScalarFunctionError::InvalidArgumentType {
73				function: ctx.fragment.clone(),
74				argument_index: 0,
75				expected: vec![Type::Time],
76				actual: other.get_type(),
77			}),
78		}
79	}
80
81	fn return_type(&self, _input_types: &[Type]) -> Type {
82		Type::Time
83	}
84}