Skip to main content

reifydb_evaluate/lower/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4mod arith;
5mod cast;
6mod compare;
7mod conditional;
8mod construct;
9mod contains;
10mod direct;
11mod error;
12mod field_access;
13mod in_list;
14mod literal;
15mod prefix;
16mod routine;
17mod schema;
18mod variant;
19mod xor;
20
21use std::sync::Arc;
22
23use arrow_array::{ArrayRef, RecordBatch, RecordBatchOptions, new_empty_array};
24use arrow_schema::{FieldRef, SchemaRef};
25use datafusion_expr::{
26	BinaryExpr, Expr, Operator, ScalarUDF, ScalarUDFImpl, Signature, Volatility, execution_props::ExecutionProps,
27	expr::ScalarFunction, physical_planning_context::PhysicalPlanningContext,
28};
29use datafusion_physical_expr::{PhysicalExpr, create_physical_expr};
30use reifydb_core::{
31	expression::{Expression, PrefixExpression, PrefixOperator, name::display_label},
32	internal_error,
33	value::column::{arith::ArithOp, factory::default_typed},
34};
35use reifydb_runtime::sync::mutex::Mutex;
36use reifydb_value::{
37	error::{BinaryOp, Diagnostic, Error, LogicalOp},
38	fragment::Fragment,
39	value::{
40		column_view::ColumnView,
41		value_type::{
42			ValueType,
43			field::{FieldType, from_field, to_field},
44		},
45	},
46};
47
48use crate::{
49	Result,
50	expression::{context::EvalContext, logic::execute_logical_op, prefix::prefix_apply},
51	lower::{
52		cast::lower_cast,
53		direct::{Direct, Spliced},
54		error::from_datafusion,
55		schema::positional_schema,
56	},
57};
58
59pub struct LoweredExpr {
60	expression: Expression,
61	operator: &'static str,
62	state: Mutex<Option<Arc<Ready>>>,
63}
64
65struct Ready {
66	schema: SchemaRef,
67	plan: Plan,
68}
69
70enum Plan {
71	Lowered {
72		expr: Arc<dyn PhysicalExpr>,
73		field: FieldRef,
74	},
75	Direct(Direct),
76	Spliced(Spliced),
77	Failed(Box<Diagnostic>),
78}
79
80struct Node {
81	expr: Expr,
82	field: FieldRef,
83}
84
85enum Stop {
86	RowChanging,
87	Error(Error),
88}
89
90impl From<Error> for Stop {
91	fn from(error: Error) -> Self {
92		Stop::Error(error)
93	}
94}
95
96type Lowered<T> = std::result::Result<T, Stop>;
97
98pub fn compile_check(expression: &Expression) -> Result<()> {
99	match variant::compile_time_error(expression) {
100		Some(error) => Err(error),
101		None => Ok(()),
102	}
103}
104
105impl LoweredExpr {
106	pub fn new(expression: Expression, operator: &'static str) -> Self {
107		Self {
108			expression,
109			operator,
110			state: Mutex::new(None),
111		}
112	}
113
114	pub fn access_column_name(&self) -> Option<&str> {
115		match &self.expression {
116			Expression::AccessSource(access) => Some(access.column.name.text()),
117			_ => None,
118		}
119	}
120
121	pub fn evaluate(&self, ctx: &EvalContext) -> Result<(FieldRef, ArrayRef)> {
122		let ready = self.ready(ctx);
123		match &ready.plan {
124			Plan::Lowered {
125				expr,
126				field,
127			} => {
128				let sized;
129				let batch = if ctx.batch.num_columns() == 0 && ctx.batch.num_rows() != ctx.row_count {
130					sized = RecordBatch::try_new_with_options(
131						ctx.batch.schema(),
132						vec![],
133						&RecordBatchOptions::new().with_row_count(Some(ctx.row_count)),
134					)
135					.map_err(|err| from_datafusion(self.operator, err.into()))?;
136					&sized
137				} else {
138					&ctx.batch
139				};
140				let value = expr.evaluate(batch).map_err(|err| from_datafusion(self.operator, err))?;
141				let array = value
142					.into_array(ctx.row_count)
143					.map_err(|err| from_datafusion(self.operator, err))?;
144				let array = match ctx.take {
145					Some(take) if take < array.len() => array.slice(0, take),
146					_ => array,
147				};
148				if !field.is_nullable() && array.logical_null_count() > 0 {
149					let mut field_type = from_field(field)?;
150					field_type.value_type = field_type
151						.value_type
152						.map(|value_type| ValueType::Option(Box::new(value_type)));
153					return Ok((Arc::new(to_field(field.name(), &field_type)), array));
154				}
155				Ok((field.clone(), array))
156			}
157			Plan::Direct(direct) => direct.evaluate(ctx),
158			Plan::Spliced(spliced) => spliced.evaluate(ctx),
159			Plan::Failed(diagnostic) => Err(Error(diagnostic.clone())),
160		}
161	}
162
163	pub fn output_field(&self, ctx: &EvalContext) -> Option<FieldRef> {
164		match &self.ready(ctx).plan {
165			Plan::Lowered {
166				field,
167				..
168			} => Some(field.clone()),
169			Plan::Direct(_) | Plan::Spliced(_) | Plan::Failed(_) => None,
170		}
171	}
172
173	fn ready(&self, ctx: &EvalContext) -> Arc<Ready> {
174		let schema = ctx.batch.schema();
175		let mut state = self.state.lock();
176		if let Some(ready) = state.as_ref()
177			&& same_schema(&ready.schema, &schema)
178		{
179			return ready.clone();
180		}
181		let ready = Arc::new(Ready {
182			plan: self.plan(ctx),
183			schema,
184		});
185		if !matches!(ready.plan, Plan::Failed(_)) {
186			*state = Some(ready.clone());
187		}
188		ready
189	}
190
191	fn plan(&self, ctx: &EvalContext) -> Plan {
192		match lower_root(ctx, self.operator, &self.expression) {
193			Ok((expr, field)) => Plan::Lowered {
194				expr,
195				field,
196			},
197			Err(Stop::Error(error)) => Plan::Failed(error.0),
198			Err(Stop::RowChanging) => {
199				let planned = match &self.expression {
200					Expression::Call(_) => {
201						Direct::new(ctx, self.operator, &self.expression).map(Plan::Direct)
202					}
203					Expression::Alias(alias)
204						if matches!(alias.expression.as_ref(), Expression::Call(_)) =>
205					{
206						Direct::new(ctx, self.operator, &self.expression).map(Plan::Direct)
207					}
208					_ => Spliced::new(ctx, self.operator, &self.expression).map(Plan::Spliced),
209				};
210				planned.unwrap_or_else(|error| Plan::Failed(error.0))
211			}
212		}
213	}
214}
215
216fn same_schema(left: &SchemaRef, right: &SchemaRef) -> bool {
217	Arc::ptr_eq(left, right) || left == right
218}
219
220fn lower_root<'e>(
221	ctx: &EvalContext,
222	operator: &'static str,
223	expression: &'e Expression,
224) -> Lowered<(Arc<dyn PhysicalExpr>, FieldRef)> {
225	if let Some(error) = variant::compile_time_error(expression) {
226		return Err(Stop::Error(error));
227	}
228	let node = lower_node(ctx, operator, expression)?;
229	let schema = positional_schema(operator, ctx.batch.schema_ref())?;
230	let expr =
231		create_physical_expr(&node.expr, &schema, &ExecutionProps::new(), &PhysicalPlanningContext::default())
232			.map_err(|err| from_datafusion(operator, err))?;
233	Ok((expr, node.field))
234}
235
236fn lower_node<'e>(ctx: &EvalContext, operator: &'static str, expression: &'e Expression) -> Lowered<Node> {
237	match expression {
238		Expression::Column(column) => Ok(schema::column(ctx, operator, column)?),
239		Expression::AccessSource(access) => Ok(schema::access(ctx, access)?),
240		Expression::Constant(constant) => {
241			Ok(literal::constant(operator, constant, display_label(expression).text())?)
242		}
243		Expression::Prefix(prefix) => match prefix.operator {
244			PrefixOperator::Not(_) => lower_not(ctx, operator, expression, prefix),
245			PrefixOperator::Minus(_) | PrefixOperator::Plus(_) => {
246				prefix::lower_prefix(ctx, operator, expression, prefix)
247			}
248		},
249		Expression::And(and) => lower_logical(
250			ctx,
251			operator,
252			expression,
253			(&and.left, &and.right),
254			&and.full_fragment_owned(),
255			(LogicalOp::And, Operator::And),
256		),
257		Expression::Or(or) => lower_logical(
258			ctx,
259			operator,
260			expression,
261			(&or.left, &or.right),
262			&or.full_fragment_owned(),
263			(LogicalOp::Or, Operator::Or),
264		),
265		Expression::Equal(e) => compare::lower_compare(
266			ctx,
267			operator,
268			expression,
269			(&e.left, &e.right),
270			e.full_fragment_owned(),
271			(BinaryOp::Equal, Operator::Eq),
272		),
273		Expression::NotEqual(e) => compare::lower_compare(
274			ctx,
275			operator,
276			expression,
277			(&e.left, &e.right),
278			e.full_fragment_owned(),
279			(BinaryOp::NotEqual, Operator::NotEq),
280		),
281		Expression::LessThan(e) => compare::lower_compare(
282			ctx,
283			operator,
284			expression,
285			(&e.left, &e.right),
286			e.full_fragment_owned(),
287			(BinaryOp::LessThan, Operator::Lt),
288		),
289		Expression::LessThanEqual(e) => compare::lower_compare(
290			ctx,
291			operator,
292			expression,
293			(&e.left, &e.right),
294			e.full_fragment_owned(),
295			(BinaryOp::LessThanEqual, Operator::LtEq),
296		),
297		Expression::GreaterThan(e) => compare::lower_compare(
298			ctx,
299			operator,
300			expression,
301			(&e.left, &e.right),
302			e.full_fragment_owned(),
303			(BinaryOp::GreaterThan, Operator::Gt),
304		),
305		Expression::GreaterThanEqual(e) => compare::lower_compare(
306			ctx,
307			operator,
308			expression,
309			(&e.left, &e.right),
310			e.full_fragment_owned(),
311			(BinaryOp::GreaterThanEqual, Operator::GtEq),
312		),
313		Expression::Between(e) => compare::lower_between(ctx, operator, expression, e),
314		Expression::Add(e) => arith::lower_arith(
315			ctx,
316			operator,
317			expression,
318			(&e.left, &e.right),
319			e.full_fragment_owned(),
320			ArithOp::Add,
321		),
322		Expression::Sub(e) => arith::lower_arith(
323			ctx,
324			operator,
325			expression,
326			(&e.left, &e.right),
327			e.full_fragment_owned(),
328			ArithOp::Sub,
329		),
330		Expression::Mul(e) => arith::lower_arith(
331			ctx,
332			operator,
333			expression,
334			(&e.left, &e.right),
335			e.full_fragment_owned(),
336			ArithOp::Mul,
337		),
338		Expression::Div(e) => arith::lower_arith(
339			ctx,
340			operator,
341			expression,
342			(&e.left, &e.right),
343			e.full_fragment_owned(),
344			ArithOp::Div,
345		),
346		Expression::Rem(e) => arith::lower_arith(
347			ctx,
348			operator,
349			expression,
350			(&e.left, &e.right),
351			e.full_fragment_owned(),
352			ArithOp::Rem,
353		),
354		Expression::Call(call) => routine::lower_call(ctx, operator, expression, call),
355		Expression::Variable(variable) => Ok(literal::variable(ctx, operator, variable)?),
356		Expression::Parameter(parameter) => Ok(literal::parameter(ctx, operator, parameter)?),
357		Expression::Type(ty) => Ok(literal::type_literal(operator, &ty.ty, &ty.fragment)?),
358		Expression::FieldAccess(access) => match access.object.as_ref() {
359			Expression::Variable(variable) => {
360				Ok(literal::variable_field(ctx, operator, variable, access.field.text())?)
361			}
362			_ => field_access::lower_field_access(ctx, operator, expression, access),
363		},
364		Expression::Alias(alias) => {
365			let inner = lower_node(ctx, operator, &alias.expression)?;
366			Ok(Node {
367				expr: inner.expr,
368				field: Arc::new(inner.field.as_ref().clone().with_name(alias.alias.name())),
369			})
370		}
371		Expression::Cast(cast) => lower_cast(ctx, operator, expression, cast),
372		Expression::In(e) => in_list::lower_in(ctx, operator, expression, e),
373		Expression::If(e) => conditional::lower_if(ctx, operator, expression, e),
374		Expression::Xor(e) => xor::lower_xor(ctx, operator, expression, e),
375		Expression::Tuple(e) => construct::lower_construct(
376			ctx,
377			operator,
378			construct::Shape::Tuple,
379			e.expressions.iter().collect(),
380			&e.fragment,
381		),
382		Expression::List(e) => construct::lower_construct(
383			ctx,
384			operator,
385			construct::Shape::List,
386			e.expressions.iter().collect(),
387			&e.fragment,
388		),
389		Expression::Record(e) => construct::lower_construct(
390			ctx,
391			operator,
392			construct::Shape::Record(e.fields.iter().map(|(name, _)| name.text().to_string()).collect()),
393			e.fields.iter().map(|(_, expression)| expression).collect(),
394			&e.fragment,
395		),
396		Expression::Map(e) => construct::lower_projection(ctx, operator, &e.expressions, &e.fragment),
397		Expression::Extend(e) => construct::lower_projection(ctx, operator, &e.expressions, &e.fragment),
398		Expression::Contains(e) => contains::lower_contains(ctx, operator, expression, e),
399		Expression::IsVariant(e) => Ok(variant::lower_is_variant(ctx, operator, e)?),
400		Expression::SumTypeConstructor(_) => Err(Stop::Error(
401			variant::compile_time_error(expression).expect("a sum type constructor is a compile error"),
402		)),
403	}
404}
405
406fn lower_logical<'e>(
407	ctx: &EvalContext,
408	operator: &'static str,
409	expression: &'e Expression,
410	(left, right): (&'e Expression, &'e Expression),
411	fragment: &Fragment,
412	(logical_op, df_op): (LogicalOp, Operator),
413) -> Lowered<Node> {
414	let left = lower_node(ctx, operator, left)?;
415	let right = lower_node(ctx, operator, right)?;
416	execute_logical_op(&logic_probe(&left.field)?, &logic_probe(&right.field)?, fragment, logical_op)?;
417	let nullable = left.field.is_nullable() || right.field.is_nullable();
418	Ok(Node {
419		expr: Expr::BinaryExpr(BinaryExpr::new(
420			Box::new(bool_operand(left)?),
421			df_op,
422			Box::new(bool_operand(right)?),
423		)),
424		field: bool_field(display_label(expression).text(), nullable),
425	})
426}
427
428fn lower_not<'e>(
429	ctx: &EvalContext,
430	operator: &'static str,
431	expression: &'e Expression,
432	prefix: &'e PrefixExpression,
433) -> Lowered<Node> {
434	let inner = lower_node(ctx, operator, &prefix.expression)?;
435	if !is_untyped_none(&inner.field)? {
436		let fragment = prefix.full_fragment_owned();
437		prefix_apply(&empty_of(&inner.field), &prefix.operator, &fragment)?;
438		if let Some(value_type) = from_field(&inner.field)?.value_type {
439			prefix_apply(&default_typed("probe", value_type, 1), &prefix.operator, &fragment)?;
440		}
441	}
442	let nullable = inner.field.is_nullable();
443	Ok(Node {
444		expr: Expr::Not(Box::new(bool_operand(inner)?)),
445		field: bool_field(display_label(expression).text(), nullable),
446	})
447}
448
449fn logic_probe(field: &FieldRef) -> Result<(FieldRef, ArrayRef)> {
450	if is_untyped_none(field)? {
451		return Ok(empty_of(&bool_field("none", true)));
452	}
453	Ok(empty_of(&Arc::new(field.as_ref().clone().with_nullable(false))))
454}
455
456fn bool_operand(node: Node) -> Result<Expr> {
457	if is_untyped_none(&node.field)? {
458		return Ok(literal::none_bool());
459	}
460	Ok(node.expr)
461}
462
463fn is_untyped_none(field: &FieldRef) -> Result<bool> {
464	Ok(ColumnView::try_from(&empty_of(field))?.is_untyped_none())
465}
466
467fn inner_type(field: &FieldRef) -> Result<ValueType> {
468	match from_field(field)?.value_type {
469		Some(value_type) => Ok(value_type.inner_type().clone()),
470		None => Err(internal_error!("operand {} has no value type", field.name())),
471	}
472}
473
474fn empty_of(field: &FieldRef) -> (FieldRef, ArrayRef) {
475	(field.clone(), new_empty_array(field.data_type()))
476}
477
478fn bool_field(name: &str, nullable: bool) -> FieldRef {
479	typed_field(name, ValueType::Boolean, nullable)
480}
481
482fn typed_field(name: &str, value_type: ValueType, nullable: bool) -> FieldRef {
483	let value_type = match nullable {
484		true => ValueType::Option(Box::new(value_type)),
485		false => value_type,
486	};
487	Arc::new(to_field(name, &FieldType::from(value_type)))
488}
489
490fn udf(function: impl ScalarUDFImpl + 'static, args: Vec<Expr>) -> Expr {
491	Expr::ScalarFunction(ScalarFunction::new_udf(Arc::new(ScalarUDF::new_from_impl(function)), args))
492}
493
494fn volatile() -> Signature {
495	Signature::user_defined(Volatility::Volatile)
496}