reifydb_engine/expression/
scalar.rs1use reifydb_core::interface::catalog::property::ColumnSaturationStrategy;
5use reifydb_value::{
6 Result,
7 error::TypeError,
8 fragment::LazyFragment,
9 value::{
10 is::IsNumber,
11 number::{
12 promote::Promote,
13 safe::{add::SafeAdd, div::SafeDiv, mul::SafeMul, remainder::SafeRemainder, sub::SafeSub},
14 },
15 value_type::get::GetType,
16 },
17};
18
19use crate::expression::context::EvalContext;
20
21macro_rules! impl_scalar_op {
22 ($method:ident, $safe_trait:ident, $checked_method:ident) => {
23 impl EvalContext<'_> {
24 pub fn $method<L, R>(
25 &self,
26 l: &L,
27 r: &R,
28 fragment: impl LazyFragment + Copy,
29 ) -> Result<Option<<L as Promote<R>>::Output>>
30 where
31 L: Promote<R>,
32 R: IsNumber,
33 <L as Promote<R>>::Output: IsNumber,
34 <L as Promote<R>>::Output: $safe_trait,
35 {
36 match &self.saturation_policy() {
37 ColumnSaturationStrategy::Error => {
38 let Some((lp, rp)) = l.checked_promote(r) else {
39 let descriptor = self
40 .target
41 .as_ref()
42 .and_then(|c| c.to_number_descriptor());
43 return Err(TypeError::NumberOutOfRange {
44 target: <L as Promote<R>>::Output::get_type(),
45 fragment: fragment.fragment(),
46 descriptor,
47 }
48 .into());
49 };
50
51 lp.$checked_method(&rp)
52 .ok_or_else(|| {
53 let descriptor = self
54 .target
55 .as_ref()
56 .and_then(|c| c.to_number_descriptor());
57 TypeError::NumberOutOfRange {
58 target: <L as Promote<R>>::Output::get_type(),
59 fragment: fragment.fragment(),
60 descriptor,
61 }
62 .into()
63 })
64 .map(Some)
65 }
66 ColumnSaturationStrategy::None => {
67 let Some((lp, rp)) = l.checked_promote(r) else {
68 return Ok(None);
69 };
70
71 match lp.$checked_method(&rp) {
72 None => Ok(None),
73 Some(value) => Ok(Some(value)),
74 }
75 }
76 }
77 }
78 }
79 };
80}
81
82macro_rules! impl_scalar_divisive_op {
83 ($method:ident, $safe_trait:ident, $checked_method:ident) => {
84 impl EvalContext<'_> {
85 pub fn $method<L, R>(
86 &self,
87 l: &L,
88 r: &R,
89 fragment: impl LazyFragment + Copy,
90 ) -> Result<Option<<L as Promote<R>>::Output>>
91 where
92 L: Promote<R>,
93 R: IsNumber,
94 <L as Promote<R>>::Output: IsNumber,
95 <L as Promote<R>>::Output: $safe_trait,
96 {
97 match &self.saturation_policy() {
98 ColumnSaturationStrategy::Error => {
99 let Some((lp, rp)) = l.checked_promote(r) else {
100 let descriptor = self
101 .target
102 .as_ref()
103 .and_then(|c| c.to_number_descriptor());
104 return Err(TypeError::NumberOutOfRange {
105 target: <L as Promote<R>>::Output::get_type(),
106 fragment: fragment.fragment(),
107 descriptor,
108 }
109 .into());
110 };
111
112 if <<L as Promote<R>>::Output as $safe_trait>::is_zero(&rp) {
113 return Err(TypeError::DivisionByZero {
114 target: <L as Promote<R>>::Output::get_type(),
115 fragment: fragment.fragment(),
116 }
117 .into());
118 }
119
120 lp.$checked_method(&rp)
121 .ok_or_else(|| {
122 let descriptor = self
123 .target
124 .as_ref()
125 .and_then(|c| c.to_number_descriptor());
126 TypeError::NumberOutOfRange {
127 target: <L as Promote<R>>::Output::get_type(),
128 fragment: fragment.fragment(),
129 descriptor,
130 }
131 .into()
132 })
133 .map(Some)
134 }
135 ColumnSaturationStrategy::None => {
136 let Some((lp, rp)) = l.checked_promote(r) else {
137 return Ok(None);
138 };
139
140 match lp.$checked_method(&rp) {
141 None => Ok(None),
142 Some(value) => Ok(Some(value)),
143 }
144 }
145 }
146 }
147 }
148 };
149}
150
151impl_scalar_op!(add, SafeAdd, checked_add);
152impl_scalar_op!(sub, SafeSub, checked_sub);
153impl_scalar_op!(mul, SafeMul, checked_mul);
154impl_scalar_divisive_op!(div, SafeDiv, checked_div);
155impl_scalar_divisive_op!(remainder, SafeRemainder, checked_rem);
156
157#[cfg(test)]
158pub mod tests {
159 use reifydb_value::fragment::Fragment;
160
161 use crate::expression::context::EvalContext;
162
163 #[test]
164 fn test_add() {
165 let test_instance = EvalContext::testing();
166 let result = test_instance.add(&1i8, &255i16, || Fragment::testing_empty());
167 assert_eq!(result, Ok(Some(256i128)));
168 }
169
170 #[test]
171 fn test_sub() {
172 let test_instance = EvalContext::testing();
173 let result = test_instance.sub(&1i8, &255i16, || Fragment::testing_empty());
174 assert_eq!(result, Ok(Some(-254i128)));
175 }
176
177 #[test]
178 fn test_mul() {
179 let test_instance = EvalContext::testing();
180 let result = test_instance.mul(&23i8, &255i16, || Fragment::testing_empty());
181 assert_eq!(result, Ok(Some(5865i128)));
182 }
183
184 #[test]
185 fn test_div() {
186 let test_instance = EvalContext::testing();
187 let result = test_instance.div(&120i8, &20i16, || Fragment::testing_empty());
188 assert_eq!(result, Ok(Some(6i128)));
189 }
190
191 #[test]
192 fn test_remainder() {
193 let test_instance = EvalContext::testing();
194 let result = test_instance.remainder(&120i8, &21i16, || Fragment::testing_empty());
195 assert_eq!(result, Ok(Some(15i128)));
196 }
197}