Skip to main content

linkrs_query/executor/streaming/chunk/
eval.rs

1//! Batch expression evaluation on DataChunk
2
3use crate::executor::expression::evaluator::operations::{
4    BinaryOperationEvaluator, UnaryOperationEvaluator,
5};
6use crate::executor::expression::evaluator::ExpressionEvaluator;
7use crate::executor::expression::ExpressionError;
8use crate::executor::streaming::chunk::core::DataChunk;
9use crate::executor::streaming::context::BorrowedRowContext;
10use crate::executor::streaming::slot::SlotId;
11use crate::executor::streaming::subquery::EvalEnv;
12use linkrs_core::types::expr::Expression;
13use linkrs_core::Value;
14use std::collections::HashMap;
15
16use super::kind::typed_literal_batch;
17use super::typed::{
18    typed_binary_batch, typed_cast_batch, typed_column_batch, typed_unary_batch, TypedBatch,
19};
20
21impl DataChunk {
22    // ── Batch expression evaluation ──
23
24    /// Evaluation length: row count, falling back to the typed layout for
25    /// rowless chunks whose scan skipped the row view. Typed arms stay in
26    /// lockstep like rows, so either source addresses the same positions.
27    fn eval_len(&self) -> usize {
28        if self.rows.is_empty() {
29            self.typed_len().unwrap_or(0)
30        } else {
31            self.rows.len()
32        }
33    }
34
35    pub fn evaluate_expressions(
36        &mut self,
37        expressions: &[Expression],
38        env: Option<&EvalEnv>,
39    ) -> Result<Vec<Vec<Value>>, ExpressionError> {
40        if self.eval_len() == 0 {
41            return Ok(vec![Vec::new(); expressions.len()]);
42        }
43        if expressions
44            .iter()
45            .all(|e| matches!(e, Expression::Variable(_)))
46        {
47            let mut columns = Vec::with_capacity(expressions.len());
48            for expr in expressions {
49                if let Expression::Variable(name) = expr {
50                    let slot = self
51                        .layout
52                        .slot_id(name)
53                        .ok_or_else(|| ExpressionError::undefined_variable(name))?;
54                    let col = self
55                        .get_column(slot)
56                        .ok_or_else(|| ExpressionError::undefined_variable(name))?;
57                    columns.push(col);
58                }
59            }
60            for _ in 0..expressions.len() {
61                self.count_columnar(true);
62            }
63            return Ok(columns);
64        }
65        if expressions
66            .iter()
67            .all(|e| matches!(e, Expression::Literal(_)))
68        {
69            let mut columns = Vec::with_capacity(expressions.len());
70            let len = self.eval_len();
71            for expr in expressions {
72                if let Expression::Literal(v) = expr {
73                    columns.push(vec![v.clone(); len]);
74                }
75            }
76            for _ in 0..expressions.len() {
77                self.count_columnar(true);
78            }
79            return Ok(columns);
80        }
81        let mut results = Vec::with_capacity(expressions.len());
82        for expr in expressions {
83            results.push(self.evaluate_expression(expr, env)?);
84        }
85        Ok(results)
86    }
87
88    /// Batch-evaluate a list of output expressions, preferring the typed
89    /// columnar path.
90    ///
91    /// Returns `Ok(None)` as soon as any expression is not covered by the
92    /// typed fast path (no `typed_columns`, a kind mismatch, or an operator
93    /// outside the typed batch set), letting the caller fall back to the
94    /// compiled-over-rows path with unchanged semantics. This is the entry
95    /// point the Project/Filter hot path uses so a chunk that carries a typed
96    /// layout is evaluated on raw scalars instead of per-row `Value`s.
97    pub fn try_evaluate_expressions_typed(
98        &mut self,
99        expressions: &[Expression],
100        env: Option<&EvalEnv>,
101    ) -> Result<Option<Vec<Vec<Value>>>, ExpressionError> {
102        if self.eval_len() == 0 {
103            return Ok(Some(vec![Vec::new(); expressions.len()]));
104        }
105        let mut out = Vec::with_capacity(expressions.len());
106        for expr in expressions {
107            match self.try_eval_typed_batch(expr, env)? {
108                Some(batch) => out.push(batch.into_values()),
109                None => return Ok(None),
110            }
111        }
112        Ok(Some(out))
113    }
114
115    pub fn evaluate_expression(
116        &mut self,
117        expression: &Expression,
118        env: Option<&EvalEnv>,
119    ) -> Result<Vec<Value>, ExpressionError> {
120        if self.eval_len() == 0 {
121            return Ok(Vec::new());
122        }
123        if let Ok((result, typed_hit)) = self.try_evaluate_columnar(expression, env) {
124            self.count_columnar(true);
125            if typed_hit {
126                self.count_typed_hit();
127            }
128            return Ok(result);
129        }
130        self.count_columnar(false);
131        debug_assert!(
132            !self.columnar_promise_holds(expression),
133            "flat column promise broken: expression {:?} should have hit the \
134             columnar path but fell back to per-row evaluation",
135            expression
136        );
137        self.evaluate_expression_per_row(expression, env)
138    }
139
140    pub fn evaluate_expression_visible(
141        &mut self,
142        expression: &Expression,
143        env: Option<&EvalEnv>,
144    ) -> Result<Vec<Value>, ExpressionError> {
145        let Some(sel) = self.selection().map(|s| s.to_vec()) else {
146            return self.evaluate_expression(expression, env);
147        };
148        if let Expression::Literal(value) = expression {
149            self.count_columnar(true);
150            self.count_selection_pushed();
151            return Ok(vec![value.clone(); sel.len()]);
152        }
153        if let Expression::Parameter(name) = expression {
154            let value = env
155                .and_then(|env| env.params.as_ref())
156                .and_then(|p| p.get(name).cloned())
157                .ok_or_else(|| ExpressionError::undefined_parameter(name))?;
158            self.count_columnar(true);
159            self.count_selection_pushed();
160            return Ok(vec![value; sel.len()]);
161        }
162        if let Expression::SessionVariable(name) = expression {
163            let value = env
164                .and_then(|env| env.session_variables.as_ref())
165                .and_then(|v| v.get(name).cloned())
166                .ok_or_else(|| {
167                    ExpressionError::type_error(format!(
168                        "Session variable `{}` is not defined",
169                        name
170                    ))
171                })?;
172            self.count_columnar(true);
173            self.count_selection_pushed();
174            return Ok(vec![value; sel.len()]);
175        }
176        let slot: Option<SlotId> = match expression {
177            Expression::Variable(name) => self.layout.slot_id(name),
178            Expression::Property { object, property }
179                if matches!(object.as_ref(), Expression::Variable(_)) =>
180            {
181                if let Expression::Variable(var) = object.as_ref() {
182                    self.layout.slot_id(&format!("{}.{}", var, property))
183                } else {
184                    None
185                }
186            }
187            _ => None,
188        };
189        if let Some(slot) = slot {
190            // Resolve a deferred join-output layout on first typed slot
191            // read; unsupported expressions below never pay the build.
192            self.ensure_typed_columns();
193            let mut out = Vec::with_capacity(sel.len());
194            for &i in &sel {
195                match self.get_typed_by_slot(i, slot) {
196                    Some(v) => out.push(v),
197                    None => return Err(ExpressionError::undefined_variable("column slot")),
198                }
199            }
200            self.count_columnar(true);
201            self.count_selection_pushed();
202            return Ok(out);
203        }
204        let layout = self.get_layout();
205        let mut out = Vec::with_capacity(sel.len());
206        for &i in &sel {
207            let row = &self.rows[i];
208            let mut ctx = match env {
209                Some(env) => BorrowedRowContext::with_env(row, layout.clone(), env),
210                None => BorrowedRowContext::new(row, layout.clone()),
211            };
212            out.push(ExpressionEvaluator::evaluate(expression, &mut ctx)?);
213        }
214        self.count_columnar(false);
215        self.count_selection_pushed();
216        Ok(out)
217    }
218
219    fn try_evaluate_columnar(
220        &mut self,
221        expression: &Expression,
222        env: Option<&EvalEnv>,
223    ) -> Result<(Vec<Value>, bool), ExpressionError> {
224        let mut col_cache: HashMap<String, Vec<Value>> = HashMap::new();
225        self.collect_variables(expression, &mut col_cache);
226        let mut typed_hit = false;
227        let result = self.eval_with_cache(expression, &col_cache, env, &mut typed_hit)?;
228        Ok((result, typed_hit))
229    }
230
231    fn collect_variables(
232        &mut self,
233        expr: &Expression,
234        col_cache: &mut HashMap<String, Vec<Value>>,
235    ) {
236        match expr {
237            Expression::Variable(name) => {
238                if !col_cache.contains_key(name) {
239                    if let Some(slot) = self.layout.slot_id(name) {
240                        if let Some(col) = self.get_column(slot) {
241                            col_cache.insert(name.clone(), col);
242                        }
243                    }
244                }
245            }
246            Expression::Binary { left, right, .. } => {
247                self.collect_variables(left, col_cache);
248                self.collect_variables(right, col_cache);
249            }
250            Expression::Unary { operand, .. } => {
251                self.collect_variables(operand, col_cache);
252            }
253            Expression::TypeCast { expression, .. } => {
254                self.collect_variables(expression, col_cache);
255            }
256            _ => {}
257        }
258    }
259
260    fn eval_with_cache(
261        &mut self,
262        expression: &Expression,
263        col_cache: &HashMap<String, Vec<Value>>,
264        env: Option<&EvalEnv>,
265        typed_used: &mut bool,
266    ) -> Result<Vec<Value>, ExpressionError> {
267        if let Some(batch) = self.try_eval_typed_batch(expression, env)? {
268            *typed_used = true;
269            return Ok(batch.into_values());
270        }
271        match expression {
272            Expression::Literal(v) => Ok(vec![v.clone(); self.rows.len()]),
273
274            Expression::Variable(name) => {
275                if let Some(col) = col_cache.get(name) {
276                    return Ok(col.clone());
277                }
278                let slot = self
279                    .layout
280                    .slot_id(name)
281                    .ok_or_else(|| ExpressionError::undefined_variable(name))?;
282                self.get_column(slot)
283                    .ok_or_else(|| ExpressionError::undefined_variable(name))
284            }
285
286            Expression::Parameter(name) => {
287                let val = env
288                    .and_then(|env| env.params.as_ref())
289                    .and_then(|p| p.get(name).cloned())
290                    .ok_or_else(|| ExpressionError::undefined_parameter(name))?;
291                Ok(vec![val; self.eval_len()])
292            }
293
294            Expression::SessionVariable(name) => {
295                let val = env
296                    .and_then(|env| env.session_variables.as_ref())
297                    .and_then(|v| v.get(name).cloned())
298                    .ok_or_else(|| {
299                        ExpressionError::type_error(format!(
300                            "Session variable `{}` is not defined",
301                            name
302                        ))
303                    })?;
304                Ok(vec![val; self.eval_len()])
305            }
306
307            Expression::Unary { op, operand } => {
308                let values = self.eval_with_cache(operand, col_cache, env, typed_used)?;
309                values
310                    .into_iter()
311                    .map(|v| UnaryOperationEvaluator::evaluate(op, &v))
312                    .collect()
313            }
314
315            Expression::Binary { left, op, right } => {
316                let left_values = self.eval_with_cache(left, col_cache, env, typed_used)?;
317                let right_values = self.eval_with_cache(right, col_cache, env, typed_used)?;
318                left_values
319                    .into_iter()
320                    .zip(right_values)
321                    .map(|(l, r)| BinaryOperationEvaluator::evaluate(&l, op, &r))
322                    .collect()
323            }
324
325            Expression::TypeCast {
326                expression,
327                target_type,
328            } => {
329                let values = self.eval_with_cache(expression, col_cache, env, typed_used)?;
330                values
331                    .into_iter()
332                    .map(|v| ExpressionEvaluator::eval_type_cast(&v, target_type))
333                    .collect()
334            }
335
336            Expression::Property { object, property } => {
337                if let Expression::Variable(var_name) = object.as_ref() {
338                    let compound = format!("{}.{}", var_name, property);
339                    if let Some(slot) = self.layout.slot_id(&compound) {
340                        if let Some(col) = self.get_column(slot) {
341                            return Ok(col);
342                        }
343                    }
344                    return Err(ExpressionError::type_error(
345                        "Property access requires per-row evaluation",
346                    ));
347                }
348                Err(ExpressionError::type_error(
349                    "Property access requires per-row evaluation",
350                ))
351            }
352
353            _ => Err(ExpressionError::type_error(
354                "Expression requires per-row evaluation",
355            )),
356        }
357    }
358
359    fn evaluate_expression_per_row(
360        &self,
361        expression: &Expression,
362        env: Option<&EvalEnv>,
363    ) -> Result<Vec<Value>, ExpressionError> {
364        let layout = self.get_layout();
365        let mut results = Vec::with_capacity(self.rows.len());
366        for row in &self.rows {
367            let mut ctx = match env {
368                Some(env) => BorrowedRowContext::with_env(row, layout.clone(), env),
369                None => BorrowedRowContext::new(row, layout.clone()),
370            };
371            results.push(ExpressionEvaluator::evaluate(expression, &mut ctx)?);
372        }
373        Ok(results)
374    }
375
376    // ── Typed batch evaluation ──
377
378    fn try_eval_typed_batch(
379        &mut self,
380        expression: &Expression,
381        env: Option<&EvalEnv>,
382    ) -> Result<Option<TypedBatch>, ExpressionError> {
383        match expression {
384            Expression::Literal(v) => Ok(typed_literal_batch(v, self.eval_len())),
385            Expression::Parameter(name) => {
386                let val = env
387                    .and_then(|env| env.params.as_ref())
388                    .and_then(|p| p.get(name).cloned())
389                    .ok_or_else(|| ExpressionError::undefined_parameter(name))?;
390                Ok(typed_literal_batch(&val, self.eval_len()))
391            }
392            Expression::SessionVariable(name) => {
393                let val = env
394                    .and_then(|env| env.session_variables.as_ref())
395                    .and_then(|v| v.get(name).cloned())
396                    .ok_or_else(|| {
397                        ExpressionError::type_error(format!(
398                            "Session variable `{}` is not defined",
399                            name
400                        ))
401                    })?;
402                Ok(typed_literal_batch(&val, self.eval_len()))
403            }
404            Expression::Variable(name) => {
405                let slot = match self.layout.slot_id(name) {
406                    Some(slot) => slot,
407                    None => return Ok(None),
408                };
409                // Resolve a deferred join-output layout on first typed
410                // slot read; unsupported expressions never pay the build.
411                self.ensure_typed_columns();
412                Ok(self.typed_column(slot).and_then(typed_column_batch))
413            }
414            Expression::Property { object, property } => {
415                // Flat property access (e.g. `p.age`) resolves to the
416                // compound slot `p.age` in the layout; when that column is
417                // typed (I64/F64/I32/Bool) the whole predicate can run on the
418                // raw batch instead of per-row Value evaluation.
419                let Expression::Variable(var_name) = object.as_ref() else {
420                    return Ok(None);
421                };
422                let compound = format!("{}.{}", var_name, property);
423                let slot = match self.layout.slot_id(&compound) {
424                    Some(slot) => slot,
425                    None => return Ok(None),
426                };
427                self.ensure_typed_columns();
428                Ok(self.typed_column(slot).and_then(typed_column_batch))
429            }
430            Expression::Unary { op, operand } => {
431                let Some(batch) = self.try_eval_typed_batch(operand, env)? else {
432                    return Ok(None);
433                };
434                Ok(typed_unary_batch(op, batch))
435            }
436            Expression::Binary { left, op, right } => {
437                let Some(left_batch) = self.try_eval_typed_batch(left, env)? else {
438                    return Ok(None);
439                };
440                let Some(right_batch) = self.try_eval_typed_batch(right, env)? else {
441                    return Ok(None);
442                };
443                Ok(typed_binary_batch(op, &left_batch, &right_batch))
444            }
445            Expression::TypeCast {
446                expression,
447                target_type,
448            } => {
449                let Some(batch) = self.try_eval_typed_batch(expression, env)? else {
450                    return Ok(None);
451                };
452                Ok(typed_cast_batch(batch, target_type))
453            }
454            _ => Ok(None),
455        }
456    }
457
458    #[cfg(debug_assertions)]
459    pub(super) fn columnar_promise_holds(&self, expr: &Expression) -> bool {
460        use linkrs_core::types::operators::{BinaryOperator, UnaryOperator};
461        match expr {
462            Expression::Literal(_) | Expression::Parameter(_) | Expression::Variable(_) => true,
463            Expression::Unary { op, operand } => {
464                // `-` and `NOT` on a NULL row error in the value path, so the
465                // promise does not hold over nullable columns.
466                if matches!(op, UnaryOperator::Minus | UnaryOperator::Not)
467                    && self.expr_references_nullable_column(operand)
468                {
469                    return false;
470                }
471                self.columnar_promise_holds(operand)
472            }
473            Expression::Binary { left, op, right } => {
474                // Arithmetic and boolean And/Or error on NULL rows in the
475                // value path.
476                if matches!(
477                    op,
478                    BinaryOperator::Add
479                        | BinaryOperator::Subtract
480                        | BinaryOperator::Multiply
481                        | BinaryOperator::Divide
482                        | BinaryOperator::Modulo
483                        | BinaryOperator::Exponent
484                        | BinaryOperator::And
485                        | BinaryOperator::Or
486                ) && (self.expr_references_nullable_column(left)
487                    || self.expr_references_nullable_column(right))
488                {
489                    return false;
490                }
491                self.columnar_promise_holds(left) && self.columnar_promise_holds(right)
492            }
493            Expression::TypeCast { expression, .. } => self.columnar_promise_holds(expression),
494            Expression::Property { object, property } => {
495                if let Expression::Variable(var) = object.as_ref() {
496                    let compound = format!("{}.{}", var, property);
497                    return self.layout.slot_id(&compound).is_some();
498                }
499                false
500            }
501            _ => false,
502        }
503    }
504
505    /// Whether any column referenced by `expr` contains a NULL row.
506    #[cfg(debug_assertions)]
507    fn expr_references_nullable_column(&self, expr: &Expression) -> bool {
508        let slot_has_null = |slot: usize| {
509            self.rows
510                .iter()
511                .any(|row| row.get(slot).is_some_and(Value::is_null))
512        };
513        match expr {
514            Expression::Variable(name) => self.layout.slot_id(name).is_some_and(&slot_has_null),
515            Expression::Property { object, property } => {
516                if let Expression::Variable(var) = object.as_ref() {
517                    let compound = format!("{}.{}", var, property);
518                    return self.layout.slot_id(&compound).is_some_and(slot_has_null);
519                }
520                false
521            }
522            Expression::Unary { operand, .. } => self.expr_references_nullable_column(operand),
523            Expression::Binary { left, right, .. } => {
524                self.expr_references_nullable_column(left)
525                    || self.expr_references_nullable_column(right)
526            }
527            Expression::TypeCast { expression, .. } => {
528                self.expr_references_nullable_column(expression)
529            }
530            _ => false,
531        }
532    }
533
534    #[cfg(not(debug_assertions))]
535    fn columnar_promise_holds(&self, _expr: &Expression) -> bool {
536        false
537    }
538}