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