Skip to main content

reifydb_evaluate/expression/
logic.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_core::value::column::{ColumnWithName, buffer::ColumnBuffer};
5use reifydb_value::{
6	error::{LogicalOp, OperandCategory, TypeError},
7	fragment::Fragment,
8	util::bitvec::BitVec,
9};
10
11use super::option::apply_option_bitvec;
12use crate::Result;
13
14pub(crate) fn try_short_circuit_and(
15	l: &ColumnWithName,
16	fragment: &Fragment,
17	row_count: usize,
18) -> Option<ColumnWithName> {
19	if is_all_false_defined(l.data()) {
20		Some(ColumnWithName::new(fragment.clone(), ColumnBuffer::bool(vec![false; row_count])))
21	} else {
22		None
23	}
24}
25
26pub(crate) fn try_short_circuit_or(
27	l: &ColumnWithName,
28	fragment: &Fragment,
29	row_count: usize,
30) -> Option<ColumnWithName> {
31	if is_all_true_defined(l.data()) {
32		Some(ColumnWithName::new(fragment.clone(), ColumnBuffer::bool(vec![true; row_count])))
33	} else {
34		None
35	}
36}
37
38fn is_all_false_defined(buffer: &ColumnBuffer) -> bool {
39	match buffer {
40		ColumnBuffer::Bool(c) => !c.is_empty() && c.data().none(),
41		ColumnBuffer::Option {
42			inner,
43			bitvec,
44		} => {
45			if !bitvec.all_ones() {
46				return false;
47			}
48			matches!(inner.as_ref(), ColumnBuffer::Bool(c) if !c.is_empty() && c.data().none())
49		}
50		_ => false,
51	}
52}
53
54fn is_all_true_defined(buffer: &ColumnBuffer) -> bool {
55	match buffer {
56		ColumnBuffer::Bool(c) => !c.is_empty() && c.data().all_ones(),
57		ColumnBuffer::Option {
58			inner,
59			bitvec,
60		} => {
61			if !bitvec.all_ones() {
62				return false;
63			}
64			matches!(inner.as_ref(), ColumnBuffer::Bool(c) if !c.is_empty() && c.data().all_ones())
65		}
66		_ => false,
67	}
68}
69
70fn is_all_none(bv: Option<&BitVec>) -> bool {
71	match bv {
72		Some(bv) => bv.count_ones() == 0,
73		None => false,
74	}
75}
76
77pub fn execute_logical_op(
78	left: &ColumnWithName,
79	right: &ColumnWithName,
80	fragment: &Fragment,
81	logical_op: LogicalOp,
82	bool_fn: fn(bool, bool) -> bool,
83) -> Result<ColumnWithName> {
84	let (left_data, left_bv) = left.data().unwrap_option();
85	let (right_data, right_bv) = right.data().unwrap_option();
86	let len = left_data.len();
87
88	let synthetic = BitVec::repeat(len, false);
89
90	let (l_v_bits, l_valid_bv) = match left_data {
91		ColumnBuffer::Bool(c) => (c.data(), left_bv),
92		_ if is_all_none(left_bv) => (&synthetic, Some(&synthetic)),
93		_ => return type_error(&logical_op, fragment, left_data, right_data),
94	};
95	let (r_v_bits, r_valid_bv) = match right_data {
96		ColumnBuffer::Bool(c) => (c.data(), right_bv),
97		_ if is_all_none(right_bv) => (&synthetic, Some(&synthetic)),
98		_ => return type_error(&logical_op, fragment, left_data, right_data),
99	};
100
101	let value_data: Vec<bool> =
102		l_v_bits.iter().zip(r_v_bits.iter()).map(|(l_val, r_val)| bool_fn(l_val, r_val)).collect();
103	let value_buffer = ColumnBuffer::bool(value_data);
104
105	let result_bv = compute_kleene_validity(&logical_op, l_valid_bv, r_valid_bv, l_v_bits, r_v_bits, len);
106
107	let final_buffer = match result_bv {
108		Some(bv) => apply_option_bitvec(value_buffer, bv),
109		None => value_buffer,
110	};
111
112	Ok(ColumnWithName::new(fragment.clone(), final_buffer))
113}
114
115fn type_error(
116	logical_op: &LogicalOp,
117	fragment: &Fragment,
118	left: &ColumnBuffer,
119	right: &ColumnBuffer,
120) -> Result<ColumnWithName> {
121	let category = if left.is_number() || right.is_number() {
122		OperandCategory::Number
123	} else if left.is_text() || right.is_text() {
124		OperandCategory::Text
125	} else if left.is_temporal() || right.is_temporal() {
126		OperandCategory::Temporal
127	} else if left.is_uuid() || right.is_uuid() {
128		OperandCategory::Uuid
129	} else {
130		unimplemented!("{} {:?} {}", left.get_type(), logical_op, right.get_type());
131	};
132	Err(TypeError::LogicalOperatorNotApplicable {
133		operator: logical_op.clone(),
134		operand_category: category,
135		fragment: fragment.clone(),
136	}
137	.into())
138}
139
140fn compute_kleene_validity(
141	logical_op: &LogicalOp,
142	left_bv: Option<&BitVec>,
143	right_bv: Option<&BitVec>,
144	l_data: &BitVec,
145	r_data: &BitVec,
146	len: usize,
147) -> Option<BitVec> {
148	if left_bv.is_none() && right_bv.is_none() {
149		return None;
150	}
151	let bv = BitVec::from_fn(len, |i| {
152		let l_valid = left_bv.is_none_or(|bv| bv.get(i));
153		let r_valid = right_bv.is_none_or(|bv| bv.get(i));
154		let l_v = l_data.get(i);
155		let r_v = r_data.get(i);
156		let both_valid = l_valid && r_valid;
157		let l_false = l_valid && !l_v;
158		let r_false = r_valid && !r_v;
159		let l_true = l_valid && l_v;
160		let r_true = r_valid && r_v;
161		match logical_op {
162			LogicalOp::And => both_valid || l_false || r_false,
163			LogicalOp::Or => both_valid || l_true || r_true,
164			LogicalOp::Xor => both_valid,
165			LogicalOp::Not => unreachable!("NOT is unary; not handled by execute_logical_op"),
166		}
167	});
168	Some(bv)
169}