reifydb_routine/function/date/
add.rs1use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns};
5use reifydb_type::value::{container::temporal::TemporalContainer, date::Date, r#type::Type};
6
7use crate::routine::{Function, FunctionKind, Routine, RoutineInfo, context::FunctionContext, error::RoutineError};
8
9pub struct DateAdd {
10 info: RoutineInfo,
11}
12
13impl Default for DateAdd {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl DateAdd {
20 pub fn new() -> Self {
21 Self {
22 info: RoutineInfo::new("date::add"),
23 }
24 }
25}
26
27impl<'a> Routine<FunctionContext<'a>> for DateAdd {
28 fn info(&self) -> &RoutineInfo {
29 &self.info
30 }
31
32 fn return_type(&self, _input_types: &[Type]) -> Type {
33 Type::Date
34 }
35
36 fn execute(&self, ctx: &mut FunctionContext<'a>, args: &Columns) -> Result<Columns, RoutineError> {
37 if args.len() != 2 {
38 return Err(RoutineError::FunctionArityMismatch {
39 function: ctx.fragment.clone(),
40 expected: 2,
41 actual: args.len(),
42 });
43 }
44
45 let date_col = &args[0];
46 let dur_col = &args[1];
47 let (date_data, date_bitvec) = date_col.unwrap_option();
48 let (dur_data, dur_bitvec) = dur_col.unwrap_option();
49 let row_count = date_data.len();
50
51 let result_data = match (date_data, dur_data) {
52 (ColumnBuffer::Date(date_container), ColumnBuffer::Duration(dur_container)) => {
53 let mut container = TemporalContainer::with_capacity(row_count);
54
55 for i in 0..row_count {
56 match (date_container.get(i), dur_container.get(i)) {
57 (Some(date), Some(dur)) => {
58 let mut year = date.year();
59 let mut month = date.month() as i32;
60 let mut day = date.day();
61
62 let total_months = month + dur.get_months();
63 year += (total_months - 1).div_euclid(12);
64 month = (total_months - 1).rem_euclid(12) + 1;
65
66 let max_day = days_in_month(year, month as u32);
67 if day > max_day {
68 day = max_day;
69 }
70
71 if let Some(base) = Date::new(year, month as u32, day) {
72 let total_days = base.to_days_since_epoch()
73 + dur.get_days() + (dur.get_nanos()
74 / 86_400_000_000_000)
75 as i32;
76 match Date::from_days_since_epoch(total_days) {
77 Some(result) => container.push(result),
78 None => container.push_default(),
79 }
80 } else {
81 container.push_default();
82 }
83 }
84 _ => container.push_default(),
85 }
86 }
87
88 ColumnBuffer::Date(container)
89 }
90 (ColumnBuffer::Date(_), other) => {
91 return Err(RoutineError::FunctionInvalidArgumentType {
92 function: ctx.fragment.clone(),
93 argument_index: 1,
94 expected: vec![Type::Duration],
95 actual: other.get_type(),
96 });
97 }
98 (other, _) => {
99 return Err(RoutineError::FunctionInvalidArgumentType {
100 function: ctx.fragment.clone(),
101 argument_index: 0,
102 expected: vec![Type::Date],
103 actual: other.get_type(),
104 });
105 }
106 };
107
108 let final_data = match (date_bitvec, dur_bitvec) {
109 (Some(bv), _) | (_, Some(bv)) => ColumnBuffer::Option {
110 inner: Box::new(result_data),
111 bitvec: bv.clone(),
112 },
113 _ => result_data,
114 };
115
116 Ok(Columns::new(vec![ColumnWithName::new(ctx.fragment.clone(), final_data)]))
117 }
118}
119
120impl Function for DateAdd {
121 fn kinds(&self) -> &[FunctionKind] {
122 &[FunctionKind::Scalar]
123 }
124}
125
126fn days_in_month(year: i32, month: u32) -> u32 {
127 match month {
128 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
129 4 | 6 | 9 | 11 => 30,
130 2 => {
131 if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
132 29
133 } else {
134 28
135 }
136 }
137 _ => 0,
138 }
139}