Skip to main content

reifydb_engine/expression/arith/
add.rs

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