Skip to main content

reifydb_engine/expression/arith/
add.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer, push::Push};
5use reifydb_value::{
6	error::{BinaryOp, TypeError},
7	fragment::{Fragment, LazyFragment},
8	reifydb_assertions,
9	value::{
10		container::{number::NumberContainer, temporal::TemporalContainer, utf8::Utf8Container},
11		is::IsNumber,
12		number::{promote::Promote, safe::add::SafeAdd},
13		value_type::{ValueType, get::GetType},
14	},
15};
16
17use crate::{
18	Result,
19	expression::{context::EvalContext, option::binary_op_unwrap_option},
20};
21
22pub(crate) fn add_columns(
23	ctx: &EvalContext,
24	left: &ColumnWithName,
25	right: &ColumnWithName,
26	fragment: impl LazyFragment + Copy,
27) -> Result<ColumnWithName> {
28	binary_op_unwrap_option(left, right, fragment.fragment(), |left, right| {
29		let target = ValueType::promote(left.get_type(), right.get_type());
30
31		dispatch_arith!(
32			&left.data(), &right.data();
33			fixed: add_numeric, arb: add_numeric_clone (ctx, target, fragment);
34
35
36			(ColumnBuffer::Duration(l), ColumnBuffer::Duration(r)) => {
37				let mut container = TemporalContainer::with_capacity(l.len());
38				for i in 0..l.len() {
39					match (l.get(i), r.get(i)) {
40						(Some(lv), Some(rv)) => container.push(*lv + *rv),
41						_ => container.push_default(),
42					}
43				}
44				Ok(ColumnWithName::new(fragment.fragment(), ColumnBuffer::Duration(container)))
45			}
46
47
48			(
49				ColumnBuffer::Utf8 {
50					container: l,
51					..
52				},
53				ColumnBuffer::Utf8 {
54					container: r,
55					..
56				},
57			) => concat_strings(l, r, target, fragment.fragment()),
58
59
60			(
61				ColumnBuffer::Utf8 {
62					container: l,
63					..
64				},
65				r,
66			) if can_promote_to_string(r) => concat_string_with_other(l, r, true, target, fragment.fragment()),
67
68
69			(
70				l,
71				ColumnBuffer::Utf8 {
72					container: r,
73					..
74				},
75			) if can_promote_to_string(l) => concat_string_with_other(r, l, false, target, fragment.fragment()),
76
77			_ => Err(TypeError::BinaryOperatorNotApplicable {
78				operator: BinaryOp::Add,
79				left: left.get_type(),
80				right: right.get_type(),
81				fragment: fragment.fragment(),
82			}.into()),
83		)
84	})
85}
86
87fn add_numeric<L, R>(
88	ctx: &EvalContext,
89	l: &NumberContainer<L>,
90	r: &NumberContainer<R>,
91	target: ValueType,
92	fragment: impl LazyFragment + Copy,
93) -> Result<ColumnWithName>
94where
95	L: GetType + Promote<R> + IsNumber,
96	R: GetType + IsNumber,
97	<L as Promote<R>>::Output: IsNumber,
98	<L as Promote<R>>::Output: SafeAdd,
99	ColumnBuffer: Push<<L as Promote<R>>::Output>,
100{
101	reifydb_assertions! {
102		assert_eq!(l.len(), r.len());
103	}
104
105	let mut data = ColumnBuffer::with_capacity(target, l.len());
106	let l_data = l.data();
107	let r_data = r.data();
108	for i in 0..l.len() {
109		if let Some(value) = ctx.add(&l_data[i], &r_data[i], fragment)? {
110			data.push(value);
111		} else {
112			data.push_none()
113		}
114	}
115	Ok(ColumnWithName {
116		name: fragment.fragment(),
117		data,
118	})
119}
120
121fn add_numeric_clone<L, R>(
122	ctx: &EvalContext,
123	l: &NumberContainer<L>,
124	r: &NumberContainer<R>,
125	target: ValueType,
126	fragment: impl LazyFragment + Copy,
127) -> Result<ColumnWithName>
128where
129	L: Clone + GetType + Promote<R> + IsNumber,
130	R: Clone + GetType + IsNumber,
131	<L as Promote<R>>::Output: IsNumber,
132	<L as Promote<R>>::Output: SafeAdd,
133	ColumnBuffer: Push<<L as Promote<R>>::Output>,
134{
135	reifydb_assertions! {
136		assert_eq!(l.len(), r.len());
137	}
138
139	let mut data = ColumnBuffer::with_capacity(target, l.len());
140	for i in 0..l.len() {
141		match (l.get(i), r.get(i)) {
142			(Some(l_val), Some(r_val)) => {
143				let l_clone = l_val.clone();
144				let r_clone = r_val.clone();
145				if let Some(value) = ctx.add(&l_clone, &r_clone, fragment)? {
146					data.push(value);
147				} else {
148					data.push_none()
149				}
150			}
151			_ => data.push_none(),
152		}
153	}
154	Ok(ColumnWithName {
155		name: fragment.fragment(),
156		data,
157	})
158}
159
160fn can_promote_to_string(data: &ColumnBuffer) -> bool {
161	matches!(
162		data,
163		ColumnBuffer::Bool(_)
164			| ColumnBuffer::Float4(_)
165			| ColumnBuffer::Float8(_)
166			| ColumnBuffer::Int1(_)
167			| ColumnBuffer::Int2(_)
168			| ColumnBuffer::Int4(_)
169			| ColumnBuffer::Int8(_)
170			| ColumnBuffer::Int16(_)
171			| ColumnBuffer::Uint1(_)
172			| ColumnBuffer::Uint2(_)
173			| ColumnBuffer::Uint4(_)
174			| ColumnBuffer::Uint8(_)
175			| ColumnBuffer::Uint16(_)
176			| ColumnBuffer::Date(_)
177			| ColumnBuffer::DateTime(_)
178			| ColumnBuffer::Time(_)
179			| ColumnBuffer::Duration(_)
180			| ColumnBuffer::Uuid4(_)
181			| ColumnBuffer::Uuid7(_)
182			| ColumnBuffer::Blob { .. }
183			| ColumnBuffer::Int { .. }
184			| ColumnBuffer::Uint { .. }
185			| ColumnBuffer::Decimal { .. }
186	)
187}
188
189fn concat_strings(
190	l: &Utf8Container,
191	r: &Utf8Container,
192	target: ValueType,
193	fragment: Fragment,
194) -> Result<ColumnWithName> {
195	reifydb_assertions! {
196		assert_eq!(l.len(), r.len());
197	}
198
199	let mut data = ColumnBuffer::with_capacity(target, l.len());
200	for i in 0..l.len() {
201		match (l.get(i), r.get(i)) {
202			(Some(l_str), Some(r_str)) => {
203				let concatenated = format!("{}{}", l_str, r_str);
204				data.push(concatenated);
205			}
206			_ => data.push_none(),
207		}
208	}
209	Ok(ColumnWithName {
210		name: fragment,
211		data,
212	})
213}
214
215fn concat_string_with_other(
216	string_data: &Utf8Container,
217	other_data: &ColumnBuffer,
218	string_is_left: bool,
219	target: ValueType,
220	fragment: Fragment,
221) -> Result<ColumnWithName> {
222	reifydb_assertions! {
223		assert_eq!(string_data.len(), other_data.len());
224	}
225
226	let mut data = ColumnBuffer::with_capacity(target, string_data.len());
227	for i in 0..string_data.len() {
228		match (string_data.get(i), other_data.is_defined(i)) {
229			(Some(str_val), true) => {
230				let other_str = other_data.as_string(i);
231				let concatenated = if string_is_left {
232					format!("{}{}", str_val, other_str)
233				} else {
234					format!("{}{}", other_str, str_val)
235				};
236				data.push(concatenated);
237			}
238			_ => data.push_none(),
239		}
240	}
241	Ok(ColumnWithName {
242		name: fragment,
243		data,
244	})
245}