reifydb_engine/evaluate/
arith.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the AGPL-3.0-or-later, see license.md file
3
4use reifydb_core::interface::ColumnSaturationPolicy;
5use reifydb_type::{
6	Error, GetType, IsNumber, LazyFragment, Promote, SafeAdd, SafeDiv, SafeMul, SafeRemainder, SafeSub,
7	diagnostic::number::number_out_of_range, return_error,
8};
9
10use crate::evaluate::ColumnEvaluationContext;
11
12impl ColumnEvaluationContext<'_> {
13	pub fn add<'a, L, R>(
14		&self,
15		l: &L,
16		r: &R,
17		fragment: impl LazyFragment<'a> + Copy,
18	) -> reifydb_core::Result<Option<<L as Promote<R>>::Output>>
19	where
20		L: Promote<R>,
21		R: IsNumber,
22		<L as Promote<R>>::Output: IsNumber,
23		<L as Promote<R>>::Output: SafeAdd,
24	{
25		match &self.saturation_policy() {
26			ColumnSaturationPolicy::Error => {
27				let Some((lp, rp)) = l.checked_promote(r) else {
28					let descriptor = self.target.as_ref().and_then(|c| c.to_number_descriptor());
29					return_error!(number_out_of_range(
30						fragment.fragment(),
31						<L as Promote<R>>::Output::get_type(),
32						descriptor.as_ref(),
33					));
34				};
35
36				lp.checked_add(&rp)
37					.ok_or_else(|| {
38						let descriptor =
39							self.target.as_ref().and_then(|c| c.to_number_descriptor());
40						Error(number_out_of_range(
41							fragment.fragment(),
42							<L as Promote<R>>::Output::get_type(),
43							descriptor.as_ref(),
44						))
45					})
46					.map(Some)
47			}
48			ColumnSaturationPolicy::Undefined => {
49				let Some((lp, rp)) = l.checked_promote(r) else {
50					return Ok(None);
51				};
52
53				match lp.checked_add(&rp) {
54					None => Ok(None),
55					Some(value) => Ok(Some(value)),
56				}
57			}
58		}
59	}
60}
61
62impl ColumnEvaluationContext<'_> {
63	pub fn sub<'a, L, R>(
64		&self,
65		l: &L,
66		r: &R,
67		fragment: impl LazyFragment<'a> + Copy,
68	) -> reifydb_core::Result<Option<<L as Promote<R>>::Output>>
69	where
70		L: Promote<R>,
71		R: IsNumber,
72		<L as Promote<R>>::Output: IsNumber,
73		<L as Promote<R>>::Output: SafeSub,
74	{
75		match &self.saturation_policy() {
76			ColumnSaturationPolicy::Error => {
77				let Some((lp, rp)) = l.checked_promote(r) else {
78					let descriptor = self.target.as_ref().and_then(|c| c.to_number_descriptor());
79					return_error!(number_out_of_range(
80						fragment.fragment(),
81						<L as Promote<R>>::Output::get_type(),
82						descriptor.as_ref(),
83					));
84				};
85
86				lp.checked_sub(&rp)
87					.ok_or_else(|| {
88						let descriptor =
89							self.target.as_ref().and_then(|c| c.to_number_descriptor());
90						Error(number_out_of_range(
91							fragment.fragment(),
92							<L as Promote<R>>::Output::get_type(),
93							descriptor.as_ref(),
94						))
95					})
96					.map(Some)
97			}
98			ColumnSaturationPolicy::Undefined => {
99				let Some((lp, rp)) = l.checked_promote(r) else {
100					return Ok(None);
101				};
102
103				match lp.checked_sub(&rp) {
104					None => Ok(None),
105					Some(value) => Ok(Some(value)),
106				}
107			}
108		}
109	}
110}
111
112impl ColumnEvaluationContext<'_> {
113	pub fn mul<'a, L, R>(
114		&self,
115		l: &L,
116		r: &R,
117		fragment: impl LazyFragment<'a> + Copy,
118	) -> reifydb_core::Result<Option<<L as Promote<R>>::Output>>
119	where
120		L: Promote<R>,
121		R: IsNumber,
122		<L as Promote<R>>::Output: IsNumber,
123		<L as Promote<R>>::Output: SafeMul,
124	{
125		match &self.saturation_policy() {
126			ColumnSaturationPolicy::Error => {
127				let Some((lp, rp)) = l.checked_promote(r) else {
128					let descriptor = self.target.as_ref().and_then(|c| c.to_number_descriptor());
129					return_error!(number_out_of_range(
130						fragment.fragment(),
131						<L as Promote<R>>::Output::get_type(),
132						descriptor.as_ref(),
133					));
134				};
135
136				lp.checked_mul(&rp)
137					.ok_or_else(|| {
138						let descriptor =
139							self.target.as_ref().and_then(|c| c.to_number_descriptor());
140						Error(number_out_of_range(
141							fragment.fragment(),
142							<L as Promote<R>>::Output::get_type(),
143							descriptor.as_ref(),
144						))
145					})
146					.map(Some)
147			}
148			ColumnSaturationPolicy::Undefined => {
149				let Some((lp, rp)) = l.checked_promote(r) else {
150					return Ok(None);
151				};
152
153				match lp.checked_mul(&rp) {
154					None => Ok(None),
155					Some(value) => Ok(Some(value)),
156				}
157			}
158		}
159	}
160}
161
162impl ColumnEvaluationContext<'_> {
163	pub fn div<'a, L, R>(
164		&self,
165		l: &L,
166		r: &R,
167		fragment: impl LazyFragment<'a> + Copy,
168	) -> reifydb_core::Result<Option<<L as Promote<R>>::Output>>
169	where
170		L: Promote<R>,
171		R: IsNumber,
172		<L as Promote<R>>::Output: IsNumber,
173		<L as Promote<R>>::Output: SafeDiv,
174	{
175		match &self.saturation_policy() {
176			ColumnSaturationPolicy::Error => {
177				let Some((lp, rp)) = l.checked_promote(r) else {
178					let descriptor = self.target.as_ref().and_then(|c| c.to_number_descriptor());
179					return_error!(number_out_of_range(
180						fragment.fragment(),
181						<L as Promote<R>>::Output::get_type(),
182						descriptor.as_ref(),
183					));
184				};
185
186				lp.checked_div(&rp)
187					.ok_or_else(|| {
188						let descriptor =
189							self.target.as_ref().and_then(|c| c.to_number_descriptor());
190						Error(number_out_of_range(
191							fragment.fragment(),
192							<L as Promote<R>>::Output::get_type(),
193							descriptor.as_ref(),
194						))
195					})
196					.map(Some)
197			}
198			ColumnSaturationPolicy::Undefined => {
199				let Some((lp, rp)) = l.checked_promote(r) else {
200					return Ok(None);
201				};
202
203				match lp.checked_div(&rp) {
204					None => Ok(None),
205					Some(value) => Ok(Some(value)),
206				}
207			}
208		}
209	}
210}
211
212impl ColumnEvaluationContext<'_> {
213	pub fn remainder<'a, L, R>(
214		&self,
215		l: &L,
216		r: &R,
217		fragment: impl LazyFragment<'a> + Copy,
218	) -> reifydb_core::Result<Option<<L as Promote<R>>::Output>>
219	where
220		L: Promote<R>,
221		R: IsNumber,
222		<L as Promote<R>>::Output: IsNumber,
223		<L as Promote<R>>::Output: SafeRemainder,
224	{
225		match &self.saturation_policy() {
226			ColumnSaturationPolicy::Error => {
227				let Some((lp, rp)) = l.checked_promote(r) else {
228					let descriptor = self.target.as_ref().and_then(|c| c.to_number_descriptor());
229					return_error!(number_out_of_range(
230						fragment.fragment(),
231						<L as Promote<R>>::Output::get_type(),
232						descriptor.as_ref(),
233					));
234				};
235
236				lp.checked_rem(&rp)
237					.ok_or_else(|| {
238						let descriptor =
239							self.target.as_ref().and_then(|c| c.to_number_descriptor());
240						Error(number_out_of_range(
241							fragment.fragment(),
242							<L as Promote<R>>::Output::get_type(),
243							descriptor.as_ref(),
244						))
245					})
246					.map(Some)
247			}
248			ColumnSaturationPolicy::Undefined => {
249				let Some((lp, rp)) = l.checked_promote(r) else {
250					return Ok(None);
251				};
252
253				match lp.checked_rem(&rp) {
254					None => Ok(None),
255					Some(value) => Ok(Some(value)),
256				}
257			}
258		}
259	}
260}
261
262#[cfg(test)]
263mod tests {
264	use reifydb_type::Fragment;
265
266	use crate::evaluate::ColumnEvaluationContext;
267
268	#[test]
269	fn test_add() {
270		let test_instance = ColumnEvaluationContext::testing();
271		let result = test_instance.add(&1i8, &255i16, || Fragment::testing_empty());
272		assert_eq!(result, Ok(Some(256i128)));
273	}
274
275	#[test]
276	fn test_sub() {
277		let test_instance = ColumnEvaluationContext::testing();
278		let result = test_instance.sub(&1i8, &255i16, || Fragment::testing_empty());
279		assert_eq!(result, Ok(Some(-254i128)));
280	}
281
282	#[test]
283	fn test_mul() {
284		let test_instance = ColumnEvaluationContext::testing();
285		let result = test_instance.mul(&23i8, &255i16, || Fragment::testing_empty());
286		assert_eq!(result, Ok(Some(5865i128)));
287	}
288
289	#[test]
290	fn test_div() {
291		let test_instance = ColumnEvaluationContext::testing();
292		let result = test_instance.div(&120i8, &20i16, || Fragment::testing_empty());
293		assert_eq!(result, Ok(Some(6i128)));
294	}
295
296	#[test]
297	fn test_remainder() {
298		let test_instance = ColumnEvaluationContext::testing();
299		let result = test_instance.remainder(&120i8, &21i16, || Fragment::testing_empty());
300		assert_eq!(result, Ok(Some(15i128)));
301	}
302}