reifydb_routine/function/math/
lcm.rs1use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, columns::Columns};
5use reifydb_type::value::r#type::Type;
6
7use crate::routine::{Function, FunctionKind, Routine, RoutineInfo, context::FunctionContext, error::RoutineError};
8
9pub struct Lcm {
10 info: RoutineInfo,
11}
12
13impl Default for Lcm {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl Lcm {
20 pub fn new() -> Self {
21 Self {
22 info: RoutineInfo::new("math::lcm"),
23 }
24 }
25}
26
27fn numeric_to_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 _ => None,
39 }
40}
41
42fn compute_gcd(mut a: i64, mut b: i64) -> i64 {
43 a = a.abs();
44 b = b.abs();
45 while b != 0 {
46 let t = b;
47 b = a % b;
48 a = t;
49 }
50 a
51}
52
53fn compute_lcm(a: i64, b: i64) -> i64 {
54 if a == 0 || b == 0 {
55 return 0;
56 }
57 (a.abs() / compute_gcd(a, b)) * b.abs()
58}
59
60impl<'a> Routine<FunctionContext<'a>> for Lcm {
61 fn info(&self) -> &RoutineInfo {
62 &self.info
63 }
64
65 fn return_type(&self, _input_types: &[Type]) -> Type {
66 Type::Int8
67 }
68
69 fn execute(&self, ctx: &mut FunctionContext<'a>, args: &Columns) -> Result<Columns, RoutineError> {
70 if args.len() != 2 {
71 return Err(RoutineError::FunctionArityMismatch {
72 function: ctx.fragment.clone(),
73 expected: 2,
74 actual: args.len(),
75 });
76 }
77
78 let a_col = &args[0];
79 let b_col = &args[1];
80
81 let (a_data, a_bitvec) = a_col.unwrap_option();
82 let (b_data, b_bitvec) = b_col.unwrap_option();
83 let row_count = a_data.len();
84
85 let expected_types = vec![
86 Type::Int1,
87 Type::Int2,
88 Type::Int4,
89 Type::Int8,
90 Type::Uint1,
91 Type::Uint2,
92 Type::Uint4,
93 Type::Uint8,
94 ];
95 if !a_data.get_type().is_number() {
96 return Err(RoutineError::FunctionInvalidArgumentType {
97 function: ctx.fragment.clone(),
98 argument_index: 0,
99 expected: expected_types,
100 actual: a_data.get_type(),
101 });
102 }
103 if !b_data.get_type().is_number() {
104 return Err(RoutineError::FunctionInvalidArgumentType {
105 function: ctx.fragment.clone(),
106 argument_index: 1,
107 expected: expected_types,
108 actual: b_data.get_type(),
109 });
110 }
111
112 let mut result = Vec::with_capacity(row_count);
113 let mut res_bitvec = Vec::with_capacity(row_count);
114
115 for i in 0..row_count {
116 match (numeric_to_i64(a_data, i), numeric_to_i64(b_data, i)) {
117 (Some(a), Some(b)) => {
118 result.push(compute_lcm(a, b));
119 res_bitvec.push(true);
120 }
121 _ => {
122 result.push(0);
123 res_bitvec.push(false);
124 }
125 }
126 }
127
128 let result_data = ColumnBuffer::int8_with_bitvec(result, res_bitvec);
129 let combined_bitvec = match (a_bitvec, b_bitvec) {
130 (Some(a), Some(b)) => Some(a.and(b)),
131 (Some(a), None) => Some(a.clone()),
132 (None, Some(b)) => Some(b.clone()),
133 (None, None) => None,
134 };
135
136 let final_data = if let Some(bv) = combined_bitvec {
137 ColumnBuffer::Option {
138 inner: Box::new(result_data),
139 bitvec: bv,
140 }
141 } else {
142 result_data
143 };
144
145 Ok(Columns::new(vec![ColumnWithName::new(ctx.fragment.clone(), final_data)]))
146 }
147}
148
149impl Function for Lcm {
150 fn kinds(&self) -> &[FunctionKind] {
151 &[FunctionKind::Scalar]
152 }
153}