Skip to main content

reifydb_routine/function/datetime/
from_epoch.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns};
5use reifydb_type::value::{container::temporal::TemporalContainer, datetime::DateTime, r#type::Type};
6
7use crate::routine::{Function, FunctionKind, Routine, RoutineInfo, context::FunctionContext, error::RoutineError};
8
9pub struct DateTimeFromEpoch {
10	info: RoutineInfo,
11}
12
13impl Default for DateTimeFromEpoch {
14	fn default() -> Self {
15		Self::new()
16	}
17}
18
19impl DateTimeFromEpoch {
20	pub fn new() -> Self {
21		Self {
22			info: RoutineInfo::new("datetime::from_epoch"),
23		}
24	}
25}
26
27fn extract_i64(data: &ColumnBuffer, i: usize) -> Option<i64> {
28	match data {
29		ColumnBuffer::Int1(c) => c.get(i).map(|&v| v as i64),
30		ColumnBuffer::Int2(c) => c.get(i).map(|&v| v as i64),
31		ColumnBuffer::Int4(c) => c.get(i).map(|&v| v as i64),
32		ColumnBuffer::Int8(c) => c.get(i).copied(),
33		ColumnBuffer::Int16(c) => c.get(i).map(|&v| v as i64),
34		ColumnBuffer::Uint1(c) => c.get(i).map(|&v| v as i64),
35		ColumnBuffer::Uint2(c) => c.get(i).map(|&v| v as i64),
36		ColumnBuffer::Uint4(c) => c.get(i).map(|&v| v as i64),
37		ColumnBuffer::Uint8(c) => c.get(i).map(|&v| v as i64),
38		ColumnBuffer::Uint16(c) => c.get(i).map(|&v| v as i64),
39		_ => None,
40	}
41}
42
43fn is_integer_type(data: &ColumnBuffer) -> bool {
44	matches!(
45		data,
46		ColumnBuffer::Int1(_)
47			| ColumnBuffer::Int2(_)
48			| ColumnBuffer::Int4(_)
49			| ColumnBuffer::Int8(_)
50			| ColumnBuffer::Int16(_)
51			| ColumnBuffer::Uint1(_)
52			| ColumnBuffer::Uint2(_)
53			| ColumnBuffer::Uint4(_)
54			| ColumnBuffer::Uint8(_)
55			| ColumnBuffer::Uint16(_)
56	)
57}
58
59impl<'a> Routine<FunctionContext<'a>> for DateTimeFromEpoch {
60	fn info(&self) -> &RoutineInfo {
61		&self.info
62	}
63
64	fn return_type(&self, _input_types: &[Type]) -> Type {
65		Type::DateTime
66	}
67
68	fn execute(&self, ctx: &mut FunctionContext<'a>, args: &Columns) -> Result<Columns, RoutineError> {
69		if args.len() != 1 {
70			return Err(RoutineError::FunctionArityMismatch {
71				function: ctx.fragment.clone(),
72				expected: 1,
73				actual: args.len(),
74			});
75		}
76
77		let column = &args[0];
78		let (data, bitvec) = column.unwrap_option();
79		let row_count = data.len();
80
81		if !is_integer_type(data) {
82			return Err(RoutineError::FunctionInvalidArgumentType {
83				function: ctx.fragment.clone(),
84				argument_index: 0,
85				expected: vec![
86					Type::Int1,
87					Type::Int2,
88					Type::Int4,
89					Type::Int8,
90					Type::Int16,
91					Type::Uint1,
92					Type::Uint2,
93					Type::Uint4,
94					Type::Uint8,
95					Type::Uint16,
96				],
97				actual: data.get_type(),
98			});
99		}
100
101		let mut container = TemporalContainer::with_capacity(row_count);
102
103		for i in 0..row_count {
104			if let Some(ts) = extract_i64(data, i) {
105				if ts < 0 {
106					return Err(RoutineError::FunctionExecutionFailed {
107						function: ctx.fragment.clone(),
108						reason: format!(
109							"datetime::from_epoch does not support negative timestamps: {}",
110							ts
111						),
112					});
113				}
114				match DateTime::from_timestamp(ts) {
115					Ok(dt) => container.push(dt),
116					Err(_) => container.push_default(),
117				}
118			} else {
119				container.push_default();
120			}
121		}
122
123		let result_data = ColumnBuffer::DateTime(container);
124
125		let final_data = if let Some(bv) = bitvec {
126			ColumnBuffer::Option {
127				inner: Box::new(result_data),
128				bitvec: bv.clone(),
129			}
130		} else {
131			result_data
132		};
133
134		Ok(Columns::new(vec![ColumnWithName::new(ctx.fragment.clone(), final_data)]))
135	}
136}
137
138impl Function for DateTimeFromEpoch {
139	fn kinds(&self) -> &[FunctionKind] {
140		&[FunctionKind::Scalar]
141	}
142}