Skip to main content

reifydb_evaluate/expression/
compare.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::cmp::Ordering;
5
6use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer};
7use reifydb_value::{
8	error::Diagnostic,
9	fragment::Fragment,
10	reifydb_assertions, return_error,
11	value::{
12		container::{
13			blob::BlobContainer, bool::BoolContainer, identity_id::IdentityIdContainer,
14			number::NumberContainer, temporal::TemporalContainer, utf8::Utf8Container, uuid::UuidContainer,
15		},
16		decimal::Decimal,
17		int::Int,
18		is::{IsNumber, IsTemporal, IsUuid},
19		number::{compare::partial_cmp, promote::Promote},
20		uint::Uint,
21		value_type::ValueType,
22	},
23};
24
25use super::option::binary_op_unwrap_option;
26use crate::Result;
27
28macro_rules! dispatch_compare {
29
30	(
31		$left:expr, $right:expr;
32		$fragment:expr;
33		$($extra:tt)*
34	) => {
35		dispatch_compare!(@rows
36			($left, $right) ($fragment)
37			[(Float4, f32) (Float8, f64) (Int1, i8) (Int2, i16) (Int4, i32) (Int8, i64) (Int16, i128) (Uint1, u8) (Uint2, u16) (Uint4, u32) (Uint8, u64) (Uint16, u128)]
38			{$($extra)*}
39			{}
40		)
41	};
42
43
44	(@rows
45		($left:expr, $right:expr) ($fragment:expr)
46		[($L:ident, $Lt:ty) $($rest:tt)*]
47		{$($extra:tt)*}
48		{$($acc:tt)*}
49	) => {
50		dispatch_compare!(@rows
51			($left, $right) ($fragment)
52			[$($rest)*]
53			{$($extra)*}
54			{
55				$($acc)*
56				(ColumnBuffer::$L(l), ColumnBuffer::Float4(r)) => { return Ok(compare_number::<Op, $Lt, f32>(l, r, $fragment)); },
57				(ColumnBuffer::$L(l), ColumnBuffer::Float8(r)) => { return Ok(compare_number::<Op, $Lt, f64>(l, r, $fragment)); },
58				(ColumnBuffer::$L(l), ColumnBuffer::Int1(r)) => { return Ok(compare_number::<Op, $Lt, i8>(l, r, $fragment)); },
59				(ColumnBuffer::$L(l), ColumnBuffer::Int2(r)) => { return Ok(compare_number::<Op, $Lt, i16>(l, r, $fragment)); },
60				(ColumnBuffer::$L(l), ColumnBuffer::Int4(r)) => { return Ok(compare_number::<Op, $Lt, i32>(l, r, $fragment)); },
61				(ColumnBuffer::$L(l), ColumnBuffer::Int8(r)) => { return Ok(compare_number::<Op, $Lt, i64>(l, r, $fragment)); },
62				(ColumnBuffer::$L(l), ColumnBuffer::Int16(r)) => { return Ok(compare_number::<Op, $Lt, i128>(l, r, $fragment)); },
63				(ColumnBuffer::$L(l), ColumnBuffer::Uint1(r)) => { return Ok(compare_number::<Op, $Lt, u8>(l, r, $fragment)); },
64				(ColumnBuffer::$L(l), ColumnBuffer::Uint2(r)) => { return Ok(compare_number::<Op, $Lt, u16>(l, r, $fragment)); },
65				(ColumnBuffer::$L(l), ColumnBuffer::Uint4(r)) => { return Ok(compare_number::<Op, $Lt, u32>(l, r, $fragment)); },
66				(ColumnBuffer::$L(l), ColumnBuffer::Uint8(r)) => { return Ok(compare_number::<Op, $Lt, u64>(l, r, $fragment)); },
67				(ColumnBuffer::$L(l), ColumnBuffer::Uint16(r)) => { return Ok(compare_number::<Op, $Lt, u128>(l, r, $fragment)); },
68				(ColumnBuffer::$L(l), ColumnBuffer::Int { container: r, .. }) => { return Ok(compare_number::<Op, $Lt, Int>(l, r, $fragment)); },
69				(ColumnBuffer::$L(l), ColumnBuffer::Uint { container: r, .. }) => { return Ok(compare_number::<Op, $Lt, Uint>(l, r, $fragment)); },
70				(ColumnBuffer::$L(l), ColumnBuffer::Decimal { container: r, .. }) => { return Ok(compare_number::<Op, $Lt, Decimal>(l, r, $fragment)); },
71			}
72		)
73	};
74
75
76	(@rows
77		($left:expr, $right:expr) ($fragment:expr)
78		[]
79		{$($extra:tt)*}
80		{$($acc:tt)*}
81	) => {
82		match ($left, $right) {
83
84			$($acc)*
85
86
87			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Float4(r)) => { return Ok(compare_number::<Op, Int, f32>(l, r, $fragment)); },
88			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Float8(r)) => { return Ok(compare_number::<Op, Int, f64>(l, r, $fragment)); },
89			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Int1(r)) => { return Ok(compare_number::<Op, Int, i8>(l, r, $fragment)); },
90			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Int2(r)) => { return Ok(compare_number::<Op, Int, i16>(l, r, $fragment)); },
91			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Int4(r)) => { return Ok(compare_number::<Op, Int, i32>(l, r, $fragment)); },
92			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Int8(r)) => { return Ok(compare_number::<Op, Int, i64>(l, r, $fragment)); },
93			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Int16(r)) => { return Ok(compare_number::<Op, Int, i128>(l, r, $fragment)); },
94			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Uint1(r)) => { return Ok(compare_number::<Op, Int, u8>(l, r, $fragment)); },
95			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Uint2(r)) => { return Ok(compare_number::<Op, Int, u16>(l, r, $fragment)); },
96			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Uint4(r)) => { return Ok(compare_number::<Op, Int, u32>(l, r, $fragment)); },
97			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Uint8(r)) => { return Ok(compare_number::<Op, Int, u64>(l, r, $fragment)); },
98			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Uint16(r)) => { return Ok(compare_number::<Op, Int, u128>(l, r, $fragment)); },
99			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Int { container: r, .. }) => { return Ok(compare_number::<Op, Int, Int>(l, r, $fragment)); },
100			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Uint { container: r, .. }) => { return Ok(compare_number::<Op, Int, Uint>(l, r, $fragment)); },
101			(ColumnBuffer::Int { container: l, .. }, ColumnBuffer::Decimal { container: r, .. }) => { return Ok(compare_number::<Op, Int, Decimal>(l, r, $fragment)); },
102
103
104			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Float4(r)) => { return Ok(compare_number::<Op, Uint, f32>(l, r, $fragment)); },
105			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Float8(r)) => { return Ok(compare_number::<Op, Uint, f64>(l, r, $fragment)); },
106			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Int1(r)) => { return Ok(compare_number::<Op, Uint, i8>(l, r, $fragment)); },
107			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Int2(r)) => { return Ok(compare_number::<Op, Uint, i16>(l, r, $fragment)); },
108			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Int4(r)) => { return Ok(compare_number::<Op, Uint, i32>(l, r, $fragment)); },
109			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Int8(r)) => { return Ok(compare_number::<Op, Uint, i64>(l, r, $fragment)); },
110			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Int16(r)) => { return Ok(compare_number::<Op, Uint, i128>(l, r, $fragment)); },
111			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Uint1(r)) => { return Ok(compare_number::<Op, Uint, u8>(l, r, $fragment)); },
112			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Uint2(r)) => { return Ok(compare_number::<Op, Uint, u16>(l, r, $fragment)); },
113			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Uint4(r)) => { return Ok(compare_number::<Op, Uint, u32>(l, r, $fragment)); },
114			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Uint8(r)) => { return Ok(compare_number::<Op, Uint, u64>(l, r, $fragment)); },
115			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Uint16(r)) => { return Ok(compare_number::<Op, Uint, u128>(l, r, $fragment)); },
116			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Int { container: r, .. }) => { return Ok(compare_number::<Op, Uint, Int>(l, r, $fragment)); },
117			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Uint { container: r, .. }) => { return Ok(compare_number::<Op, Uint, Uint>(l, r, $fragment)); },
118			(ColumnBuffer::Uint { container: l, .. }, ColumnBuffer::Decimal { container: r, .. }) => { return Ok(compare_number::<Op, Uint, Decimal>(l, r, $fragment)); },
119
120
121			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Float4(r)) => { return Ok(compare_number::<Op, Decimal, f32>(l, r, $fragment)); },
122			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Float8(r)) => { return Ok(compare_number::<Op, Decimal, f64>(l, r, $fragment)); },
123			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Int1(r)) => { return Ok(compare_number::<Op, Decimal, i8>(l, r, $fragment)); },
124			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Int2(r)) => { return Ok(compare_number::<Op, Decimal, i16>(l, r, $fragment)); },
125			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Int4(r)) => { return Ok(compare_number::<Op, Decimal, i32>(l, r, $fragment)); },
126			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Int8(r)) => { return Ok(compare_number::<Op, Decimal, i64>(l, r, $fragment)); },
127			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Int16(r)) => { return Ok(compare_number::<Op, Decimal, i128>(l, r, $fragment)); },
128			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Uint1(r)) => { return Ok(compare_number::<Op, Decimal, u8>(l, r, $fragment)); },
129			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Uint2(r)) => { return Ok(compare_number::<Op, Decimal, u16>(l, r, $fragment)); },
130			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Uint4(r)) => { return Ok(compare_number::<Op, Decimal, u32>(l, r, $fragment)); },
131			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Uint8(r)) => { return Ok(compare_number::<Op, Decimal, u64>(l, r, $fragment)); },
132			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Uint16(r)) => { return Ok(compare_number::<Op, Decimal, u128>(l, r, $fragment)); },
133			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Int { container: r, .. }) => { return Ok(compare_number::<Op, Decimal, Int>(l, r, $fragment)); },
134			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Uint { container: r, .. }) => { return Ok(compare_number::<Op, Decimal, Uint>(l, r, $fragment)); },
135			(ColumnBuffer::Decimal { container: l, .. }, ColumnBuffer::Decimal { container: r, .. }) => { return Ok(compare_number::<Op, Decimal, Decimal>(l, r, $fragment)); },
136
137
138			$($extra)*
139		}
140	};
141}
142
143pub trait CompareOp {
144	fn compare_ordering(ordering: Option<Ordering>) -> bool;
145	fn compare_bool(_l: bool, _r: bool) -> Option<bool> {
146		None
147	}
148}
149
150pub struct Equal;
151pub struct NotEqual;
152pub struct GreaterThan;
153pub struct GreaterThanEqual;
154pub struct LessThan;
155pub struct LessThanEqual;
156
157impl CompareOp for Equal {
158	#[inline]
159	fn compare_ordering(o: Option<Ordering>) -> bool {
160		o == Some(Ordering::Equal)
161	}
162	#[inline]
163	fn compare_bool(l: bool, r: bool) -> Option<bool> {
164		Some(l == r)
165	}
166}
167
168impl CompareOp for NotEqual {
169	#[inline]
170	fn compare_ordering(o: Option<Ordering>) -> bool {
171		o != Some(Ordering::Equal)
172	}
173	#[inline]
174	fn compare_bool(l: bool, r: bool) -> Option<bool> {
175		Some(l != r)
176	}
177}
178
179impl CompareOp for GreaterThan {
180	#[inline]
181	fn compare_ordering(o: Option<Ordering>) -> bool {
182		o == Some(Ordering::Greater)
183	}
184}
185
186impl CompareOp for GreaterThanEqual {
187	#[inline]
188	fn compare_ordering(o: Option<Ordering>) -> bool {
189		matches!(o, Some(Ordering::Greater) | Some(Ordering::Equal))
190	}
191}
192
193impl CompareOp for LessThan {
194	#[inline]
195	fn compare_ordering(o: Option<Ordering>) -> bool {
196		o == Some(Ordering::Less)
197	}
198}
199
200impl CompareOp for LessThanEqual {
201	#[inline]
202	fn compare_ordering(o: Option<Ordering>) -> bool {
203		matches!(o, Some(Ordering::Less) | Some(Ordering::Equal))
204	}
205}
206
207#[inline]
208fn compare_number<Op: CompareOp, L, R>(
209	l: &NumberContainer<L>,
210	r: &NumberContainer<R>,
211	fragment: Fragment,
212) -> ColumnWithName
213where
214	L: Promote<R> + IsNumber,
215	R: IsNumber,
216	<L as Promote<R>>::Output: IsNumber,
217{
218	reifydb_assertions! {
219		assert_eq!(l.len(), r.len());
220	}
221
222	let data: Vec<bool> =
223		l.data().iter()
224			.zip(r.data().iter())
225			.map(|(l_val, r_val)| Op::compare_ordering(partial_cmp(l_val, r_val)))
226			.collect();
227
228	ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data))
229}
230
231#[inline]
232fn compare_temporal<Op: CompareOp, T>(
233	l: &TemporalContainer<T>,
234	r: &TemporalContainer<T>,
235	fragment: Fragment,
236) -> ColumnWithName
237where
238	T: IsTemporal + Copy + PartialOrd,
239{
240	reifydb_assertions! {
241		assert_eq!(l.len(), r.len());
242	}
243
244	let data: Vec<bool> =
245		l.data().iter()
246			.zip(r.data().iter())
247			.map(|(l_val, r_val)| Op::compare_ordering(l_val.partial_cmp(r_val)))
248			.collect();
249
250	ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data))
251}
252
253#[inline]
254fn compare_uuid<Op: CompareOp, T>(l: &UuidContainer<T>, r: &UuidContainer<T>, fragment: Fragment) -> ColumnWithName
255where
256	T: IsUuid + PartialOrd,
257{
258	reifydb_assertions! {
259		assert_eq!(l.len(), r.len());
260	}
261
262	let data: Vec<bool> =
263		l.data().iter()
264			.zip(r.data().iter())
265			.map(|(l_val, r_val)| Op::compare_ordering(l_val.partial_cmp(r_val)))
266			.collect();
267
268	ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data))
269}
270
271#[inline]
272fn compare_identity_id<Op: CompareOp>(
273	l: &IdentityIdContainer,
274	r: &IdentityIdContainer,
275	fragment: Fragment,
276) -> ColumnWithName {
277	reifydb_assertions! {
278		assert_eq!(l.len(), r.len());
279	}
280
281	let data: Vec<bool> =
282		l.iter().zip(r.iter()).map(|(l_val, r_val)| Op::compare_ordering(l_val.partial_cmp(&r_val))).collect();
283
284	ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data))
285}
286
287#[inline]
288fn compare_blob<Op: CompareOp>(l: &BlobContainer, r: &BlobContainer, fragment: Fragment) -> ColumnWithName {
289	reifydb_assertions! {
290		assert_eq!(l.len(), r.len());
291	}
292
293	let data: Vec<bool> = l
294		.iter_bytes()
295		.zip(r.iter_bytes())
296		.map(|(l_val, r_val)| Op::compare_ordering(l_val.partial_cmp(r_val)))
297		.collect();
298
299	ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data))
300}
301
302#[inline]
303fn compare_utf8<Op: CompareOp>(l: &Utf8Container, r: &Utf8Container, fragment: Fragment) -> ColumnWithName {
304	reifydb_assertions! {
305		assert_eq!(l.len(), r.len());
306	}
307
308	let data: Vec<bool> = l
309		.iter_str()
310		.zip(r.iter_str())
311		.map(|(l_val, r_val)| Op::compare_ordering(l_val.partial_cmp(r_val)))
312		.collect();
313
314	ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data))
315}
316
317#[inline]
318fn compare_bool<Op: CompareOp>(l: &BoolContainer, r: &BoolContainer, fragment: Fragment) -> Option<ColumnWithName> {
319	reifydb_assertions! {
320		assert_eq!(l.len(), r.len());
321	}
322
323	let data: Vec<bool> =
324		l.data().iter()
325			.zip(r.data().iter())
326			.filter_map(|(l_val, r_val)| Op::compare_bool(l_val, r_val))
327			.collect();
328
329	if data.len() == l.len() {
330		Some(ColumnWithName::new(Fragment::internal(fragment.text()), ColumnBuffer::bool(data)))
331	} else {
332		None
333	}
334}
335
336pub fn compare_columns<Op: CompareOp>(
337	left: &ColumnWithName,
338	right: &ColumnWithName,
339	fragment: Fragment,
340	error_fn: impl FnOnce(Fragment, ValueType, ValueType) -> Diagnostic,
341) -> Result<ColumnWithName> {
342	binary_op_unwrap_option(left, right, fragment.clone(), |left, right| {
343		dispatch_compare!(
344			&left.data(), &right.data();
345			fragment;
346
347			(ColumnBuffer::Bool(l), ColumnBuffer::Bool(r)) => {
348				if let Some(col) = compare_bool::<Op>(l, r, fragment.clone()) {
349					return Ok(col);
350				}
351				return_error!(error_fn(fragment, left.get_type(), right.get_type()))
352			}
353
354			(ColumnBuffer::Date(l), ColumnBuffer::Date(r)) => {
355				Ok(compare_temporal::<Op, _>(l, r, fragment))
356			},
357			(ColumnBuffer::DateTime(l), ColumnBuffer::DateTime(r)) => {
358				Ok(compare_temporal::<Op, _>(l, r, fragment))
359			},
360			(ColumnBuffer::Time(l), ColumnBuffer::Time(r)) => {
361				Ok(compare_temporal::<Op, _>(l, r, fragment))
362			},
363			(ColumnBuffer::Duration(l), ColumnBuffer::Duration(r)) => {
364				Ok(compare_temporal::<Op, _>(l, r, fragment))
365			},
366
367			(
368				ColumnBuffer::Utf8 {
369					container: l,
370					..
371				},
372				ColumnBuffer::Utf8 {
373					container: r,
374					..
375				},
376			) => {
377				Ok(compare_utf8::<Op>(l, r, fragment))
378			},
379
380			(ColumnBuffer::Uuid4(l), ColumnBuffer::Uuid4(r)) => {
381				Ok(compare_uuid::<Op, _>(l, r, fragment))
382			},
383			(ColumnBuffer::Uuid7(l), ColumnBuffer::Uuid7(r)) => {
384				Ok(compare_uuid::<Op, _>(l, r, fragment))
385			},
386			(ColumnBuffer::IdentityId(l), ColumnBuffer::IdentityId(r)) => {
387				Ok(compare_identity_id::<Op>(l, r, fragment))
388			},
389			(
390				ColumnBuffer::Blob {
391					container: l,
392					..
393				},
394				ColumnBuffer::Blob {
395					container: r,
396					..
397				},
398			) => {
399				Ok(compare_blob::<Op>(l, r, fragment))
400			},
401
402			_ => {
403				return_error!(error_fn(fragment, left.get_type(), right.get_type()))
404			},
405		)
406	})
407}