Skip to main content

radixdb_executor/expression/
evaluator_bridge.rs

1// Copyright 2026 RadixDB Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// CompiledEvaluator Bridge
16//
17// Provides an Evaluator-compatible API using the Expression VM internally.
18// This allows gradual migration from AST-based evaluation to bytecode execution.
19//
20// Design:
21// - Matches Evaluator's public API (new, init_columns, set_row_array, evaluate)
22// - Uses per-evaluator local cache for compiled programs
23// - Uses ExprVM for execution
24//
25// Performance Optimization:
26// - For closure-based filtering, use `RowFilter` instead of creating evaluators per-row
27// - `RowFilter` pre-compiles the expression once and shares `CompactArc<Program>` across threads
28// - The VM is lightweight and can be created per-thread without performance penalty
29
30use std::hash::{Hash, Hasher};
31use std::num::NonZeroUsize;
32use std::sync::Arc;
33
34use lru::LruCache;
35use parking_lot::Mutex;
36use radixdb_core::ParamVec;
37use radixdb_core::{CompactArc, StringMap};
38use rustc_hash::{FxHashMap, FxHasher};
39
40use super::compiler::{CompileContext, ExprCompiler};
41use super::execution_context::ExecuteContext;
42use super::program::Program;
43use super::vm::ExprVM;
44use radixdb_core::{Error, Result, Row, Value};
45use radixdb_functions::{global_registry, FunctionRegistry};
46use radixdb_sql::ast::Expression;
47
48use crate::context::{ExecutionContext, StoredFunctionInvoker};
49
50// ============================================================================
51// PROGRAM CACHE - Global cache for compiled expression programs
52// ============================================================================
53
54/// Maximum number of cached programs (LRU eviction)
55const PROGRAM_CACHE_SIZE: usize = 256;
56
57/// Global cache for compiled programs using O(1) LRU eviction.
58/// Uses parking_lot::Mutex for efficient locking.
59#[derive(Clone)]
60struct ProgramCacheEntry {
61    expression: Expression,
62    columns: Vec<String>,
63    registry_generation: u64,
64    program: SharedProgram,
65}
66
67#[derive(Clone)]
68struct LocalProgramCacheEntry {
69    expression: Expression,
70    registry_generation: u64,
71    program: SharedProgram,
72}
73
74static PROGRAM_CACHE: Mutex<Option<LruCache<u64, ProgramCacheEntry>>> = Mutex::new(None);
75
76/// Clear the program cache. Call on database drop to release memory.
77pub fn clear_program_cache() {
78    let mut guard = PROGRAM_CACHE.lock();
79    *guard = None;
80}
81
82fn checked_alias_map(aliases: &[(String, usize)]) -> Result<StringMap<u16>> {
83    aliases
84        .iter()
85        .map(|(name, index)| {
86            let index = u16::try_from(*index).map_err(|_| {
87                Error::invalid_argument(format!(
88                    "expression alias '{}' index {} exceeds the u16 bytecode limit",
89                    name, index
90                ))
91            })?;
92            Ok((name.to_lowercase(), index))
93        })
94        .collect()
95}
96
97/// Compute cache key from expression and columns using efficient recursive hashing.
98/// This avoids the overhead of Debug formatting by directly hashing expression structure.
99/// Uses FxHasher which is 2-5x faster than SipHash for small keys.
100fn compute_cache_key(expr: &Expression, columns: &[String], registry_generation: u64) -> u64 {
101    let mut hasher = FxHasher::default();
102    // Use efficient recursive hashing (same as CompiledEvaluator::hash_expression)
103    hash_expression(expr, &mut hasher);
104    // Hash column names
105    columns.hash(&mut hasher);
106    registry_generation.hash(&mut hasher);
107    hasher.finish()
108}
109
110/// Compute a u64 hash of an expression without string allocation.
111/// This is O(expression_size) and avoids Debug formatting overhead.
112/// Use this for cache keys instead of format!("{:?}", expr).
113/// Uses FxHasher which is 2-5x faster than SipHash for small keys.
114#[inline]
115pub fn compute_expression_hash(expr: &Expression) -> u64 {
116    let mut hasher = FxHasher::default();
117    hash_expression(expr, &mut hasher);
118    hasher.finish()
119}
120
121/// Recursively hash an expression without string allocation.
122/// This is O(expression_size) and avoids Debug formatting overhead.
123fn hash_expression(expr: &Expression, hasher: &mut FxHasher) {
124    // First hash the discriminant to distinguish variants
125    std::mem::discriminant(expr).hash(hasher);
126
127    match expr {
128        Expression::Identifier(id) => {
129            id.value_lower.hash(hasher);
130        }
131        Expression::QualifiedIdentifier(qid) => {
132            qid.qualifier.value_lower.hash(hasher);
133            qid.name.value_lower.hash(hasher);
134        }
135        Expression::IntegerLiteral(lit) => {
136            lit.value.hash(hasher);
137        }
138        Expression::FloatLiteral(lit) => {
139            lit.value.to_bits().hash(hasher);
140        }
141        Expression::StringLiteral(lit) => {
142            lit.value.hash(hasher);
143            lit.type_hint.hash(hasher);
144        }
145        Expression::BooleanLiteral(lit) => {
146            lit.value.hash(hasher);
147        }
148        Expression::NullLiteral(_) => {
149            // Just discriminant is enough
150        }
151        Expression::BoundValue(value) => {
152            value.hash(hasher);
153        }
154        Expression::IntervalLiteral(lit) => {
155            lit.value.hash(hasher);
156            lit.unit.hash(hasher);
157        }
158        Expression::Parameter(param) => {
159            param.index.hash(hasher);
160            param.name.hash(hasher);
161        }
162        Expression::Prefix(prefix) => {
163            std::mem::discriminant(&prefix.op_type).hash(hasher);
164            hash_expression(&prefix.right, hasher);
165        }
166        Expression::Infix(infix) => {
167            std::mem::discriminant(&infix.op_type).hash(hasher);
168            hash_expression(&infix.left, hasher);
169            hash_expression(&infix.right, hasher);
170        }
171        Expression::List(list) => {
172            list.elements.len().hash(hasher);
173            for val in &list.elements {
174                hash_expression(val, hasher);
175            }
176        }
177        Expression::Distinct(dist) => {
178            hash_expression(&dist.expr, hasher);
179        }
180        Expression::Exists(exists) => {
181            // Use pointer identity for hashing - avoids expensive Debug format allocation
182            // The subquery AST is stable during query execution
183            (exists.subquery.as_ref() as *const _ as usize).hash(hasher);
184        }
185        Expression::AllAny(aa) => {
186            aa.operator.hash(hasher);
187            std::mem::discriminant(&aa.all_any_type).hash(hasher);
188            hash_expression(&aa.left, hasher);
189            // Use pointer identity for hashing - avoids expensive Debug format allocation
190            (aa.subquery.as_ref() as *const _ as usize).hash(hasher);
191        }
192        Expression::In(in_expr) => {
193            in_expr.not.hash(hasher);
194            hash_expression(&in_expr.left, hasher);
195            hash_expression(&in_expr.right, hasher);
196        }
197        Expression::InHashSet(in_hash) => {
198            in_hash.not.hash(hasher);
199            hash_expression(&in_hash.column, hasher);
200            let mut values: Vec<&Value> = in_hash.values.iter().collect();
201            values.sort_unstable();
202            values.hash(hasher);
203        }
204        Expression::Between(between) => {
205            between.not.hash(hasher);
206            hash_expression(&between.expr, hasher);
207            hash_expression(&between.lower, hasher);
208            hash_expression(&between.upper, hasher);
209        }
210        Expression::Like(like) => {
211            like.operator.hash(hasher);
212            hash_expression(&like.left, hasher);
213            hash_expression(&like.pattern, hasher);
214            if let Some(ref escape) = like.escape {
215                true.hash(hasher);
216                hash_expression(escape, hasher);
217            } else {
218                false.hash(hasher);
219            }
220        }
221        Expression::ScalarSubquery(sq) => {
222            // Use pointer identity for hashing - avoids expensive Debug format allocation
223            (sq.subquery.as_ref() as *const _ as usize).hash(hasher);
224        }
225        Expression::ExpressionList(list) => {
226            list.expressions.len().hash(hasher);
227            for e in &list.expressions {
228                hash_expression(e, hasher);
229            }
230        }
231        Expression::Case(case) => {
232            if let Some(ref val) = case.value {
233                true.hash(hasher);
234                hash_expression(val, hasher);
235            } else {
236                false.hash(hasher);
237            }
238            case.when_clauses.len().hash(hasher);
239            for when_clause in &case.when_clauses {
240                hash_expression(&when_clause.condition, hasher);
241                hash_expression(&when_clause.then_result, hasher);
242            }
243            if let Some(ref else_val) = case.else_value {
244                true.hash(hasher);
245                hash_expression(else_val, hasher);
246            } else {
247                false.hash(hasher);
248            }
249        }
250        Expression::Cast(cast) => {
251            hash_expression(&cast.expr, hasher);
252            cast.type_name.hash(hasher);
253        }
254        Expression::FunctionCall(func) => {
255            func.function.hash(hasher);
256            func.is_distinct.hash(hasher);
257            func.arguments.len().hash(hasher);
258            for arg in &func.arguments {
259                hash_expression(arg, hasher);
260            }
261            if let Some(ref filter) = func.filter {
262                true.hash(hasher);
263                hash_expression(filter, hasher);
264            } else {
265                false.hash(hasher);
266            }
267        }
268        Expression::Aliased(aliased) => {
269            aliased.alias.value_lower.hash(hasher);
270            hash_expression(&aliased.expression, hasher);
271        }
272        Expression::Window(window) => {
273            window.function.function.hash(hasher);
274            window.function.is_distinct.hash(hasher);
275            window.function.arguments.len().hash(hasher);
276            for arg in &window.function.arguments {
277                hash_expression(arg, hasher);
278            }
279            window.partition_by.len().hash(hasher);
280            for e in &window.partition_by {
281                hash_expression(e, hasher);
282            }
283            window.order_by.len().hash(hasher);
284            for order in &window.order_by {
285                hash_expression(&order.expression, hasher);
286                order.ascending.hash(hasher);
287                order.nulls_first.hash(hasher);
288            }
289        }
290        Expression::TableSource(ts) => {
291            ts.name.value_lower.hash(hasher);
292            if let Some(ref alias) = ts.alias {
293                true.hash(hasher);
294                alias.value_lower.hash(hasher);
295            } else {
296                false.hash(hasher);
297            }
298        }
299        Expression::JoinSource(js) => {
300            // Use pointer identity for hashing - avoids expensive Debug format allocation
301            (js.as_ref() as *const _ as usize).hash(hasher);
302        }
303        Expression::SubquerySource(sq) => {
304            if let Some(ref alias) = sq.alias {
305                true.hash(hasher);
306                alias.value_lower.hash(hasher);
307            } else {
308                false.hash(hasher);
309            }
310            // Use pointer identity for hashing - avoids expensive Debug format allocation
311            (sq.subquery.as_ref() as *const _ as usize).hash(hasher);
312        }
313        Expression::ValuesSource(vs) => {
314            if let Some(ref alias) = vs.alias {
315                true.hash(hasher);
316                alias.value_lower.hash(hasher);
317            } else {
318                false.hash(hasher);
319            }
320            vs.rows.len().hash(hasher);
321        }
322        Expression::CteReference(cte) => {
323            cte.name.value_lower.hash(hasher);
324        }
325        Expression::FunctionTableSource(fts) => {
326            fts.function.value_lower.hash(hasher);
327            for arg in &fts.arguments {
328                hash_expression(arg, hasher);
329            }
330        }
331        Expression::Star(_) => {
332            // Just discriminant
333        }
334        Expression::QualifiedStar(qs) => {
335            qs.qualifier.hash(hasher);
336        }
337        Expression::Default(_) => {
338            // Just discriminant
339        }
340    }
341}
342
343/// Try to get a cached program, or compile and cache it.
344/// Uses O(1) LRU cache with parking_lot::Mutex for efficient concurrent access.
345fn compile_expression_cached(expr: &Expression, columns: &[String]) -> Result<SharedProgram> {
346    let registry = global_registry();
347    let registry_generation = registry.generation();
348    let cache_key = compute_cache_key(expr, columns, registry_generation);
349
350    // Try cache first (O(1) lookup and LRU update)
351    {
352        let mut guard = PROGRAM_CACHE.lock();
353        let cache = guard.get_or_insert_with(|| {
354            // SAFETY: PROGRAM_CACHE_SIZE is always > 0
355            LruCache::new(NonZeroUsize::new(PROGRAM_CACHE_SIZE).unwrap())
356        });
357        if let Some(entry) = cache.get(&cache_key) {
358            if entry.registry_generation == registry_generation
359                && entry.expression == *expr
360                && entry.columns == columns
361            {
362                return Ok(entry.program.clone());
363            }
364        }
365    }
366
367    // Cache miss - compile the expression (outside lock to avoid blocking)
368    let ctx = CompileContext::new(columns, registry);
369    let compiler = ExprCompiler::new(&ctx);
370    let program: SharedProgram = compiler
371        .compile(expr)
372        .map(CompactArc::new)
373        .map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
374
375    // Store in cache (O(1) insertion with automatic LRU eviction)
376    {
377        let mut guard = PROGRAM_CACHE.lock();
378        let cache = guard
379            .get_or_insert_with(|| LruCache::new(NonZeroUsize::new(PROGRAM_CACHE_SIZE).unwrap()));
380        cache.put(
381            cache_key,
382            ProgramCacheEntry {
383                expression: expr.clone(),
384                columns: columns.to_vec(),
385                registry_generation,
386                program: program.clone(),
387            },
388        );
389    }
390
391    Ok(program)
392}
393
394// ============================================================================
395// STANDALONE COMPILATION FUNCTIONS
396// ============================================================================
397
398/// Compile an expression to a program for a given column schema.
399///
400/// This is the recommended way to compile expressions for use in closures
401/// or parallel execution. The returned `CompactArc<Program>` is `Send + Sync` and
402/// can be shared across threads efficiently.
403///
404/// **Note:** Results are cached globally for performance. Repeated calls
405/// with the same expression and columns will return the cached program.
406///
407/// # Arguments
408/// * `expr` - The expression to compile
409/// * `columns` - Column names for the schema
410///
411/// # Returns
412/// * `CompactArc<Program>` that can be executed with `RowFilter` or `ExprVM`
413pub fn compile_expression(expr: &Expression, columns: &[String]) -> Result<SharedProgram> {
414    compile_expression_cached(expr, columns)
415}
416
417/// Evaluate a column-free AST expression to a concrete Value at query time.
418///
419/// Returns `Some(value)` if the expression is entirely self-contained (no column
420/// references) and can be evaluated. Returns `None` if the expression references
421/// columns, contains context-dependent functions, or evaluation fails.
422///
423/// This is used by pushdown rules to resolve compound constant expressions like
424/// `NOW() - INTERVAL '24 hours'` into concrete Values for index/storage filtering.
425///
426/// Non-deterministic functions like NOW() and RANDOM() ARE allowed here — they
427/// produce valid values with a blank context (they read system clock / RNG).
428/// Only context-dependent functions (CURRENT_TRANSACTION_ID) are rejected because
429/// they require ExecuteContext fields that are unavailable here.
430///
431/// Note: this is distinct from compile-time constant folding (which rejects ALL
432/// non-deterministic functions to avoid caching stale values in the program LRU).
433pub fn try_eval_constant_expr(expr: &Expression) -> Option<Value> {
434    use std::cell::RefCell;
435
436    // Reject expressions that require execution context (e.g. transaction_id).
437    // CURRENT_TRANSACTION_ID is the only such function; it emits Op::LoadTransactionId
438    // which returns NULL with a blank context.
439    if contains_context_dependent_function(expr) {
440        return None;
441    }
442
443    thread_local! {
444        static EVAL_VM: RefCell<ExprVM> = RefCell::new(ExprVM::new());
445        static EVAL_ROW: Row = Row::new();
446    }
447
448    let empty_cols: &[String] = &[];
449    let ctx = CompileContext::with_global_registry(empty_cols);
450    let compiler = ExprCompiler::new(&ctx);
451    let program = compiler.compile(expr).ok()?;
452
453    EVAL_ROW.with(|empty_row| {
454        let exec_ctx = ExecuteContext::new(empty_row);
455        EVAL_VM.with(|vm_cell| {
456            let mut vm = vm_cell.borrow_mut();
457            vm.execute(&program, &exec_ctx).ok()
458        })
459    })
460}
461
462/// Check if an expression contains functions that depend on ExecuteContext
463/// (transaction state, session variables, etc.) and cannot be evaluated
464/// with a blank context. Currently only CURRENT_TRANSACTION_ID.
465fn contains_context_dependent_function(expr: &Expression) -> bool {
466    match expr {
467        Expression::FunctionCall(func) => {
468            func.function.eq_ignore_ascii_case("CURRENT_TRANSACTION_ID")
469                || func
470                    .arguments
471                    .iter()
472                    .any(contains_context_dependent_function)
473        }
474        Expression::Infix(infix) => {
475            contains_context_dependent_function(&infix.left)
476                || contains_context_dependent_function(&infix.right)
477        }
478        Expression::Prefix(prefix) => contains_context_dependent_function(&prefix.right),
479        Expression::Cast(cast) => contains_context_dependent_function(&cast.expr),
480        Expression::Case(case) => {
481            case.value
482                .as_ref()
483                .is_some_and(|v| contains_context_dependent_function(v))
484                || case.when_clauses.iter().any(|w| {
485                    contains_context_dependent_function(&w.condition)
486                        || contains_context_dependent_function(&w.then_result)
487                })
488                || case
489                    .else_value
490                    .as_ref()
491                    .is_some_and(|v| contains_context_dependent_function(v))
492        }
493        Expression::Between(between) => {
494            contains_context_dependent_function(&between.expr)
495                || contains_context_dependent_function(&between.lower)
496                || contains_context_dependent_function(&between.upper)
497        }
498        Expression::In(in_expr) => {
499            contains_context_dependent_function(&in_expr.left)
500                || contains_context_dependent_function(&in_expr.right)
501        }
502        Expression::Like(like) => {
503            contains_context_dependent_function(&like.left)
504                || contains_context_dependent_function(&like.pattern)
505                || like
506                    .escape
507                    .as_ref()
508                    .is_some_and(|e| contains_context_dependent_function(e))
509        }
510        Expression::List(list) => list
511            .elements
512            .iter()
513            .any(contains_context_dependent_function),
514        Expression::ExpressionList(list) => list
515            .expressions
516            .iter()
517            .any(contains_context_dependent_function),
518        Expression::Aliased(aliased) => contains_context_dependent_function(&aliased.expression),
519        Expression::Distinct(distinct) => contains_context_dependent_function(&distinct.expr),
520        Expression::AllAny(all_any) => contains_context_dependent_function(&all_any.left),
521        Expression::InHashSet(in_hash) => contains_context_dependent_function(&in_hash.column),
522        _ => false,
523    }
524}
525
526/// Compile an expression with full context (parameters, outer columns, etc.)
527///
528/// Use this when you need parameters or correlated subquery support.
529pub fn compile_expression_with_context(
530    expr: &Expression,
531    columns: &[String],
532    outer_columns: Option<&[String]>,
533    function_registry: &FunctionRegistry,
534) -> Result<SharedProgram> {
535    let mut ctx = CompileContext::new(columns, function_registry);
536    if let Some(outer_cols) = outer_columns {
537        ctx = ctx.with_outer_columns(outer_cols);
538    }
539    let compiler = ExprCompiler::new(&ctx);
540    compiler
541        .compile(expr)
542        .map(CompactArc::new)
543        .map_err(|e| Error::internal(format!("Compile error: {}", e)))
544}
545
546// ============================================================================
547// ROW FILTER - Lightweight, Send+Sync filter for closures
548// ============================================================================
549
550/// A lightweight, thread-safe row filter for closure-based filtering.
551///
552/// `RowFilter` pre-compiles the expression once and can be cloned cheaply
553/// (it uses `CompactArc<Program>` internally). Each thread should create its own
554/// `ExprVM` for execution.
555///
556/// # Example
557/// ```ignore
558/// // Create filter once
559/// let filter = RowFilter::new(&where_expr, &columns)?;
560///
561/// // Use in closure (filter is cloned into closure)
562/// let predicate = move |row: &Row| filter.matches(row);
563///
564/// // Or use with parallel iteration
565/// rows.par_iter().filter(|row| filter.matches(row)).collect()
566/// ```
567#[derive(Clone)]
568pub struct RowFilter {
569    /// Pre-compiled program (shared across clones)
570    program: SharedProgram,
571    /// Query parameters (shared) - uses `CompactArc<Vec<Value>>` to match `ExecutionContext`
572    params: CompactArc<ParamVec>,
573    /// Named parameters (shared)
574    named_params: Arc<FxHashMap<String, Value>>,
575    /// Transaction ID for CURRENT_TRANSACTION_ID()
576    transaction_id: Option<u64>,
577    stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
578    /// Bound outer row used by correlated predicates.
579    outer_row: Option<Arc<FxHashMap<CompactArc<str>, Value>>>,
580}
581
582impl RowFilter {
583    /// Create a new row filter by compiling the given expression.
584    ///
585    /// # Arguments
586    /// * `expr` - The boolean expression to use as filter
587    /// * `columns` - Column names matching the row schema
588    pub fn new(expr: &Expression, columns: &[String]) -> Result<Self> {
589        let program = compile_expression(expr, columns)?;
590        Ok(Self {
591            program,
592            params: CompactArc::new(ParamVec::new()),
593            named_params: Arc::new(FxHashMap::default()),
594            transaction_id: None,
595            stored_function_invoker: None,
596            outer_row: None,
597        })
598    }
599
600    /// Create a row filter with expression aliases for HAVING clause evaluation.
601    ///
602    /// Expression aliases map expression strings (like "SUM(amount)") to column
603    /// indices in the result row. This is used for HAVING clauses where aggregate
604    /// expressions need to reference pre-computed aggregate results.
605    ///
606    /// # Arguments
607    /// * `expr` - The boolean expression to use as filter
608    /// * `columns` - Column names matching the row schema
609    /// * `aliases` - Slice of (expression_name, column_index) pairs
610    ///
611    /// # Example
612    /// ```ignore
613    /// // For HAVING SUM(amount) > 100, where SUM(amount) is at column 2
614    /// let aliases = vec![("sum(amount)".to_string(), 2)];
615    /// let filter = RowFilter::with_aliases(&having_expr, &columns, &aliases)?;
616    ///
617    /// // Filter rows
618    /// for row in rows {
619    ///     if filter.matches(&row) {
620    ///         // row passes HAVING clause
621    ///     }
622    /// }
623    /// ```
624    pub fn with_aliases(
625        expr: &Expression,
626        columns: &[String],
627        aliases: &[(String, usize)],
628    ) -> Result<Self> {
629        let alias_map = checked_alias_map(aliases)?;
630
631        let ctx = CompileContext::with_global_registry(columns).with_expression_aliases(alias_map);
632        let compiler = ExprCompiler::new(&ctx);
633        let program = compiler
634            .compile(expr)
635            .map(CompactArc::new)
636            .map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
637
638        Ok(Self {
639            program,
640            params: CompactArc::new(ParamVec::new()),
641            named_params: Arc::new(FxHashMap::default()),
642            transaction_id: None,
643            stored_function_invoker: None,
644            outer_row: None,
645        })
646    }
647
648    /// Compile a HAVING/filter expression with aliases and lexical outer scope
649    /// bound at the same time. Compiling first and attaching only parameters
650    /// later cannot emit `LoadOuterColumn` instructions.
651    pub fn with_aliases_and_context(
652        expr: &Expression,
653        columns: &[String],
654        aliases: &[(String, usize)],
655        execution: &ExecutionContext,
656    ) -> Result<Self> {
657        let alias_map = checked_alias_map(aliases)?;
658        let mut context =
659            CompileContext::with_global_registry(columns).with_expression_aliases(alias_map);
660        if let Some(outer) = execution.outer_row() {
661            let outer_columns: Vec<String> = outer.keys().map(ToString::to_string).collect();
662            context = context.with_outer_columns(&outer_columns);
663        }
664        let compiler = ExprCompiler::new(&context);
665        let program = compiler
666            .compile(expr)
667            .map(CompactArc::new)
668            .map_err(|error| Error::internal(format!("Compile error: {error}")))?;
669
670        Ok(Self {
671            program,
672            params: CompactArc::clone(execution.params_arc()),
673            named_params: Arc::clone(execution.named_params_arc()),
674            transaction_id: execution.transaction_id(),
675            stored_function_invoker: execution.stored_function_invoker().cloned(),
676            outer_row: execution.outer_row().cloned().map(Arc::new),
677        })
678    }
679
680    /// Create a filter with query parameters.
681    pub fn with_params(mut self, params: ParamVec) -> Self {
682        self.params = CompactArc::new(params);
683        self
684    }
685
686    /// Create a filter with named parameters.
687    pub fn with_named_params(mut self, named_params: FxHashMap<String, Value>) -> Self {
688        self.named_params = Arc::new(named_params);
689        self
690    }
691
692    /// Create a filter from execution context.
693    ///
694    /// PERF: Both `params` and `named_params` share the Arc - zero cloning.
695    pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
696        // Share params Arc - no cloning needed (both use CompactArc<Vec<Value>>)
697        self.params = CompactArc::clone(ctx.params_arc());
698        // Share named_params Arc - no cloning needed
699        self.named_params = Arc::clone(ctx.named_params_arc());
700        self.transaction_id = ctx.transaction_id();
701        self.stored_function_invoker = ctx.stored_function_invoker().cloned();
702        self.outer_row = ctx.outer_row().cloned().map(Arc::new);
703        self
704    }
705
706    /// Create a filter from a pre-compiled program.
707    pub fn from_program(program: SharedProgram) -> Self {
708        Self {
709            program,
710            params: CompactArc::new(ParamVec::new()),
711            named_params: Arc::new(FxHashMap::default()),
712            transaction_id: None,
713            stored_function_invoker: None,
714            outer_row: None,
715        }
716    }
717
718    /// Check if a row matches the filter condition.
719    ///
720    /// This method is thread-safe and can be called from multiple threads.
721    /// Each call uses a thread-local VM for execution.
722    #[inline]
723    pub fn matches(&self, row: &Row) -> Result<bool> {
724        // Use thread-local VM for zero allocation in hot path
725        thread_local! {
726            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
727        }
728
729        VM.with(|vm| {
730            let mut ctx = ExecuteContext::new(row);
731
732            if !self.params.is_empty() {
733                ctx = ctx.with_params(&self.params);
734            }
735            if !self.named_params.is_empty() {
736                ctx = ctx.with_named_params(&self.named_params);
737            }
738            if let Some(outer_row) = self.outer_row.as_deref() {
739                ctx = ctx.with_outer_row(outer_row);
740            }
741            ctx = ctx
742                .with_transaction_id(self.transaction_id)
743                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
744
745            // Use try_borrow_mut to avoid panic on recursive calls (e.g., nested subqueries).
746            // If the VM is already borrowed, create a temporary one for this call.
747            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
748                borrowed_vm.execute_bool(&self.program, &ctx)
749            } else {
750                // Fallback: create a fresh VM for recursive calls
751                let mut temp_vm = ExprVM::new();
752                temp_vm.execute_bool(&self.program, &ctx)
753            }
754        })
755    }
756
757    /// Explicit checked alias retained for callers that spell out fallibility.
758    /// `matches()` and this method both propagate runtime and result-type errors.
759    #[inline]
760    pub fn matches_checked(&self, row: &Row) -> Result<bool> {
761        thread_local! {
762            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
763        }
764
765        VM.with(|vm| {
766            let mut ctx = ExecuteContext::new(row);
767
768            if !self.params.is_empty() {
769                ctx = ctx.with_params(&self.params);
770            }
771            if !self.named_params.is_empty() {
772                ctx = ctx.with_named_params(&self.named_params);
773            }
774            if let Some(outer_row) = self.outer_row.as_deref() {
775                ctx = ctx.with_outer_row(outer_row);
776            }
777            ctx = ctx
778                .with_transaction_id(self.transaction_id)
779                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
780
781            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
782                borrowed_vm.execute_bool_checked(&self.program, &ctx)
783            } else {
784                let mut temp_vm = ExprVM::new();
785                temp_vm.execute_bool_checked(&self.program, &ctx)
786            }
787        })
788    }
789
790    /// Evaluate a predicate over a compact JOIN row without materializing it.
791    #[inline]
792    pub fn matches_deferred_checked(&self, row: &radixdb_storage::DeferredRow) -> Result<bool> {
793        thread_local! {
794            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
795        }
796
797        VM.with(|vm| {
798            let mut ctx = ExecuteContext::for_deferred(row);
799
800            if !self.params.is_empty() {
801                ctx = ctx.with_params(&self.params);
802            }
803            if !self.named_params.is_empty() {
804                ctx = ctx.with_named_params(&self.named_params);
805            }
806            if let Some(outer_row) = self.outer_row.as_deref() {
807                ctx = ctx.with_outer_row(outer_row);
808            }
809            ctx = ctx
810                .with_transaction_id(self.transaction_id)
811                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
812
813            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
814                borrowed_vm.execute_bool_checked(&self.program, &ctx)
815            } else {
816                let mut temp_vm = ExprVM::new();
817                temp_vm.execute_bool_checked(&self.program, &ctx)
818            }
819        })
820    }
821
822    /// Evaluate a predicate over a virtual executor row without materializing it.
823    #[inline]
824    pub fn matches_row_ref_checked(&self, row: &crate::operator::RowRef) -> Result<bool> {
825        thread_local! {
826            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
827        }
828
829        VM.with(|vm| {
830            let mut ctx = ExecuteContext::for_row_ref(row);
831
832            if !self.params.is_empty() {
833                ctx = ctx.with_params(&self.params);
834            }
835            if !self.named_params.is_empty() {
836                ctx = ctx.with_named_params(&self.named_params);
837            }
838            if let Some(outer_row) = self.outer_row.as_deref() {
839                ctx = ctx.with_outer_row(outer_row);
840            }
841            ctx = ctx
842                .with_transaction_id(self.transaction_id)
843                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
844
845            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
846                borrowed_vm.execute_bool_checked(&self.program, &ctx)
847            } else {
848                ExprVM::new().execute_bool_checked(&self.program, &ctx)
849            }
850        })
851    }
852
853    /// Filter a RowVec in-place, removing rows that don't match.
854    /// Returns Err if the filter expression produces a runtime error
855    /// (e.g. invalid REGEXP pattern supplied via a parameter).
856    pub fn retain_checked(&self, rows: &mut radixdb_core::RowVec) -> Result<()> {
857        let mut error: Option<radixdb_core::Error> = None;
858        rows.retain(|(_, row)| {
859            if error.is_some() {
860                return false;
861            }
862            match self.matches_checked(row) {
863                Ok(b) => b,
864                Err(e) => {
865                    error = Some(e);
866                    false
867                }
868            }
869        });
870        match error {
871            Some(e) => Err(e),
872            None => Ok(()),
873        }
874    }
875
876    /// Evaluate the filter expression and return the value.
877    #[inline]
878    pub fn evaluate(&self, row: &Row) -> Result<Value> {
879        thread_local! {
880            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
881        }
882
883        VM.with(|vm| {
884            let mut ctx = ExecuteContext::new(row);
885
886            if !self.params.is_empty() {
887                ctx = ctx.with_params(&self.params);
888            }
889            if !self.named_params.is_empty() {
890                ctx = ctx.with_named_params(&self.named_params);
891            }
892            ctx = ctx
893                .with_transaction_id(self.transaction_id)
894                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
895
896            // Use try_borrow_mut to avoid panic on recursive calls (e.g., nested subqueries).
897            // If the VM is already borrowed, create a temporary one for this call.
898            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
899                borrowed_vm.execute_cow(&self.program, &ctx)
900            } else {
901                // Fallback: create a fresh VM for recursive calls
902                let mut temp_vm = ExprVM::new();
903                temp_vm.execute_cow(&self.program, &ctx)
904            }
905        })
906    }
907
908    /// Get the underlying program (for advanced use cases).
909    pub fn program(&self) -> &SharedProgram {
910        &self.program
911    }
912}
913
914// Static assertions to verify RowFilter implements Send + Sync.
915// This is safer than unsafe impl because it will fail at compile time
916// if any field doesn't implement Send/Sync, rather than causing UB at runtime.
917// All fields are Send + Sync:
918// - CompactArc<Program> is Send + Sync (Program is immutable)
919// - CompactArc<Value> is Send + Sync
920// - Arc<FxHashMap<String, Value>> is Send + Sync
921const _: () = {
922    const fn assert_send_sync<T: Send + Sync>() {}
923    let _ = assert_send_sync::<RowFilter>;
924};
925
926// ============================================================================
927// JOIN FILTER - For join condition evaluation
928// ============================================================================
929
930/// A filter for join condition evaluation between two rows.
931#[derive(Clone)]
932pub struct JoinFilter {
933    /// Pre-compiled program
934    program: SharedProgram,
935    /// Query parameters (shared Arc to avoid cloning)
936    params: CompactArc<ParamVec>,
937    /// Named parameters (shared Arc to avoid cloning)
938    named_params: Arc<FxHashMap<String, Value>>,
939    /// Transaction ID for context-dependent functions in residual predicates.
940    transaction_id: Option<u64>,
941    stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
942}
943
944impl JoinFilter {
945    /// Create a join filter by compiling the condition.
946    ///
947    /// # Arguments
948    /// * `expr` - The join condition expression
949    /// * `left_columns` - Column names for the left table
950    /// * `right_columns` - Column names for the right table
951    pub fn new(
952        expr: &Expression,
953        left_columns: &[String],
954        right_columns: &[String],
955        function_registry: &FunctionRegistry,
956    ) -> Result<Self> {
957        let ctx =
958            CompileContext::new(left_columns, function_registry).with_second_row(right_columns);
959        let compiler = ExprCompiler::new(&ctx);
960        let program = compiler
961            .compile(expr)
962            .map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
963        Ok(Self {
964            program: CompactArc::new(program),
965            params: CompactArc::new(ParamVec::new()),
966            named_params: Arc::new(FxHashMap::default()),
967            transaction_id: None,
968            stored_function_invoker: None,
969        })
970    }
971
972    /// Set parameters from execution context.
973    /// This is required when the join condition contains parameter placeholders ($1, $2, etc.).
974    #[inline]
975    pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
976        self.params = CompactArc::clone(ctx.params_arc());
977        self.named_params = Arc::clone(ctx.named_params_arc());
978        self.transaction_id = ctx.transaction_id();
979        self.stored_function_invoker = ctx.stored_function_invoker().cloned();
980        self
981    }
982
983    /// Check if a pair of rows satisfies the join condition.
984    #[inline]
985    pub fn matches(&self, left_row: &Row, right_row: &Row) -> Result<bool> {
986        thread_local! {
987            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
988        }
989
990        VM.with(|vm| {
991            let mut ctx = ExecuteContext::for_join(left_row, right_row)
992                .with_transaction_id(self.transaction_id)
993                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
994
995            // Apply params if present (required for parameter placeholders like $1, $2)
996            if !self.params.is_empty() {
997                ctx = ctx.with_params(&self.params);
998            }
999            if !self.named_params.is_empty() {
1000                ctx = ctx.with_named_params(&self.named_params);
1001            }
1002
1003            // Use try_borrow_mut to avoid panic on recursive calls (e.g., nested subqueries).
1004            // If the VM is already borrowed, create a temporary one for this call.
1005            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
1006                borrowed_vm.execute_bool(&self.program, &ctx)
1007            } else {
1008                // Fallback: create a fresh VM for recursive calls
1009                let mut temp_vm = ExprVM::new();
1010                temp_vm.execute_bool(&self.program, &ctx)
1011            }
1012        })
1013    }
1014
1015    /// Checked join predicate evaluation. Runtime expression errors must be
1016    /// propagated by physical operators rather than converted into non-matches.
1017    #[inline]
1018    pub fn matches_checked(&self, left_row: &Row, right_row: &Row) -> Result<bool> {
1019        thread_local! {
1020            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
1021        }
1022
1023        VM.with(|vm| {
1024            let mut ctx = ExecuteContext::for_join(left_row, right_row)
1025                .with_transaction_id(self.transaction_id)
1026                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1027            if !self.params.is_empty() {
1028                ctx = ctx.with_params(&self.params);
1029            }
1030            if !self.named_params.is_empty() {
1031                ctx = ctx.with_named_params(&self.named_params);
1032            }
1033
1034            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
1035                borrowed_vm.execute_bool_checked(&self.program, &ctx)
1036            } else {
1037                ExprVM::new().execute_bool_checked(&self.program, &ctx)
1038            }
1039        })
1040    }
1041
1042    /// Checked predicate evaluation over a deferred JOIN outer row.
1043    ///
1044    /// This preserves the same compiled program and SQL semantics as
1045    /// `matches_checked`, but lets a following JOIN edge read projected slots
1046    /// without forcing the preceding edge to materialize an owned `Row`.
1047    #[inline]
1048    pub fn matches_row_ref_checked(
1049        &self,
1050        left_row: &crate::operator::RowRef,
1051        right_row: &Row,
1052    ) -> Result<bool> {
1053        thread_local! {
1054            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
1055        }
1056
1057        VM.with(|vm| {
1058            let mut ctx = ExecuteContext::for_join_ref(left_row, right_row)
1059                .with_transaction_id(self.transaction_id)
1060                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1061            if !self.params.is_empty() {
1062                ctx = ctx.with_params(&self.params);
1063            }
1064            if !self.named_params.is_empty() {
1065                ctx = ctx.with_named_params(&self.named_params);
1066            }
1067
1068            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
1069                borrowed_vm.execute_bool_checked(&self.program, &ctx)
1070            } else {
1071                ExprVM::new().execute_bool_checked(&self.program, &ctx)
1072            }
1073        })
1074    }
1075
1076    /// Checked predicate evaluation over two virtual JOIN inputs.
1077    ///
1078    /// This is the hash-join residual path: equality candidates remain row
1079    /// references until the complete `ON` predicate accepts the pair.
1080    #[inline]
1081    pub fn matches_row_refs_checked(
1082        &self,
1083        left_row: &crate::operator::RowRef,
1084        right_row: &crate::operator::RowRef,
1085    ) -> Result<bool> {
1086        thread_local! {
1087            static VM: std::cell::RefCell<ExprVM> = std::cell::RefCell::new(ExprVM::new());
1088        }
1089
1090        VM.with(|vm| {
1091            let mut ctx = ExecuteContext::for_join_refs(left_row, right_row)
1092                .with_transaction_id(self.transaction_id)
1093                .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1094            if !self.params.is_empty() {
1095                ctx = ctx.with_params(&self.params);
1096            }
1097            if !self.named_params.is_empty() {
1098                ctx = ctx.with_named_params(&self.named_params);
1099            }
1100
1101            if let Ok(mut borrowed_vm) = vm.try_borrow_mut() {
1102                borrowed_vm.execute_bool_checked(&self.program, &ctx)
1103            } else {
1104                ExprVM::new().execute_bool_checked(&self.program, &ctx)
1105            }
1106        })
1107    }
1108
1109    /// Get the underlying program.
1110    pub fn program(&self) -> &SharedProgram {
1111        &self.program
1112    }
1113}
1114
1115// Static assertions to verify JoinFilter implements Send + Sync.
1116// This is safer than unsafe impl because it will fail at compile time
1117// if any field doesn't implement Send/Sync, rather than causing UB at runtime.
1118const _: () = {
1119    const fn assert_send_sync<T: Send + Sync>() {}
1120    let _ = assert_send_sync::<JoinFilter>;
1121};
1122
1123// ============================================================================
1124// EXPRESSION EVAL - Direct VM usage for maximum performance
1125// ============================================================================
1126
1127/// Lightweight expression evaluator using direct VM execution.
1128///
1129/// `ExpressionEval` provides the simplest possible API for expression evaluation:
1130/// 1. Compile the expression once with `new()`
1131/// 2. Evaluate rows with `eval()` or `eval_bool()`
1132///
1133/// This is the recommended replacement for `CompiledEvaluator` when you have
1134/// a single expression to evaluate repeatedly.
1135///
1136/// # Example
1137/// ```ignore
1138/// // Compile once
1139/// let eval = ExpressionEval::compile(&expr, &columns)?;
1140///
1141/// // Evaluate many rows
1142/// for row in rows {
1143///     let value = eval.eval(&row)?;
1144///     // or for boolean: let matches = eval.eval_bool(&row);
1145/// }
1146/// ```
1147pub struct ExpressionEval {
1148    /// Pre-compiled program
1149    program: SharedProgram,
1150    /// VM instance (reusable, maintains stack)
1151    vm: ExprVM,
1152    /// Query parameters (shared) - uses `CompactArc<Vec<Value>>` to match `ExecutionContext`
1153    params: CompactArc<ParamVec>,
1154    /// Named parameters (shared) - uses Arc to match ExecutionContext
1155    named_params: Arc<FxHashMap<String, Value>>,
1156    /// Outer row context for correlated subqueries
1157    outer_row: Option<FxHashMap<CompactArc<str>, Value>>,
1158    /// Transaction ID
1159    transaction_id: Option<u64>,
1160    stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
1161}
1162
1163impl ExpressionEval {
1164    /// Compile an expression for evaluation.
1165    pub fn compile(expr: &Expression, columns: &[String]) -> Result<Self> {
1166        let program = compile_expression(expr, columns)?;
1167        Ok(Self {
1168            program,
1169            vm: ExprVM::new(),
1170            params: CompactArc::new(ParamVec::new()),
1171            named_params: Arc::new(FxHashMap::default()),
1172            outer_row: None,
1173            transaction_id: None,
1174            stored_function_invoker: None,
1175        })
1176    }
1177
1178    /// Compile with expression aliases for HAVING clause evaluation.
1179    ///
1180    /// Expression aliases map expression strings (like "SUM(amount)") to column
1181    /// indices in the result row. This is used for HAVING clauses where aggregate
1182    /// expressions need to reference pre-computed aggregate results.
1183    ///
1184    /// # Arguments
1185    /// * `expr` - The expression to compile
1186    /// * `columns` - Column names for the result row
1187    /// * `aliases` - Slice of (expression_name, column_index) pairs
1188    ///
1189    /// # Example
1190    /// ```ignore
1191    /// // For HAVING SUM(amount) > 100, where SUM(amount) is at column 2
1192    /// let aliases = vec![("sum(amount)".to_string(), 2)];
1193    /// let eval = ExpressionEval::compile_with_aliases(&having_expr, &columns, &aliases)?;
1194    /// ```
1195    pub fn compile_with_aliases(
1196        expr: &Expression,
1197        columns: &[String],
1198        aliases: &[(String, usize)],
1199    ) -> Result<Self> {
1200        let alias_map = checked_alias_map(aliases)?;
1201
1202        Self::compile_with_options(
1203            expr,
1204            columns,
1205            None,
1206            None,
1207            Some(alias_map),
1208            global_registry(),
1209        )
1210    }
1211
1212    /// Compile with full context options.
1213    pub fn compile_with_options(
1214        expr: &Expression,
1215        columns: &[String],
1216        columns2: Option<&[String]>,
1217        outer_columns: Option<&[String]>,
1218        expression_aliases: Option<StringMap<u16>>,
1219        function_registry: &FunctionRegistry,
1220    ) -> Result<Self> {
1221        let mut ctx = CompileContext::new(columns, function_registry);
1222        if let Some(cols2) = columns2 {
1223            ctx = ctx.with_second_row(cols2);
1224        }
1225        if let Some(outer) = outer_columns {
1226            ctx = ctx.with_outer_columns(outer);
1227        }
1228        if let Some(aliases) = expression_aliases {
1229            ctx = ctx.with_expression_aliases(aliases);
1230        }
1231        let compiler = ExprCompiler::new(&ctx);
1232        let program = compiler
1233            .compile(expr)
1234            .map(CompactArc::new)
1235            .map_err(|e| Error::internal(format!("Compile error: {}", e)))?;
1236        Ok(Self {
1237            program,
1238            vm: ExprVM::new(),
1239            params: CompactArc::new(ParamVec::new()),
1240            named_params: Arc::new(FxHashMap::default()),
1241            outer_row: None,
1242            transaction_id: None,
1243            stored_function_invoker: None,
1244        })
1245    }
1246
1247    /// Create from a pre-compiled program.
1248    pub fn from_program(program: SharedProgram) -> Self {
1249        Self {
1250            program,
1251            vm: ExprVM::new(),
1252            params: CompactArc::new(ParamVec::new()),
1253            named_params: Arc::new(FxHashMap::default()),
1254            outer_row: None,
1255            transaction_id: None,
1256            stored_function_invoker: None,
1257        }
1258    }
1259
1260    /// Set query parameters.
1261    pub fn with_params(mut self, params: ParamVec) -> Self {
1262        self.params = CompactArc::new(params);
1263        self
1264    }
1265
1266    /// Set named parameters.
1267    pub fn with_named_params(mut self, named_params: FxHashMap<String, Value>) -> Self {
1268        self.named_params = Arc::new(named_params);
1269        self
1270    }
1271
1272    /// Set context from ExecutionContext.
1273    ///
1274    /// PERF: Both `params` and `named_params` share the Arc - zero cloning.
1275    pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
1276        // Share params Arc - no cloning needed
1277        self.params = CompactArc::clone(ctx.params_arc());
1278        // Share named_params Arc - no cloning needed
1279        self.named_params = Arc::clone(ctx.named_params_arc());
1280        if let Some(outer) = ctx.outer_row() {
1281            // Clone the map directly (CompactArc<str> clones are cheap)
1282            let arc_map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1283            self.outer_row = Some(arc_map);
1284        }
1285        self.transaction_id = ctx.transaction_id();
1286        self.stored_function_invoker = ctx.stored_function_invoker().cloned();
1287        self
1288    }
1289
1290    /// Set transaction ID.
1291    pub fn with_transaction_id(mut self, txn_id: Option<u64>) -> Self {
1292        self.transaction_id = txn_id;
1293        self
1294    }
1295
1296    /// Set outer row for correlated subqueries.
1297    /// Accepts `CompactArc<str>` keys directly to avoid conversion overhead.
1298    pub fn set_outer_row(&mut self, outer: &FxHashMap<CompactArc<str>, Value>) {
1299        // Clone the map (CompactArc clones are cheap, Value clones may be expensive but needed)
1300        let map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1301        self.outer_row = Some(map);
1302    }
1303
1304    /// Clear outer row.
1305    pub fn clear_outer_row(&mut self) {
1306        self.outer_row = None;
1307    }
1308
1309    /// Evaluate the expression for a row.
1310    #[inline]
1311    pub fn eval(&mut self, row: &Row) -> Result<Value> {
1312        let mut ctx = ExecuteContext::new(row);
1313
1314        if !self.params.is_empty() {
1315            ctx = ctx.with_params(&self.params);
1316        }
1317        if !self.named_params.is_empty() {
1318            ctx = ctx.with_named_params(&self.named_params);
1319        }
1320        if let Some(ref outer) = self.outer_row {
1321            ctx = ctx.with_outer_row(outer);
1322        }
1323        ctx = ctx
1324            .with_transaction_id(self.transaction_id)
1325            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1326
1327        self.vm.execute_cow(&self.program, &ctx)
1328    }
1329
1330    /// Evaluate as boolean (for WHERE/HAVING).
1331    #[inline]
1332    pub fn eval_bool(&mut self, row: &Row) -> Result<bool> {
1333        let mut ctx = ExecuteContext::new(row);
1334
1335        if !self.params.is_empty() {
1336            ctx = ctx.with_params(&self.params);
1337        }
1338        if !self.named_params.is_empty() {
1339            ctx = ctx.with_named_params(&self.named_params);
1340        }
1341        if let Some(ref outer) = self.outer_row {
1342            ctx = ctx.with_outer_row(outer);
1343        }
1344        ctx = ctx
1345            .with_transaction_id(self.transaction_id)
1346            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1347
1348        self.vm.execute_bool(&self.program, &ctx)
1349    }
1350
1351    /// Like eval_bool but returns errors instead of swallowing them.
1352    #[inline]
1353    pub fn eval_bool_checked(&mut self, row: &Row) -> Result<bool> {
1354        let mut ctx = ExecuteContext::new(row);
1355
1356        if !self.params.is_empty() {
1357            ctx = ctx.with_params(&self.params);
1358        }
1359        if !self.named_params.is_empty() {
1360            ctx = ctx.with_named_params(&self.named_params);
1361        }
1362        if let Some(ref outer) = self.outer_row {
1363            ctx = ctx.with_outer_row(outer);
1364        }
1365        ctx = ctx
1366            .with_transaction_id(self.transaction_id)
1367            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1368
1369        self.vm.execute_bool_checked(&self.program, &ctx)
1370    }
1371
1372    /// Evaluate with two rows (for joins).
1373    #[inline]
1374    pub fn eval_join(&mut self, left: &Row, right: &Row) -> Result<Value> {
1375        let ctx = ExecuteContext::for_join(left, right)
1376            .with_transaction_id(self.transaction_id)
1377            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1378        self.vm.execute_cow(&self.program, &ctx)
1379    }
1380
1381    /// Evaluate join as boolean.
1382    #[inline]
1383    pub fn eval_join_bool(&mut self, left: &Row, right: &Row) -> Result<bool> {
1384        let ctx = ExecuteContext::for_join(left, right)
1385            .with_transaction_id(self.transaction_id)
1386            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1387        self.vm.execute_bool(&self.program, &ctx)
1388    }
1389
1390    /// Evaluate with a row reference.
1391    #[inline]
1392    pub fn eval_slice(&mut self, row: &Row) -> Result<Value> {
1393        let mut ctx = ExecuteContext::new(row);
1394
1395        if !self.params.is_empty() {
1396            ctx = ctx.with_params(&self.params);
1397        }
1398        if !self.named_params.is_empty() {
1399            ctx = ctx.with_named_params(&self.named_params);
1400        }
1401        if let Some(ref outer) = self.outer_row {
1402            ctx = ctx.with_outer_row(outer);
1403        }
1404        ctx = ctx
1405            .with_transaction_id(self.transaction_id)
1406            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1407
1408        self.vm.execute_cow(&self.program, &ctx)
1409    }
1410
1411    /// Evaluate as boolean.
1412    #[inline]
1413    pub fn eval_slice_bool(&mut self, row: &Row) -> Result<bool> {
1414        let mut ctx = ExecuteContext::new(row);
1415
1416        if !self.params.is_empty() {
1417            ctx = ctx.with_params(&self.params);
1418        }
1419        if !self.named_params.is_empty() {
1420            ctx = ctx.with_named_params(&self.named_params);
1421        }
1422        if let Some(ref outer) = self.outer_row {
1423            ctx = ctx.with_outer_row(outer);
1424        }
1425        ctx = ctx
1426            .with_transaction_id(self.transaction_id)
1427            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1428
1429        self.vm.execute_bool(&self.program, &ctx)
1430    }
1431
1432    /// Get the underlying program.
1433    pub fn program(&self) -> &SharedProgram {
1434        &self.program
1435    }
1436}
1437
1438// ============================================================================
1439// MULTI-EXPRESSION EVALUATOR - For SELECT projections
1440// ============================================================================
1441
1442/// Evaluates multiple expressions efficiently (for SELECT projections).
1443///
1444/// Pre-compiles all expressions once, then evaluates them together for each row.
1445pub struct MultiExpressionEval {
1446    /// Pre-compiled programs for each expression
1447    programs: Vec<SharedProgram>,
1448    /// Single VM instance (reused for all expressions)
1449    vm: ExprVM,
1450    /// Query parameters (shared) - uses `CompactArc<Vec<Value>>` to match `ExecutionContext`
1451    params: CompactArc<ParamVec>,
1452    /// Named parameters (shared) - uses Arc to match ExecutionContext
1453    named_params: Arc<FxHashMap<String, Value>>,
1454    /// Transaction ID
1455    transaction_id: Option<u64>,
1456    stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
1457}
1458
1459impl MultiExpressionEval {
1460    /// Compile multiple expressions.
1461    pub fn compile(exprs: &[Expression], columns: &[String]) -> Result<Self> {
1462        let ctx = CompileContext::with_global_registry(columns);
1463        let compiler = ExprCompiler::new(&ctx);
1464
1465        let programs = exprs
1466            .iter()
1467            .map(|expr| {
1468                compiler
1469                    .compile(expr)
1470                    .map(CompactArc::new)
1471                    .map_err(|e| Error::internal(format!("Compile error: {}", e)))
1472            })
1473            .collect::<Result<Vec<_>>>()?;
1474
1475        Ok(Self {
1476            programs,
1477            vm: ExprVM::new(),
1478            params: CompactArc::new(ParamVec::new()),
1479            named_params: Arc::new(FxHashMap::default()),
1480            transaction_id: None,
1481            stored_function_invoker: None,
1482        })
1483    }
1484
1485    /// Compile multiple expressions with expression aliases.
1486    ///
1487    /// Expression aliases map expression strings (like "SUM(amount)") to column
1488    /// indices in the result row. This is used for window function ORDER BY
1489    /// clauses where aggregate expressions need to reference pre-computed results.
1490    ///
1491    /// # Arguments
1492    /// * `exprs` - The expressions to compile
1493    /// * `columns` - Column names for the result row
1494    /// * `aliases` - Slice of (expression_name, column_index) pairs
1495    pub fn compile_with_aliases(
1496        exprs: &[Expression],
1497        columns: &[String],
1498        aliases: &[(String, usize)],
1499    ) -> Result<Self> {
1500        let alias_map = checked_alias_map(aliases)?;
1501
1502        let ctx = CompileContext::with_global_registry(columns).with_expression_aliases(alias_map);
1503        let compiler = ExprCompiler::new(&ctx);
1504
1505        let programs = exprs
1506            .iter()
1507            .map(|expr| {
1508                compiler
1509                    .compile(expr)
1510                    .map(CompactArc::new)
1511                    .map_err(|e| Error::internal(format!("Compile error: {}", e)))
1512            })
1513            .collect::<Result<Vec<_>>>()?;
1514
1515        Ok(Self {
1516            programs,
1517            vm: ExprVM::new(),
1518            params: CompactArc::new(ParamVec::new()),
1519            named_params: Arc::new(FxHashMap::default()),
1520            transaction_id: None,
1521            stored_function_invoker: None,
1522        })
1523    }
1524
1525    /// Set query parameters.
1526    pub fn with_params(mut self, params: ParamVec) -> Self {
1527        self.params = CompactArc::new(params);
1528        self
1529    }
1530
1531    /// Set from execution context.
1532    ///
1533    /// PERF: Both `params` and `named_params` share the Arc - zero cloning.
1534    pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
1535        // Share params Arc - no cloning needed
1536        self.params = CompactArc::clone(ctx.params_arc());
1537        // Share named_params Arc - no cloning needed
1538        self.named_params = Arc::clone(ctx.named_params_arc());
1539        self.transaction_id = ctx.transaction_id();
1540        self.stored_function_invoker = ctx.stored_function_invoker().cloned();
1541        self
1542    }
1543
1544    /// Evaluate all expressions for a row, returning values in order.
1545    #[inline]
1546    pub fn eval_all(&mut self, row: &Row) -> Result<Vec<Value>> {
1547        let mut ctx = ExecuteContext::new(row);
1548
1549        if !self.params.is_empty() {
1550            ctx = ctx.with_params(&self.params);
1551        }
1552        if !self.named_params.is_empty() {
1553            ctx = ctx.with_named_params(&self.named_params);
1554        }
1555        ctx = ctx
1556            .with_transaction_id(self.transaction_id)
1557            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1558
1559        self.programs
1560            .iter()
1561            .map(|prog| self.vm.execute_cow(prog, &ctx))
1562            .collect()
1563    }
1564
1565    /// Evaluate all expressions, writing results into provided buffer.
1566    #[inline]
1567    pub fn eval_into(&mut self, row: &Row, output: &mut Vec<Value>) -> Result<()> {
1568        let mut ctx = ExecuteContext::new(row);
1569
1570        if !self.params.is_empty() {
1571            ctx = ctx.with_params(&self.params);
1572        }
1573        if !self.named_params.is_empty() {
1574            ctx = ctx.with_named_params(&self.named_params);
1575        }
1576        ctx = ctx
1577            .with_transaction_id(self.transaction_id)
1578            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
1579
1580        output.clear();
1581        for prog in &self.programs {
1582            output.push(self.vm.execute_cow(prog, &ctx)?);
1583        }
1584        Ok(())
1585    }
1586
1587    /// Number of expressions.
1588    pub fn len(&self) -> usize {
1589        self.programs.len()
1590    }
1591
1592    /// Check if empty.
1593    pub fn is_empty(&self) -> bool {
1594        self.programs.is_empty()
1595    }
1596}
1597
1598/// Shared program reference for zero-copy caching
1599pub type SharedProgram = CompactArc<Program>;
1600
1601// ============================================================================
1602// COMPILED EVALUATOR - DEPRECATED, use ExpressionEval instead
1603// ============================================================================
1604
1605/// Compiled expression evaluator using the Expression VM.
1606///
1607/// # Deprecated
1608///
1609/// **This type is deprecated.** Use the new, more efficient alternatives:
1610///
1611/// - [`ExpressionEval`] - For single expression evaluation (most common case)
1612/// - [`RowFilter`] - For closure-based filtering (Send+Sync safe)
1613/// - [`JoinFilter`] - For join condition evaluation
1614/// - [`MultiExpressionEval`] - For SELECT projections (multiple expressions)
1615///
1616/// The new APIs pre-compile expressions eagerly rather than lazily, avoiding
1617/// cache invalidation issues and providing better performance.
1618///
1619/// ## Migration Guide
1620///
1621/// **Before (CompiledEvaluator):**
1622/// ```ignore
1623/// let mut eval = CompiledEvaluator::new(&registry);
1624/// eval.init_columns(&columns);
1625/// for row in rows {
1626///     eval.set_row_array(&row);
1627///     let value = eval.evaluate(&expr)?;
1628/// }
1629/// ```
1630///
1631/// **After (ExpressionEval):**
1632/// ```ignore
1633/// let mut eval = ExpressionEval::compile(&expr, &columns)?;
1634/// for row in rows {
1635///     let value = eval.eval(&row)?;
1636/// }
1637/// ```
1638///
1639/// # When to use CompiledEvaluator vs new APIs
1640///
1641/// **Use the new APIs (recommended for most cases):**
1642/// - [`ExpressionEval`] - Single expression with static schema
1643/// - [`RowFilter`] - WHERE clause filtering (thread-safe)
1644/// - [`MultiExpressionEval`] - SELECT projections (multiple expressions)
1645///
1646/// **Use CompiledEvaluator when:**
1647/// - Expressions change per-row (e.g., after `process_correlated_expression`)
1648/// - You need dynamic/lazy expression compilation
1649/// - Complex scenarios with correlated subqueries
1650pub struct CompiledEvaluator<'a> {
1651    /// Function registry for compilation
1652    function_registry: &'a FunctionRegistry,
1653
1654    /// Column names for compilation context (Arc for zero-copy sharing)
1655    columns: CompactArc<Vec<String>>,
1656
1657    /// Second row columns (for joins)
1658    columns2: Option<Vec<String>>,
1659
1660    /// Outer query columns (for correlated subqueries)
1661    outer_columns: Option<Vec<String>>,
1662
1663    /// Query parameters (positional) - uses `CompactArc<Vec<Value>>` to match `ExecutionContext`
1664    params: CompactArc<ParamVec>,
1665
1666    /// Query parameters (named) - uses Arc to match ExecutionContext
1667    named_params: Arc<FxHashMap<String, Value>>,
1668
1669    /// Outer row context for correlated subqueries
1670    outer_row: Option<FxHashMap<CompactArc<str>, Value>>,
1671
1672    /// Current transaction ID
1673    transaction_id: Option<u64>,
1674    stored_function_invoker: Option<Arc<dyn StoredFunctionInvoker>>,
1675
1676    /// Expression aliases for HAVING clause
1677    expression_aliases: StringMap<u16>,
1678
1679    /// Column aliases
1680    column_aliases: StringMap<String>,
1681
1682    /// VM instance (reusable)
1683    vm: ExprVM,
1684
1685    /// Local cache with collision-checked expression identity.
1686    local_cache: FxHashMap<u64, LocalProgramCacheEntry>,
1687
1688    /// Deferred schema/alias validation failures for infallible setters.
1689    context_errors: Vec<String>,
1690
1691    /// Current row values for execution (owned copy for safety)
1692    current_row: Option<Row>,
1693
1694    /// Second row for joins (owned copy for safety)
1695    current_row2: Option<Row>,
1696}
1697
1698// CompiledEvaluator is Send + Sync because all fields are Send + Sync:
1699// - function_registry: &FunctionRegistry is Send + Sync (shared reference to thread-safe registry)
1700// - All other fields are owned types that are Send + Sync
1701
1702impl<'a> CompiledEvaluator<'a> {
1703    /// Create a new compiled evaluator with a function registry reference
1704    pub fn new(function_registry: &'a FunctionRegistry) -> Self {
1705        Self {
1706            function_registry,
1707            columns: CompactArc::new(Vec::new()),
1708            columns2: None,
1709            outer_columns: None,
1710            params: CompactArc::new(ParamVec::new()),
1711            named_params: Arc::new(FxHashMap::default()),
1712            outer_row: None,
1713            transaction_id: None,
1714            stored_function_invoker: None,
1715            expression_aliases: StringMap::new(),
1716            column_aliases: StringMap::new(),
1717            vm: ExprVM::new(),
1718            local_cache: FxHashMap::default(),
1719            context_errors: Vec::new(),
1720            current_row: None,
1721            current_row2: None,
1722        }
1723    }
1724
1725    /// Create an evaluator using the global function registry.
1726    pub fn with_defaults() -> CompiledEvaluator<'static> {
1727        CompiledEvaluator::new(global_registry())
1728    }
1729
1730    fn column_limit_error(label: &str, len: usize) -> Option<String> {
1731        (len > (u16::MAX as usize + 1)).then(|| {
1732            format!(
1733                "{label} has {len} columns; expression bytecode supports at most {}",
1734                u16::MAX as usize + 1
1735            )
1736        })
1737    }
1738
1739    /// Clear all state for reuse.
1740    pub fn clear(&mut self) {
1741        self.columns = CompactArc::new(Vec::new());
1742        self.columns2 = None;
1743        self.outer_columns = None;
1744        self.params = CompactArc::new(ParamVec::new());
1745        self.named_params = Arc::new(FxHashMap::default());
1746        self.outer_row = None;
1747        self.transaction_id = None;
1748        self.stored_function_invoker = None;
1749        self.expression_aliases.clear();
1750        self.column_aliases.clear();
1751        self.local_cache.clear();
1752        self.context_errors.clear();
1753        self.current_row = None;
1754        self.current_row2 = None;
1755    }
1756
1757    fn replace_context_error(&mut self, label: &str, error: Option<String>) {
1758        self.context_errors
1759            .retain(|existing| !existing.starts_with(label));
1760        if let Some(error) = error {
1761            self.context_errors.push(error);
1762        }
1763    }
1764
1765    fn context_error(&self) -> Option<String> {
1766        (!self.context_errors.is_empty()).then(|| self.context_errors.join("; "))
1767    }
1768
1769    /// Set the current transaction ID
1770    pub fn set_transaction_id(&mut self, txn_id: u64) {
1771        self.transaction_id = Some(txn_id);
1772    }
1773
1774    /// Set query parameters (positional) - fluent API
1775    pub fn with_params(mut self, params: ParamVec) -> Self {
1776        self.params = CompactArc::new(params);
1777        self
1778    }
1779
1780    /// Set named query parameters - fluent API
1781    pub fn with_named_params(mut self, named_params: FxHashMap<String, Value>) -> Self {
1782        self.named_params = Arc::new(named_params);
1783        self
1784    }
1785
1786    /// Set parameters from execution context - fluent API
1787    ///
1788    /// PERF: Both `params` and `named_params` share the Arc - zero cloning.
1789    pub fn with_context(mut self, ctx: &ExecutionContext) -> Self {
1790        // Share params Arc - no cloning needed
1791        self.params = CompactArc::clone(ctx.params_arc());
1792        // Share named_params Arc - no cloning needed
1793        self.named_params = Arc::clone(ctx.named_params_arc());
1794
1795        // Set outer row context for correlated subqueries
1796        if let Some(outer) = ctx.outer_row() {
1797            // Clone the map directly (CompactArc<str> clones are cheap)
1798            let arc_map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1799            // Convert CompactArc<str> keys to String for outer_columns (needed for compilation)
1800            let outer_cols: Vec<String> = outer.keys().map(|k| k.to_string()).collect();
1801            self.outer_row = Some(arc_map);
1802            // Also set up outer_columns for compilation
1803            if !outer_cols.is_empty() {
1804                self.outer_columns = Some(outer_cols);
1805                let error = Self::column_limit_error(
1806                    "outer row",
1807                    self.outer_columns.as_ref().map_or(0, Vec::len),
1808                );
1809                self.replace_context_error("outer row", error);
1810                // Invalidate local cache since compilation context changed
1811                self.local_cache.clear();
1812            }
1813        }
1814
1815        self.transaction_id = ctx.transaction_id();
1816        self.stored_function_invoker = ctx.stored_function_invoker().cloned();
1817        self
1818    }
1819
1820    /// Bind an owned row and its column names for fluent evaluation.
1821    pub fn with_row(mut self, row: Row, columns: &[String]) -> Self {
1822        self.init_columns(columns);
1823        self.current_row = Some(row);
1824        self.current_row2 = None;
1825        self
1826    }
1827
1828    /// Initialize the column index mapping (call once before set_row_array)
1829    ///
1830    /// Repeated semantic schemas avoid cloning; pointer identity is never used
1831    /// because allocator reuse is not a schema identity.
1832    pub fn init_columns(&mut self, columns: &[String]) {
1833        if self.columns.as_ref() == columns {
1834            return;
1835        }
1836
1837        self.columns = CompactArc::new(columns.to_vec());
1838        let error = Self::column_limit_error("primary row", columns.len());
1839        self.replace_context_error("primary row", error);
1840        // Clear local cache since compilation context changed
1841        self.local_cache.clear();
1842    }
1843
1844    /// Initialize columns from an Arc (zero-copy when schema already has Arc)
1845    ///
1846    /// This is the preferred method when the caller already has a `CompactArc<Vec<String>>`,
1847    /// such as from `Schema::column_names_arc()`. It avoids all string cloning.
1848    #[inline]
1849    pub fn init_columns_arc(&mut self, columns: CompactArc<Vec<String>>) {
1850        if self.columns.as_ref() == columns.as_ref() {
1851            return;
1852        }
1853
1854        let error = Self::column_limit_error("primary row", columns.len());
1855        self.replace_context_error("primary row", error);
1856        self.columns = columns;
1857        // Clear local cache since compilation context changed
1858        self.local_cache.clear();
1859    }
1860
1861    /// Add aggregate expression aliases for HAVING clause evaluation
1862    pub fn add_aggregate_aliases(&mut self, aliases: &[(String, usize)]) {
1863        for (expr_name, idx) in aliases {
1864            let lower = expr_name.to_lowercase();
1865            match u16::try_from(*idx) {
1866                Ok(index) => {
1867                    self.expression_aliases.insert(lower, index);
1868                }
1869                Err(_) => {
1870                    self.context_errors.push(format!(
1871                        "expression alias '{}' index {} exceeds the u16 bytecode limit",
1872                        expr_name, idx
1873                    ));
1874                }
1875            }
1876        }
1877        // Invalidate local cache since compilation context changed
1878        self.local_cache.clear();
1879    }
1880
1881    /// Add expression aliases for HAVING clause with GROUP BY expressions
1882    pub fn add_expression_aliases(&mut self, aliases: &[(String, usize)]) {
1883        for (expr_str, idx) in aliases {
1884            let lower = expr_str.to_lowercase();
1885            match u16::try_from(*idx) {
1886                Ok(index) => {
1887                    self.expression_aliases.insert(lower, index);
1888                }
1889                Err(_) => {
1890                    self.context_errors.push(format!(
1891                        "expression alias '{}' index {} exceeds the u16 bytecode limit",
1892                        expr_str, idx
1893                    ));
1894                }
1895            }
1896        }
1897        // Invalidate local cache since compilation context changed
1898        self.local_cache.clear();
1899    }
1900
1901    /// Set the row using array-based access (optimized - no map rebuilding)
1902    /// Call init_columns() once before using this method.
1903    #[inline]
1904    pub fn set_row_array(&mut self, row: &Row) {
1905        self.current_row = Some(row.clone());
1906        // Clear join mode
1907        self.current_row2 = None;
1908    }
1909
1910    /// Set the outer row context for correlated subqueries
1911    /// Accepts `CompactArc<str>` keys directly to avoid conversion overhead.
1912    #[inline]
1913    pub fn set_outer_row(&mut self, outer_row: Option<&FxHashMap<CompactArc<str>, Value>>) {
1914        if let Some(outer) = outer_row {
1915            // Clone the map (CompactArc clones are cheap)
1916            let map = outer.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
1917            self.outer_row = Some(map);
1918        } else {
1919            self.outer_row = None;
1920        }
1921    }
1922
1923    /// Set the outer row context by taking ownership
1924    /// Accepts `CompactArc<str>` keys directly to avoid conversion overhead.
1925    #[inline]
1926    pub fn set_outer_row_owned(&mut self, outer_row: FxHashMap<CompactArc<str>, Value>) {
1927        // Collect outer column names for compilation (convert CompactArc<str> to String for outer_columns)
1928        let outer_cols: Vec<String> = outer_row.keys().map(|k| k.to_string()).collect();
1929        self.outer_row = Some(outer_row);
1930        // Also set up outer_columns for compilation so LoadOuterColumn can be emitted
1931        if !outer_cols.is_empty() {
1932            // Sort for deterministic order
1933            let mut sorted_cols = outer_cols;
1934            sorted_cols.sort();
1935            self.outer_columns = Some(sorted_cols);
1936            let error = Self::column_limit_error(
1937                "outer row",
1938                self.outer_columns.as_ref().map_or(0, Vec::len),
1939            );
1940            self.replace_context_error("outer row", error);
1941            // Invalidate local cache since compilation context changed
1942            self.local_cache.clear();
1943        }
1944    }
1945
1946    /// Take ownership of the outer row back (for reuse)
1947    /// Returns `CompactArc<str>` keys directly to avoid conversion overhead.
1948    #[inline]
1949    pub fn take_outer_row(&mut self) -> FxHashMap<CompactArc<str>, Value> {
1950        self.outer_row.take().unwrap_or_default()
1951    }
1952
1953    /// Clear the outer row context
1954    #[inline]
1955    pub fn clear_outer_row(&mut self) {
1956        self.outer_row = None;
1957    }
1958
1959    /// Compute hash of expression content for local cache key.
1960    /// Fast recursive hash that avoids string allocation.
1961    /// Uses FxHasher which is 2-5x faster than SipHash for small keys.
1962    #[inline]
1963    fn expr_hash(&self, expr: &Expression) -> u64 {
1964        let mut hasher = FxHasher::default();
1965        Self::hash_expression(expr, &mut hasher);
1966        hasher.finish()
1967    }
1968
1969    /// Recursively hash an expression without string allocation
1970    fn hash_expression(expr: &Expression, hasher: &mut FxHasher) {
1971        // First hash the discriminant to distinguish variants
1972        std::mem::discriminant(expr).hash(hasher);
1973
1974        match expr {
1975            Expression::Identifier(id) => {
1976                id.value_lower.hash(hasher);
1977            }
1978            Expression::QualifiedIdentifier(qid) => {
1979                qid.qualifier.value_lower.hash(hasher);
1980                qid.name.value_lower.hash(hasher);
1981            }
1982            Expression::IntegerLiteral(lit) => {
1983                lit.value.hash(hasher);
1984            }
1985            Expression::FloatLiteral(lit) => {
1986                lit.value.to_bits().hash(hasher);
1987            }
1988            Expression::StringLiteral(lit) => {
1989                lit.value.hash(hasher);
1990                lit.type_hint.hash(hasher);
1991            }
1992            Expression::BooleanLiteral(lit) => {
1993                lit.value.hash(hasher);
1994            }
1995            Expression::NullLiteral(_) => {
1996                // Just discriminant is enough
1997            }
1998            Expression::BoundValue(value) => {
1999                value.hash(hasher);
2000            }
2001            Expression::IntervalLiteral(lit) => {
2002                lit.value.hash(hasher);
2003                lit.unit.hash(hasher);
2004            }
2005            Expression::Parameter(param) => {
2006                param.index.hash(hasher);
2007                param.name.hash(hasher);
2008            }
2009            Expression::Prefix(prefix) => {
2010                std::mem::discriminant(&prefix.op_type).hash(hasher);
2011                Self::hash_expression(&prefix.right, hasher);
2012            }
2013            Expression::Infix(infix) => {
2014                std::mem::discriminant(&infix.op_type).hash(hasher);
2015                Self::hash_expression(&infix.left, hasher);
2016                Self::hash_expression(&infix.right, hasher);
2017            }
2018            Expression::List(list) => {
2019                list.elements.len().hash(hasher);
2020                for val in &list.elements {
2021                    Self::hash_expression(val, hasher);
2022                }
2023            }
2024            Expression::Distinct(dist) => {
2025                Self::hash_expression(&dist.expr, hasher);
2026            }
2027            Expression::Exists(exists) => {
2028                // Use pointer identity for hashing - avoids expensive Debug format allocation
2029                (exists.subquery.as_ref() as *const _ as usize).hash(hasher);
2030            }
2031            Expression::AllAny(aa) => {
2032                aa.operator.hash(hasher);
2033                std::mem::discriminant(&aa.all_any_type).hash(hasher);
2034                Self::hash_expression(&aa.left, hasher);
2035                // Use pointer identity for hashing - avoids expensive Debug format allocation
2036                (aa.subquery.as_ref() as *const _ as usize).hash(hasher);
2037            }
2038            Expression::In(in_expr) => {
2039                in_expr.not.hash(hasher);
2040                Self::hash_expression(&in_expr.left, hasher);
2041                Self::hash_expression(&in_expr.right, hasher);
2042            }
2043            Expression::InHashSet(in_hash) => {
2044                in_hash.not.hash(hasher);
2045                Self::hash_expression(&in_hash.column, hasher);
2046                let mut values: Vec<&Value> = in_hash.values.iter().collect();
2047                values.sort_unstable();
2048                values.hash(hasher);
2049            }
2050            Expression::Between(between) => {
2051                between.not.hash(hasher);
2052                Self::hash_expression(&between.expr, hasher);
2053                Self::hash_expression(&between.lower, hasher);
2054                Self::hash_expression(&between.upper, hasher);
2055            }
2056            Expression::Like(like) => {
2057                like.operator.hash(hasher);
2058                Self::hash_expression(&like.left, hasher);
2059                Self::hash_expression(&like.pattern, hasher);
2060                if let Some(ref escape) = like.escape {
2061                    true.hash(hasher);
2062                    Self::hash_expression(escape, hasher);
2063                } else {
2064                    false.hash(hasher);
2065                }
2066            }
2067            Expression::ScalarSubquery(sq) => {
2068                // Use pointer identity for hashing - avoids expensive Debug format allocation
2069                (sq.subquery.as_ref() as *const _ as usize).hash(hasher);
2070            }
2071            Expression::ExpressionList(list) => {
2072                list.expressions.len().hash(hasher);
2073                for expr in &list.expressions {
2074                    Self::hash_expression(expr, hasher);
2075                }
2076            }
2077            Expression::Case(case) => {
2078                if let Some(ref val) = case.value {
2079                    true.hash(hasher);
2080                    Self::hash_expression(val, hasher);
2081                } else {
2082                    false.hash(hasher);
2083                }
2084                case.when_clauses.len().hash(hasher);
2085                for when_clause in &case.when_clauses {
2086                    Self::hash_expression(&when_clause.condition, hasher);
2087                    Self::hash_expression(&when_clause.then_result, hasher);
2088                }
2089                if let Some(ref else_val) = case.else_value {
2090                    true.hash(hasher);
2091                    Self::hash_expression(else_val, hasher);
2092                } else {
2093                    false.hash(hasher);
2094                }
2095            }
2096            Expression::Cast(cast) => {
2097                Self::hash_expression(&cast.expr, hasher);
2098                cast.type_name.hash(hasher);
2099            }
2100            Expression::FunctionCall(func) => {
2101                func.function.hash(hasher);
2102                func.is_distinct.hash(hasher);
2103                func.arguments.len().hash(hasher);
2104                for arg in &func.arguments {
2105                    Self::hash_expression(arg, hasher);
2106                }
2107                if let Some(ref filter) = func.filter {
2108                    true.hash(hasher);
2109                    Self::hash_expression(filter, hasher);
2110                } else {
2111                    false.hash(hasher);
2112                }
2113            }
2114            Expression::Aliased(aliased) => {
2115                aliased.alias.value_lower.hash(hasher);
2116                Self::hash_expression(&aliased.expression, hasher);
2117            }
2118            Expression::Window(window) => {
2119                // Hash the FunctionCall directly (not as Expression)
2120                window.function.function.hash(hasher);
2121                window.function.is_distinct.hash(hasher);
2122                window.function.arguments.len().hash(hasher);
2123                for arg in &window.function.arguments {
2124                    Self::hash_expression(arg, hasher);
2125                }
2126                window.partition_by.len().hash(hasher);
2127                for expr in &window.partition_by {
2128                    Self::hash_expression(expr, hasher);
2129                }
2130                window.order_by.len().hash(hasher);
2131                for order in &window.order_by {
2132                    Self::hash_expression(&order.expression, hasher);
2133                    order.ascending.hash(hasher);
2134                    order.nulls_first.hash(hasher);
2135                }
2136            }
2137            Expression::TableSource(ts) => {
2138                ts.name.value_lower.hash(hasher);
2139                if let Some(ref alias) = ts.alias {
2140                    true.hash(hasher);
2141                    alias.value_lower.hash(hasher);
2142                } else {
2143                    false.hash(hasher);
2144                }
2145            }
2146            Expression::JoinSource(js) => {
2147                // Use pointer identity for hashing - avoids expensive Debug format allocation
2148                (js.as_ref() as *const _ as usize).hash(hasher);
2149            }
2150            Expression::SubquerySource(sq) => {
2151                if let Some(ref alias) = sq.alias {
2152                    true.hash(hasher);
2153                    alias.value_lower.hash(hasher);
2154                } else {
2155                    false.hash(hasher);
2156                }
2157                // Use pointer identity for hashing - avoids expensive Debug format allocation
2158                (sq.subquery.as_ref() as *const _ as usize).hash(hasher);
2159            }
2160            Expression::ValuesSource(vs) => {
2161                if let Some(ref alias) = vs.alias {
2162                    true.hash(hasher);
2163                    alias.value_lower.hash(hasher);
2164                } else {
2165                    false.hash(hasher);
2166                }
2167                vs.rows.len().hash(hasher);
2168            }
2169            Expression::CteReference(cte) => {
2170                cte.name.value_lower.hash(hasher);
2171            }
2172            Expression::FunctionTableSource(fts) => {
2173                fts.function.value_lower.hash(hasher);
2174                for arg in &fts.arguments {
2175                    Self::hash_expression(arg, hasher);
2176                }
2177            }
2178            Expression::Star(_) => {
2179                // Just discriminant
2180            }
2181            Expression::QualifiedStar(qs) => {
2182                qs.qualifier.hash(hasher);
2183            }
2184            Expression::Default(_) => {
2185                // Just discriminant
2186            }
2187        }
2188    }
2189
2190    /// Get or compile a program for the expression.
2191    /// Uses local cache for fast lookup within single query evaluation.
2192    fn get_or_compile(&mut self, expr: &Expression) -> Result<SharedProgram> {
2193        if let Some(error) = self.context_error() {
2194            return Err(Error::invalid_argument(error));
2195        }
2196        let registry_generation = self.function_registry.generation();
2197        let mut expr_key = self.expr_hash(expr);
2198        expr_key ^= registry_generation.rotate_left(17);
2199
2200        // Check local cache (fast path, no synchronization)
2201        if let Some(entry) = self.local_cache.get(&expr_key) {
2202            if entry.registry_generation == registry_generation && entry.expression == *expr {
2203                return Ok(CompactArc::clone(&entry.program));
2204            }
2205        }
2206
2207        // Cache miss: compile the expression
2208        let program = CompactArc::new(self.compile_expression(expr)?);
2209        self.local_cache.insert(
2210            expr_key,
2211            LocalProgramCacheEntry {
2212                expression: expr.clone(),
2213                registry_generation,
2214                program: CompactArc::clone(&program),
2215            },
2216        );
2217
2218        Ok(program)
2219    }
2220
2221    /// Compile an expression to a Program
2222    fn compile_expression(&self, expr: &Expression) -> Result<Program> {
2223        if let Some(error) = self.context_error() {
2224            return Err(Error::invalid_argument(error));
2225        }
2226        let mut ctx = CompileContext::new(&self.columns, self.function_registry);
2227
2228        // Add second row columns if available
2229        if let Some(ref cols2) = self.columns2 {
2230            ctx = ctx.with_second_row(cols2);
2231        }
2232
2233        // Add outer columns if available
2234        if let Some(ref outer_cols) = self.outer_columns {
2235            ctx = ctx.with_outer_columns(outer_cols);
2236        }
2237
2238        // Add expression aliases
2239        if !self.expression_aliases.is_empty() {
2240            ctx = ctx.with_expression_aliases(self.expression_aliases.clone());
2241        }
2242
2243        // Add column aliases
2244        if !self.column_aliases.is_empty() {
2245            ctx = ctx.with_column_aliases(self.column_aliases.clone());
2246        }
2247
2248        let compiler = ExprCompiler::new(&ctx);
2249        compiler
2250            .compile(expr)
2251            .map_err(|e| Error::internal(format!("Compile error: {}", e)))
2252    }
2253
2254    /// Evaluate an expression to a Value
2255    pub fn evaluate(&mut self, expr: &Expression) -> Result<Value> {
2256        // Compile the expression first
2257        let program = self.get_or_compile(expr)?;
2258
2259        // Static empty row for fallback
2260        static EMPTY_ROW: std::sync::LazyLock<Row> = std::sync::LazyLock::new(Row::new);
2261
2262        // Get row data from owned copy
2263        let row = self.current_row.as_ref().unwrap_or(&EMPTY_ROW);
2264
2265        // Get second row if in join mode
2266        let row2 = self.current_row2.as_ref();
2267
2268        // Build execution context
2269        let mut ctx = if let Some(r2) = row2 {
2270            ExecuteContext::for_join(row, r2)
2271        } else {
2272            ExecuteContext::new(row)
2273        };
2274
2275        // Add parameters
2276        if !self.params.is_empty() {
2277            ctx = ctx.with_params(&self.params);
2278        }
2279
2280        // Add named parameters
2281        if !self.named_params.is_empty() {
2282            ctx = ctx.with_named_params(&self.named_params);
2283        }
2284
2285        // Add outer row
2286        if let Some(ref outer) = self.outer_row {
2287            ctx = ctx.with_outer_row(outer);
2288        }
2289
2290        // Add transaction ID
2291        ctx = ctx
2292            .with_transaction_id(self.transaction_id)
2293            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
2294
2295        // Execute
2296        self.vm.execute_cow(&program, &ctx)
2297    }
2298
2299    /// Evaluate an expression as a boolean (for WHERE/HAVING clauses)
2300    ///
2301    /// Returns false for NULL results (SQL three-valued logic).
2302    pub fn evaluate_bool(&mut self, expr: &Expression) -> Result<bool> {
2303        // Compile the expression first
2304        let program = self.get_or_compile(expr)?;
2305
2306        // Static empty row for fallback
2307        static EMPTY_ROW: std::sync::LazyLock<Row> = std::sync::LazyLock::new(Row::new);
2308
2309        // Get row data from owned copy
2310        let row = self.current_row.as_ref().unwrap_or(&EMPTY_ROW);
2311
2312        // Get second row if in join mode
2313        let row2 = self.current_row2.as_ref();
2314
2315        // Build execution context
2316        let mut ctx = if let Some(r2) = row2 {
2317            ExecuteContext::for_join(row, r2)
2318        } else {
2319            ExecuteContext::new(row)
2320        };
2321
2322        // Add parameters
2323        if !self.params.is_empty() {
2324            ctx = ctx.with_params(&self.params);
2325        }
2326
2327        // Add named parameters
2328        if !self.named_params.is_empty() {
2329            ctx = ctx.with_named_params(&self.named_params);
2330        }
2331
2332        // Add outer row
2333        if let Some(ref outer) = self.outer_row {
2334            ctx = ctx.with_outer_row(outer);
2335        }
2336
2337        // Add transaction ID
2338        ctx = ctx
2339            .with_transaction_id(self.transaction_id)
2340            .with_stored_function_invoker(self.stored_function_invoker.as_ref());
2341
2342        // Execute and convert to bool. This must use the checked VM path:
2343        // callers rely on `Result<bool>` to distinguish SQL false/NULL from
2344        // runtime expression errors.
2345        self.vm.execute_bool_checked(&program, &ctx)
2346    }
2347}
2348
2349impl Default for CompiledEvaluator<'static> {
2350    fn default() -> Self {
2351        Self::with_defaults()
2352    }
2353}
2354
2355#[cfg(test)]
2356mod tests {
2357    use super::*;
2358    use radixdb_sql::ast::{
2359        Expression, FunctionCall, Identifier, InfixExpression, InfixOperator, IntegerLiteral,
2360    };
2361    use radixdb_sql::token::{Position, Token, TokenType};
2362
2363    #[derive(Default)]
2364    struct MutableScalarOne;
2365
2366    #[derive(Default)]
2367    struct MutableScalarTwo;
2368
2369    macro_rules! mutable_scalar {
2370        ($type:ty, $value:expr) => {
2371            impl radixdb_functions::ScalarFunction for $type {
2372                fn name(&self) -> &str {
2373                    "MUTABLE_TEST"
2374                }
2375
2376                fn info(&self) -> radixdb_functions::FunctionInfo {
2377                    radixdb_functions::FunctionInfo::new(
2378                        "MUTABLE_TEST",
2379                        radixdb_functions::FunctionType::Scalar,
2380                        "cache generation test",
2381                        radixdb_functions::FunctionSignature::new(
2382                            radixdb_functions::FunctionDataType::Integer,
2383                            vec![],
2384                            0,
2385                            0,
2386                        ),
2387                    )
2388                }
2389
2390                fn evaluate(&self, _args: &[Value]) -> Result<Value> {
2391                    Ok(Value::Integer($value))
2392                }
2393            }
2394        };
2395    }
2396
2397    mutable_scalar!(MutableScalarOne, 1);
2398    mutable_scalar!(MutableScalarTwo, 2);
2399
2400    fn dummy_token() -> Token {
2401        Token::new(TokenType::Eof, "", Position::default())
2402    }
2403
2404    fn make_identifier(name: &str) -> Expression {
2405        Expression::Identifier(Identifier {
2406            token: dummy_token(),
2407            value: name.into(),
2408            value_lower: name.to_lowercase().into(),
2409        })
2410    }
2411
2412    fn make_int_literal(value: i64) -> Expression {
2413        Expression::IntegerLiteral(IntegerLiteral {
2414            token: dummy_token(),
2415            value,
2416        })
2417    }
2418
2419    fn make_infix(left: Expression, op: InfixOperator, right: Expression) -> Expression {
2420        let op_str = match op {
2421            InfixOperator::GreaterThan => ">",
2422            InfixOperator::LessThan => "<",
2423            InfixOperator::Equal => "=",
2424            InfixOperator::Add => "+",
2425            InfixOperator::Multiply => "*",
2426            _ => "?",
2427        };
2428        Expression::Infix(InfixExpression {
2429            token: dummy_token(),
2430            left: Box::new(left),
2431            operator: op_str.into(),
2432            op_type: op,
2433            right: Box::new(right),
2434        })
2435    }
2436
2437    fn make_function(name: &str) -> Expression {
2438        Expression::FunctionCall(Box::new(FunctionCall {
2439            token: dummy_token(),
2440            function: name.into(),
2441            arguments: Vec::new(),
2442            is_distinct: false,
2443            order_by: Vec::new(),
2444            filter: None,
2445        }))
2446    }
2447
2448    // =========================================================================
2449    // compute_expression_hash tests
2450    // =========================================================================
2451
2452    #[test]
2453    fn test_compute_expression_hash_same_expr() {
2454        let expr1 = make_int_literal(42);
2455        let expr2 = make_int_literal(42);
2456        assert_eq!(
2457            compute_expression_hash(&expr1),
2458            compute_expression_hash(&expr2)
2459        );
2460    }
2461
2462    #[test]
2463    fn test_compute_expression_hash_different_expr() {
2464        let expr1 = make_int_literal(42);
2465        let expr2 = make_int_literal(43);
2466        assert_ne!(
2467            compute_expression_hash(&expr1),
2468            compute_expression_hash(&expr2)
2469        );
2470    }
2471
2472    #[test]
2473    fn test_compute_expression_hash_complex() {
2474        // col > 5
2475        let expr1 = make_infix(
2476            make_identifier("col"),
2477            InfixOperator::GreaterThan,
2478            make_int_literal(5),
2479        );
2480        // col > 5 (same)
2481        let expr2 = make_infix(
2482            make_identifier("col"),
2483            InfixOperator::GreaterThan,
2484            make_int_literal(5),
2485        );
2486        assert_eq!(
2487            compute_expression_hash(&expr1),
2488            compute_expression_hash(&expr2)
2489        );
2490    }
2491
2492    // =========================================================================
2493    // compile_expression tests
2494    // =========================================================================
2495
2496    #[test]
2497    fn test_compile_expression_basic() {
2498        // col > 5
2499        let expr = make_infix(
2500            make_identifier("col"),
2501            InfixOperator::GreaterThan,
2502            make_int_literal(5),
2503        );
2504        let columns = vec!["col".to_string()];
2505        let program = compile_expression(&expr, &columns);
2506        assert!(program.is_ok());
2507    }
2508
2509    #[test]
2510    fn test_compile_expression_unknown_column() {
2511        // unknown_col > 5
2512        let expr = make_infix(
2513            make_identifier("unknown_col"),
2514            InfixOperator::GreaterThan,
2515            make_int_literal(5),
2516        );
2517        let columns = vec!["col".to_string()];
2518        // Unknown columns cause compilation errors
2519        let program = compile_expression(&expr, &columns);
2520        assert!(program.is_err());
2521    }
2522
2523    // =========================================================================
2524    // RowFilter tests
2525    // =========================================================================
2526
2527    #[test]
2528    fn test_row_filter_new() {
2529        // col > 5
2530        let expr = make_infix(
2531            make_identifier("col"),
2532            InfixOperator::GreaterThan,
2533            make_int_literal(5),
2534        );
2535        let columns = vec!["col".to_string()];
2536        let filter = RowFilter::new(&expr, &columns);
2537        assert!(filter.is_ok());
2538    }
2539
2540    #[test]
2541    fn test_row_filter_matches_true() {
2542        // col > 5
2543        let expr = make_infix(
2544            make_identifier("col"),
2545            InfixOperator::GreaterThan,
2546            make_int_literal(5),
2547        );
2548        let columns = vec!["col".to_string()];
2549        let filter = RowFilter::new(&expr, &columns).unwrap();
2550
2551        // Row with col = 10 (> 5)
2552        let row = Row::from(vec![Value::Integer(10)]);
2553        assert!(filter.matches(&row).unwrap());
2554    }
2555
2556    #[test]
2557    fn test_row_filter_matches_false() {
2558        // col > 5
2559        let expr = make_infix(
2560            make_identifier("col"),
2561            InfixOperator::GreaterThan,
2562            make_int_literal(5),
2563        );
2564        let columns = vec!["col".to_string()];
2565        let filter = RowFilter::new(&expr, &columns).unwrap();
2566
2567        // Row with col = 3 (not > 5)
2568        let row = Row::from(vec![Value::Integer(3)]);
2569        assert!(!filter.matches(&row).unwrap());
2570    }
2571
2572    #[test]
2573    fn test_row_filter_evaluate() {
2574        // col + 10
2575        let expr = make_infix(
2576            make_identifier("col"),
2577            InfixOperator::Add,
2578            make_int_literal(10),
2579        );
2580        let columns = vec!["col".to_string()];
2581        let filter = RowFilter::new(&expr, &columns).unwrap();
2582
2583        let row = Row::from(vec![Value::Integer(5)]);
2584        let result = filter.evaluate(&row).unwrap();
2585        assert_eq!(result, Value::Integer(15));
2586    }
2587
2588    #[test]
2589    fn test_row_filter_clone() {
2590        let expr = make_infix(
2591            make_identifier("col"),
2592            InfixOperator::GreaterThan,
2593            make_int_literal(5),
2594        );
2595        let columns = vec!["col".to_string()];
2596        let filter = RowFilter::new(&expr, &columns).unwrap();
2597        let cloned = filter.clone();
2598
2599        let row = Row::from(vec![Value::Integer(10)]);
2600        assert!(filter.matches(&row).unwrap());
2601        assert!(cloned.matches(&row).unwrap());
2602    }
2603
2604    // =========================================================================
2605    // ExpressionEval tests
2606    // =========================================================================
2607
2608    #[test]
2609    fn test_expression_eval_compile() {
2610        let expr = make_infix(
2611            make_identifier("col"),
2612            InfixOperator::GreaterThan,
2613            make_int_literal(5),
2614        );
2615        let columns = vec!["col".to_string()];
2616        let eval = ExpressionEval::compile(&expr, &columns);
2617        assert!(eval.is_ok());
2618    }
2619
2620    #[test]
2621    fn test_expression_eval_eval() {
2622        // col + 10
2623        let expr = make_infix(
2624            make_identifier("col"),
2625            InfixOperator::Add,
2626            make_int_literal(10),
2627        );
2628        let columns = vec!["col".to_string()];
2629        let mut eval = ExpressionEval::compile(&expr, &columns).unwrap();
2630
2631        let row = Row::from(vec![Value::Integer(5)]);
2632        let result = eval.eval(&row).unwrap();
2633        assert_eq!(result, Value::Integer(15));
2634    }
2635
2636    #[test]
2637    fn test_expression_eval_eval_bool() {
2638        // col > 5
2639        let expr = make_infix(
2640            make_identifier("col"),
2641            InfixOperator::GreaterThan,
2642            make_int_literal(5),
2643        );
2644        let columns = vec!["col".to_string()];
2645        let mut eval = ExpressionEval::compile(&expr, &columns).unwrap();
2646
2647        let row = Row::from(vec![Value::Integer(10)]);
2648        assert!(eval.eval_bool(&row).unwrap());
2649
2650        let row = Row::from(vec![Value::Integer(3)]);
2651        assert!(!eval.eval_bool(&row).unwrap());
2652    }
2653
2654    // =========================================================================
2655    // MultiExpressionEval tests
2656    // =========================================================================
2657
2658    #[test]
2659    fn test_multi_expression_eval_compile() {
2660        let expr1 = make_infix(
2661            make_identifier("col"),
2662            InfixOperator::Add,
2663            make_int_literal(10),
2664        );
2665        let expr2 = make_infix(
2666            make_identifier("col"),
2667            InfixOperator::Multiply,
2668            make_int_literal(2),
2669        );
2670        let columns = vec!["col".to_string()];
2671
2672        let eval = MultiExpressionEval::compile(&[expr1, expr2], &columns);
2673        assert!(eval.is_ok());
2674        assert_eq!(eval.unwrap().len(), 2);
2675    }
2676
2677    #[test]
2678    fn test_multi_expression_eval_all() {
2679        let expr1 = make_infix(
2680            make_identifier("col"),
2681            InfixOperator::Add,
2682            make_int_literal(10),
2683        );
2684        let expr2 = make_infix(
2685            make_identifier("col"),
2686            InfixOperator::Multiply,
2687            make_int_literal(2),
2688        );
2689        let columns = vec!["col".to_string()];
2690        let mut eval = MultiExpressionEval::compile(&[expr1, expr2], &columns).unwrap();
2691
2692        let row = Row::from(vec![Value::Integer(5)]);
2693        let results = eval.eval_all(&row).unwrap();
2694        assert_eq!(results.len(), 2);
2695        assert_eq!(results[0], Value::Integer(15)); // 5 + 10
2696        assert_eq!(results[1], Value::Integer(10)); // 5 * 2
2697    }
2698
2699    // =========================================================================
2700    // CompiledEvaluator tests
2701    // =========================================================================
2702
2703    #[test]
2704    fn test_compiled_evaluator_with_defaults() {
2705        let eval = CompiledEvaluator::with_defaults();
2706        assert!(eval.columns.is_empty());
2707    }
2708
2709    #[test]
2710    fn test_compiled_evaluator_init_columns() {
2711        let mut eval = CompiledEvaluator::with_defaults();
2712        eval.init_columns(&["col1".to_string(), "col2".to_string()]);
2713        assert_eq!(eval.columns.len(), 2);
2714    }
2715
2716    #[test]
2717    fn compiled_evaluator_with_row_binds_owned_row_for_value_and_bool_evaluation() {
2718        let columns = vec!["col".to_string()];
2719        let mut eval = CompiledEvaluator::with_defaults()
2720            .with_row(Row::from(vec![Value::Integer(10)]), &columns);
2721
2722        assert_eq!(
2723            eval.evaluate(&make_identifier("col")).unwrap(),
2724            Value::Integer(10)
2725        );
2726        assert!(eval
2727            .evaluate_bool(&make_infix(
2728                make_identifier("col"),
2729                InfixOperator::GreaterThan,
2730                make_int_literal(5),
2731            ))
2732            .unwrap());
2733    }
2734
2735    #[test]
2736    fn test_compiled_evaluator_evaluate_bool() {
2737        let mut eval = CompiledEvaluator::with_defaults();
2738        eval.init_columns(&["col".to_string()]);
2739        let row = Row::from(vec![Value::Integer(10)]);
2740        eval.set_row_array(&row);
2741
2742        // col > 5
2743        let expr = make_infix(
2744            make_identifier("col"),
2745            InfixOperator::GreaterThan,
2746            make_int_literal(5),
2747        );
2748
2749        let result = eval.evaluate_bool(&expr);
2750        assert!(result.is_ok());
2751        assert!(result.unwrap());
2752    }
2753
2754    #[test]
2755    fn test_compiled_evaluator_evaluate() {
2756        let mut eval = CompiledEvaluator::with_defaults();
2757        eval.init_columns(&["col".to_string()]);
2758        let row = Row::from(vec![Value::Integer(5)]);
2759        eval.set_row_array(&row);
2760
2761        // col + 10
2762        let expr = make_infix(
2763            make_identifier("col"),
2764            InfixOperator::Add,
2765            make_int_literal(10),
2766        );
2767
2768        let result = eval.evaluate(&expr);
2769        assert!(result.is_ok());
2770        assert_eq!(result.unwrap(), Value::Integer(15));
2771    }
2772
2773    #[test]
2774    fn test_compiled_evaluator_default() {
2775        let eval = CompiledEvaluator::default();
2776        assert!(eval.columns.is_empty());
2777    }
2778
2779    #[test]
2780    fn compiled_evaluator_rebinds_semantically_changed_schemas() {
2781        let mut eval = CompiledEvaluator::with_defaults();
2782        let expr = make_identifier("a");
2783
2784        eval.init_columns(&["a".to_string(), "b".to_string()]);
2785        eval.set_row_array(&Row::from(vec![Value::Integer(1), Value::Integer(2)]));
2786        assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(1));
2787
2788        eval.init_columns(&["b".to_string(), "a".to_string()]);
2789        eval.set_row_array(&Row::from(vec![Value::Integer(1), Value::Integer(2)]));
2790        assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(2));
2791    }
2792
2793    #[test]
2794    fn compiled_evaluator_rejects_wide_schema_without_losing_other_errors() {
2795        let mut eval = CompiledEvaluator::with_defaults();
2796        let columns = (0..=u16::MAX as usize + 1)
2797            .map(|index| format!("c{index}"))
2798            .collect::<Vec<_>>();
2799        eval.init_columns(&columns);
2800        assert!(eval.evaluate(&make_int_literal(1)).is_err());
2801
2802        eval.init_columns(&["c".to_string()]);
2803        eval.set_row_array(&Row::from(vec![Value::Integer(1)]));
2804        assert_eq!(
2805            eval.evaluate(&make_int_literal(1)).unwrap(),
2806            Value::Integer(1)
2807        );
2808    }
2809
2810    #[test]
2811    fn join_filter_reads_deferred_projection_without_materializing_it() {
2812        let expression = make_infix(
2813            make_identifier("left_id"),
2814            InfixOperator::Equal,
2815            make_identifier("right_id"),
2816        );
2817        let filter = JoinFilter::new(
2818            &expression,
2819            &["left_id".to_string()],
2820            &["right_id".to_string()],
2821            global_registry(),
2822        )
2823        .unwrap();
2824        let left = crate::operator::RowRef::projected(
2825            crate::operator::RowRef::owned(Row::from_values(vec![
2826                Value::Integer(99),
2827                Value::Integer(7),
2828            ])),
2829            crate::operator::RowRef::owned(Row::new()),
2830            CompactArc::from(vec![crate::operator::ColumnSource::Outer(1)]),
2831        );
2832
2833        assert!(left.is_deferred());
2834        assert!(filter
2835            .matches_row_ref_checked(&left, &Row::from_values(vec![Value::Integer(7)]))
2836            .unwrap());
2837        assert!(left.is_deferred());
2838    }
2839
2840    #[test]
2841    fn row_filter_reads_portable_deferred_projection_without_materializing_it() {
2842        let expression = make_infix(
2843            make_identifier("status"),
2844            InfixOperator::Equal,
2845            make_int_literal(7),
2846        );
2847        let filter = RowFilter::new(&expression, &["status".to_string()]).unwrap();
2848        let row = radixdb_storage::DeferredRow::projected(
2849            radixdb_storage::DeferredRow::owned(Row::from_values(vec![
2850                Value::Integer(99),
2851                Value::Integer(7),
2852            ])),
2853            radixdb_storage::DeferredRow::owned(Row::new()),
2854            CompactArc::from(vec![radixdb_storage::DeferredColumnSource::Left(1)]),
2855        );
2856
2857        assert!(row.is_deferred());
2858        assert!(filter.matches_deferred_checked(&row).unwrap());
2859        assert!(row.is_deferred());
2860    }
2861
2862    #[test]
2863    fn row_filter_reads_executor_projection_without_materializing_it() {
2864        let expression = make_infix(
2865            make_identifier("status"),
2866            InfixOperator::Equal,
2867            make_int_literal(7),
2868        );
2869        let filter = RowFilter::new(&expression, &["status".to_string()]).unwrap();
2870        let row = crate::operator::RowRef::projected(
2871            crate::operator::RowRef::owned(Row::from_values(vec![
2872                Value::Integer(99),
2873                Value::Integer(7),
2874            ])),
2875            crate::operator::RowRef::owned(Row::new()),
2876            CompactArc::from(vec![crate::operator::ColumnSource::Outer(1)]),
2877        );
2878
2879        assert!(row.is_deferred());
2880        assert!(filter.matches_row_ref_checked(&row).unwrap());
2881        assert!(row.is_deferred());
2882    }
2883
2884    #[test]
2885    fn registry_generation_invalidates_embedded_function_programs() {
2886        let registry = FunctionRegistry::new();
2887        registry.register_scalar::<MutableScalarOne>();
2888        let mut eval = CompiledEvaluator::new(&registry);
2889        eval.set_row_array(&Row::new());
2890        let expr = make_function("MUTABLE_TEST");
2891        assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(1));
2892
2893        registry.register_scalar::<MutableScalarTwo>();
2894        assert_eq!(eval.evaluate(&expr).unwrap(), Value::Integer(2));
2895    }
2896}