Skip to main content

reifydb_engine/expression/
scalar.rs

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