Skip to main content

reifydb_engine/expression/
scalar.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::interface::catalog::property::ColumnSaturationPolicy;
5use reifydb_type::{
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		r#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<'a, 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					ColumnSaturationPolicy::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					ColumnSaturationPolicy::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
82impl_scalar_op!(add, SafeAdd, checked_add);
83impl_scalar_op!(sub, SafeSub, checked_sub);
84impl_scalar_op!(mul, SafeMul, checked_mul);
85impl_scalar_op!(div, SafeDiv, checked_div);
86impl_scalar_op!(remainder, SafeRemainder, checked_rem);
87
88#[cfg(test)]
89pub mod tests {
90	use reifydb_type::fragment::Fragment;
91
92	use crate::expression::context::EvalContext;
93
94	#[test]
95	fn test_add() {
96		let test_instance = EvalContext::testing();
97		let result = test_instance.add(&1i8, &255i16, || Fragment::testing_empty());
98		assert_eq!(result, Ok(Some(256i128)));
99	}
100
101	#[test]
102	fn test_sub() {
103		let test_instance = EvalContext::testing();
104		let result = test_instance.sub(&1i8, &255i16, || Fragment::testing_empty());
105		assert_eq!(result, Ok(Some(-254i128)));
106	}
107
108	#[test]
109	fn test_mul() {
110		let test_instance = EvalContext::testing();
111		let result = test_instance.mul(&23i8, &255i16, || Fragment::testing_empty());
112		assert_eq!(result, Ok(Some(5865i128)));
113	}
114
115	#[test]
116	fn test_div() {
117		let test_instance = EvalContext::testing();
118		let result = test_instance.div(&120i8, &20i16, || Fragment::testing_empty());
119		assert_eq!(result, Ok(Some(6i128)));
120	}
121
122	#[test]
123	fn test_remainder() {
124		let test_instance = EvalContext::testing();
125		let result = test_instance.remainder(&120i8, &21i16, || Fragment::testing_empty());
126		assert_eq!(result, Ok(Some(15i128)));
127	}
128}