Skip to main content

reifydb_engine/expression/
scalar.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2026 ReifyDB
3
4use 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}