reifydb_engine/expression/arith/
add.rs1use 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 (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 (
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 (
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 (
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}