Skip to main content

radixdb_executor/expression/
ops.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// Compiled Expression Operations
16//
17// These operations form the instruction set for the expression VM.
18// Each operation is designed to be:
19// - Self-contained (no external dependencies during execution)
20// - Fast to dispatch (small enum, good for branch prediction)
21// - Zero allocation (all data pre-computed at compile time)
22
23use std::sync::Arc;
24
25use memchr::memmem;
26use radixdb_core::CompactArc;
27
28use radixdb_core::{DataType, Error, Result, Value, ValueSet};
29use radixdb_functions::{NativeFn1, ScalarFunction};
30
31/// Compiled LIKE/ILIKE pattern for fast matching
32#[derive(Debug, Clone)]
33pub enum CompiledPattern {
34    /// Exact match (no wildcards)
35    Exact(String),
36    /// Prefix match: "abc%"
37    Prefix(String),
38    /// Suffix match: "%abc"
39    Suffix(String),
40    /// Contains match: "%abc%"
41    Contains(String),
42    /// Prefix + Suffix: "abc%xyz"
43    PrefixSuffix(String, String),
44    /// Complex pattern requiring regex
45    Regex(regex::Regex),
46    /// Match all: "%"
47    MatchAll,
48    /// Single char: "_"
49    SingleChar,
50}
51
52impl CompiledPattern {
53    /// Compile a LIKE pattern into optimized form
54    pub fn compile(pattern: &str, case_insensitive: bool) -> Result<Self> {
55        let pat = if case_insensitive {
56            pattern.to_lowercase()
57        } else {
58            pattern.to_string()
59        };
60
61        // Check for simple patterns that don't need regex
62        let has_percent = pat.contains('%');
63        let has_underscore = pat.contains('_');
64        let has_escape = pat.contains('\\');
65
66        if !has_percent && !has_underscore && !has_escape {
67            return Ok(CompiledPattern::Exact(pat));
68        }
69
70        // When backslash escapes are present (e.g. \% for literal %), skip all fast
71        // paths — they don't understand escape sequences. Fall through to regex which
72        // correctly handles them via like_to_regex().
73        if !has_escape {
74            if pat == "%" {
75                return Ok(CompiledPattern::MatchAll);
76            }
77
78            if pat == "_" {
79                return Ok(CompiledPattern::SingleChar);
80            }
81
82            // Check for prefix pattern: "abc%"
83            if pat.ends_with('%') && !pat[..pat.len() - 1].contains('%') && !has_underscore {
84                return Ok(CompiledPattern::Prefix(pat[..pat.len() - 1].to_string()));
85            }
86
87            // Check for suffix pattern: "%abc"
88            if pat.starts_with('%') && !pat[1..].contains('%') && !has_underscore {
89                return Ok(CompiledPattern::Suffix(pat[1..].to_string()));
90            }
91
92            // Check for contains pattern: "%abc%"
93            if pat.starts_with('%') && pat.ends_with('%') && pat.len() > 2 {
94                let middle = &pat[1..pat.len() - 1];
95                if !middle.contains('%') && !middle.contains('_') {
96                    return Ok(CompiledPattern::Contains(middle.to_string()));
97                }
98            }
99
100            // Check for prefix+suffix: "abc%xyz"
101            if has_percent && !has_underscore {
102                let parts: Vec<&str> = pat.split('%').collect();
103                if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
104                    return Ok(CompiledPattern::PrefixSuffix(
105                        parts[0].to_string(),
106                        parts[1].to_string(),
107                    ));
108                }
109            }
110        }
111
112        // Fall back to regex for complex patterns
113        let regex_pattern = Self::like_to_regex(&pat);
114        let regex = if case_insensitive {
115            regex::Regex::new(&format!("(?i)^{}$", regex_pattern))
116        } else {
117            regex::Regex::new(&format!("^{}$", regex_pattern))
118        };
119
120        regex
121            .map(CompiledPattern::Regex)
122            .map_err(|error| Error::invalid_argument(format!("invalid LIKE pattern: {error}")))
123    }
124
125    /// Compile a GLOB pattern into optimized form
126    /// GLOB uses * for any sequence and ? for single character (case-sensitive)
127    pub fn compile_glob(pattern: &str) -> Result<Self> {
128        let pat = pattern.to_string();
129
130        // Check for simple patterns that don't need regex
131        let has_star = pat.contains('*');
132        let has_question = pat.contains('?');
133
134        if !has_star && !has_question {
135            return Ok(CompiledPattern::Exact(pat));
136        }
137
138        if pat == "*" {
139            return Ok(CompiledPattern::MatchAll);
140        }
141
142        if pat == "?" {
143            return Ok(CompiledPattern::SingleChar);
144        }
145
146        // Check for prefix pattern: "abc*"
147        if pat.ends_with('*') && !pat[..pat.len() - 1].contains('*') && !has_question {
148            return Ok(CompiledPattern::Prefix(pat[..pat.len() - 1].to_string()));
149        }
150
151        // Check for suffix pattern: "*abc"
152        if pat.starts_with('*') && !pat[1..].contains('*') && !has_question {
153            return Ok(CompiledPattern::Suffix(pat[1..].to_string()));
154        }
155
156        // Check for contains pattern: "*abc*"
157        if pat.starts_with('*') && pat.ends_with('*') && pat.len() > 2 {
158            let middle = &pat[1..pat.len() - 1];
159            if !middle.contains('*') && !middle.contains('?') {
160                return Ok(CompiledPattern::Contains(middle.to_string()));
161            }
162        }
163
164        // Check for prefix+suffix: "abc*xyz"
165        if has_star && !has_question {
166            let parts: Vec<&str> = pat.split('*').collect();
167            if parts.len() == 2 && !parts[0].is_empty() && !parts[1].is_empty() {
168                return Ok(CompiledPattern::PrefixSuffix(
169                    parts[0].to_string(),
170                    parts[1].to_string(),
171                ));
172            }
173        }
174
175        // Fall back to regex for complex patterns
176        let regex_pattern = Self::glob_to_regex(&pat);
177        let regex = regex::Regex::new(&format!("^{}$", regex_pattern));
178
179        regex
180            .map(CompiledPattern::Regex)
181            .map_err(|error| Error::invalid_argument(format!("invalid GLOB pattern: {error}")))
182    }
183
184    /// Convert GLOB pattern to regex (* -> .*, ? -> .)
185    fn glob_to_regex(pattern: &str) -> String {
186        let mut result = String::with_capacity(pattern.len() * 2);
187        let mut chars = pattern.chars().peekable();
188
189        while let Some(c) = chars.next() {
190            match c {
191                '*' => result.push_str(".*"),
192                '?' => result.push('.'),
193                '\\' => {
194                    // Escape sequence
195                    if let Some(&next) = chars.peek() {
196                        if next == '*' || next == '?' || next == '\\' {
197                            result.push_str(&regex::escape(&next.to_string()));
198                            chars.next();
199                        } else {
200                            result.push_str(&regex::escape("\\"));
201                        }
202                    }
203                }
204                '[' => {
205                    // Character class in GLOB - pass through to regex
206                    result.push('[');
207                    for c in chars.by_ref() {
208                        if c == ']' {
209                            result.push(']');
210                            break;
211                        }
212                        result.push(c);
213                    }
214                }
215                _ => result.push_str(&regex::escape(&c.to_string())),
216            }
217        }
218
219        result
220    }
221
222    /// Convert LIKE pattern to regex
223    fn like_to_regex(pattern: &str) -> String {
224        let mut result = String::with_capacity(pattern.len() * 2);
225        let mut chars = pattern.chars().peekable();
226
227        while let Some(c) = chars.next() {
228            match c {
229                '%' => result.push_str(".*"),
230                '_' => result.push('.'),
231                '\\' => {
232                    // Escape sequence: \% → literal %, \_ → literal _, \\ → literal \
233                    if let Some(&next) = chars.peek() {
234                        if next == '%' || next == '_' || next == '\\' {
235                            chars.next();
236                            result.push_str(&regex::escape(&next.to_string()));
237                        } else {
238                            result.push_str(&regex::escape("\\"));
239                        }
240                    } else {
241                        // Trailing backslash: emit literal backslash
242                        result.push_str(&regex::escape("\\"));
243                    }
244                }
245                _ => result.push_str(&regex::escape(&c.to_string())),
246            }
247        }
248
249        result
250    }
251
252    /// Match a string against this pattern
253    #[inline]
254    pub fn matches(&self, text: &str, case_insensitive: bool) -> bool {
255        // Fast path: case-sensitive matching (no allocation)
256        if !case_insensitive {
257            return match self {
258                CompiledPattern::Exact(p) => text == p,
259                CompiledPattern::Prefix(p) => text.starts_with(p),
260                CompiledPattern::Suffix(p) => text.ends_with(p),
261                // Use memmem::find for SIMD-accelerated substring search
262                CompiledPattern::Contains(p) => {
263                    memmem::find(text.as_bytes(), p.as_bytes()).is_some()
264                }
265                CompiledPattern::PrefixSuffix(prefix, suffix) => {
266                    text.starts_with(prefix)
267                        && text.ends_with(suffix)
268                        && text.len() >= prefix.len() + suffix.len()
269                }
270                CompiledPattern::Regex(re) => re.is_match(text),
271                CompiledPattern::MatchAll => true,
272                CompiledPattern::SingleChar => text.chars().count() == 1,
273            };
274        }
275
276        // Case-insensitive: use ASCII fast path when possible, fall back to Unicode
277        // NOTE: Pattern is already lowercased at compile time (see compile() method),
278        // so we only need to lowercase the input text, not the pattern again.
279        match self {
280            CompiledPattern::Exact(p) => {
281                if text.is_ascii() && p.is_ascii() {
282                    text.eq_ignore_ascii_case(p)
283                } else {
284                    // Pattern already lowercased at compile time
285                    text.to_lowercase() == *p
286                }
287            }
288            CompiledPattern::Prefix(p) => {
289                if text.len() < p.len() {
290                    return false;
291                }
292                if text.is_ascii() && p.is_ascii() {
293                    text[..p.len()].eq_ignore_ascii_case(p)
294                } else {
295                    // Pattern already lowercased at compile time
296                    text.to_lowercase().starts_with(p)
297                }
298            }
299            CompiledPattern::Suffix(p) => {
300                if text.len() < p.len() {
301                    return false;
302                }
303                if text.is_ascii() && p.is_ascii() {
304                    text[text.len() - p.len()..].eq_ignore_ascii_case(p)
305                } else {
306                    // Pattern already lowercased at compile time
307                    text.to_lowercase().ends_with(p)
308                }
309            }
310            CompiledPattern::Contains(p) => {
311                if p.len() > text.len() {
312                    return false;
313                }
314                if text.is_ascii() && p.is_ascii() {
315                    // Fast path: ASCII-only, use byte-level comparison without allocation
316                    text.as_bytes()
317                        .windows(p.len())
318                        .any(|window| window.eq_ignore_ascii_case(p.as_bytes()))
319                } else {
320                    // Unicode path: lowercase text only (pattern already lowercased)
321                    text.to_lowercase().contains(p)
322                }
323            }
324            CompiledPattern::PrefixSuffix(prefix, suffix) => {
325                if text.len() < prefix.len() + suffix.len() {
326                    return false;
327                }
328                if text.is_ascii() && prefix.is_ascii() && suffix.is_ascii() {
329                    text[..prefix.len()].eq_ignore_ascii_case(prefix)
330                        && text[text.len() - suffix.len()..].eq_ignore_ascii_case(suffix)
331                } else {
332                    // Patterns already lowercased at compile time
333                    let text_lower = text.to_lowercase();
334                    text_lower.starts_with(prefix) && text_lower.ends_with(suffix)
335                }
336            }
337            // Regex already has case-insensitivity compiled in
338            CompiledPattern::Regex(re) => re.is_match(text),
339            CompiledPattern::MatchAll => true,
340            CompiledPattern::SingleChar => text.chars().count() == 1,
341        }
342    }
343}
344
345/// Expression VM Operation
346///
347/// Each operation is self-contained with all data needed for execution.
348/// No external lookups during execution - everything resolved at compile time.
349#[derive(Clone)]
350pub enum Op {
351    // =========================================================================
352    // LOAD OPERATIONS - Push values onto stack
353    // =========================================================================
354    /// Load column value by pre-resolved index
355    /// Stack: `[] -> [value]`
356    LoadColumn(u16),
357
358    /// Load column from second row (for joins)
359    /// Stack: `[] -> [value]`
360    LoadColumn2(u16),
361
362    /// Load from outer row context (for correlated subqueries)
363    /// Uses pre-resolved key
364    /// Stack: `[] -> [value]`
365    LoadOuterColumn(CompactArc<str>),
366
367    /// Load constant value (pre-cloned at compile time)
368    /// Stack: `[] -> [value]`
369    LoadConst(Value),
370
371    /// Load query parameter by index
372    /// Stack: `[] -> [value]`
373    LoadParam(u16),
374
375    /// Load named parameter
376    /// Stack: `[] -> [value]`
377    LoadNamedParam(CompactArc<str>),
378
379    /// Load NULL with type hint
380    /// Stack: `[] -> [null]`
381    LoadNull(DataType),
382
383    // =========================================================================
384    // COMPARISON OPERATIONS - Pop 2, push bool
385    // =========================================================================
386    /// Equal: a == b
387    Eq,
388    /// Not equal: a != b
389    Ne,
390    /// Less than: a < b
391    Lt,
392    /// Less than or equal: a <= b
393    Le,
394    /// Greater than: a > b
395    Gt,
396    /// Greater than or equal: a >= b
397    Ge,
398
399    /// IS NULL check
400    /// Stack: `[value] -> [bool]`
401    IsNull,
402
403    /// IS NOT NULL check
404    /// Stack: `[value] -> [bool]`
405    IsNotNull,
406
407    /// IS DISTINCT FROM (NULL-safe not equal)
408    /// Stack: `[a, b] -> [bool]`
409    IsDistinctFrom,
410
411    /// IS NOT DISTINCT FROM (NULL-safe equal)
412    /// Stack: `[a, b] -> [bool]`
413    IsNotDistinctFrom,
414
415    // =========================================================================
416    // FUSED COMPARISON OPERATIONS - Single instruction for column vs constant
417    // These avoid push/pop overhead for the most common filter patterns
418    // =========================================================================
419    /// Fused: column == constant
420    /// Stack: `[] -> [bool]`
421    EqColumnConst(u16, Value),
422
423    /// Fused: column != constant
424    /// Stack: `[] -> [bool]`
425    NeColumnConst(u16, Value),
426
427    /// Fused: column < constant
428    /// Stack: `[] -> [bool]`
429    LtColumnConst(u16, Value),
430
431    /// Fused: column <= constant
432    /// Stack: `[] -> [bool]`
433    LeColumnConst(u16, Value),
434
435    /// Fused: column > constant
436    /// Stack: `[] -> [bool]`
437    GtColumnConst(u16, Value),
438
439    /// Fused: column >= constant
440    /// Stack: `[] -> [bool]`
441    GeColumnConst(u16, Value),
442
443    /// Fused: column IS NULL
444    /// Stack: `[] -> [bool]`
445    IsNullColumn(u16),
446
447    /// Fused: column IS NOT NULL
448    /// Stack: `[] -> [bool]`
449    IsNotNullColumn(u16),
450
451    /// Fused: column LIKE pattern
452    /// Stack: `[] -> [bool]`
453    LikeColumn(u16, Arc<CompiledPattern>, bool), // col_idx, pattern, case_insensitive
454
455    /// Fused: column IN (constant set with AHash)
456    /// Stack: `[] -> [bool]`
457    InSetColumn(u16, CompactArc<ValueSet>, bool), // col_idx, set, has_null
458
459    /// Fused: column BETWEEN low AND high (constants)
460    /// Stack: `[] -> [bool]`
461    BetweenColumnConst(u16, Value, Value), // col_idx, low, high
462
463    // =========================================================================
464    // LOGICAL OPERATIONS
465    // =========================================================================
466    /// Logical AND with short-circuit
467    /// If top of stack is false, jump to target
468    /// Stack: `[bool] -> [bool] (or jump)`
469    And(u16), // Jump target if false
470
471    /// Logical OR with short-circuit
472    /// If top of stack is true, jump to target
473    /// Stack: `[bool] -> [bool] (or jump)`
474    Or(u16), // Jump target if true
475
476    /// Logical NOT
477    /// Stack: `[bool] -> [bool]`
478    Not,
479
480    /// Logical XOR
481    /// Stack: `[a, b] -> [bool]`
482    Xor,
483
484    /// AND finalize - combine left and right results
485    /// Stack: `[left_bool, right_bool] -> [bool]`
486    AndFinalize,
487
488    /// OR finalize - combine left and right results
489    /// Stack: `[left_bool, right_bool] -> [bool]`
490    OrFinalize,
491
492    // =========================================================================
493    // ARITHMETIC OPERATIONS - Pop 2, push result
494    // =========================================================================
495    Add,
496    Sub,
497    Mul,
498    Div,
499    Mod,
500
501    /// Unary negation
502    /// Stack: `[value] -> [-value]`
503    Neg,
504
505    // =========================================================================
506    // BITWISE OPERATIONS
507    // =========================================================================
508    BitAnd,
509    BitOr,
510    BitXor,
511    BitNot,
512    Shl,
513    Shr,
514
515    // =========================================================================
516    // STRING OPERATIONS
517    // =========================================================================
518    /// String concatenation (binary)
519    /// Stack: `[a, b] -> [a || b]`
520    Concat,
521
522    /// Multi-value string concatenation (optimized for chained ||)
523    /// Stack: `[v1, v2, ..., vN] -> [v1 || v2 || ... || vN]`
524    /// Pre-calculates total length and allocates once
525    ConcatN(u8),
526
527    /// LIKE pattern match (pre-compiled pattern)
528    /// Stack: `[text] -> [bool]`
529    Like(Arc<CompiledPattern>, bool), // pattern, case_insensitive
530
531    /// GLOB pattern match
532    /// Stack: `[text] -> [bool]`
533    Glob(Arc<CompiledPattern>),
534
535    /// REGEXP match (pre-compiled regex)
536    /// Stack: `[text] -> [bool]`
537    Regexp(Arc<regex::Regex>),
538
539    /// LIKE with ESCAPE character
540    /// Stack: `[text] -> [bool]`
541    LikeEscape(Arc<CompiledPattern>, bool, char), // pattern, case_insensitive, escape_char
542
543    /// Dynamic LIKE: pattern is on the stack (e.g. from a parameter)
544    /// Stack: `[text, pattern_text] -> [bool]`
545    LikeDynamic(bool), // case_insensitive
546
547    /// Dynamic LIKE with ESCAPE: pattern is on the stack, escape char is compiled in
548    /// Stack: `[text, pattern_text] -> [bool]`
549    LikeDynamicEscape(bool, char), // case_insensitive, escape_char
550
551    /// Dynamic GLOB: pattern is on the stack
552    /// Stack: `[text, pattern_text] -> [bool]`
553    GlobDynamic,
554
555    /// Dynamic REGEXP: pattern is on the stack
556    /// Stack: `[text, pattern_text] -> [bool]`
557    RegexpDynamic,
558
559    // =========================================================================
560    // JSON OPERATIONS
561    // =========================================================================
562    /// JSON access: json -> key (returns JSON)
563    /// Stack: `[json, key] -> [json_value]`
564    JsonAccess,
565
566    /// JSON access text: json ->> key (returns TEXT)
567    /// Stack: `[json, key] -> [text_value]`
568    JsonAccessText,
569
570    // =========================================================================
571    // TIMESTAMP OPERATIONS
572    // =========================================================================
573    /// Add interval to timestamp: timestamp + interval_string
574    /// Stack: `[timestamp, interval_text] -> [timestamp]`
575    TimestampAddInterval,
576
577    /// Subtract interval from timestamp: timestamp - interval_string
578    /// Stack: `[timestamp, interval_text] -> [timestamp]`
579    TimestampSubInterval,
580
581    /// Subtract timestamps: timestamp - timestamp (returns interval text)
582    /// Stack: `[timestamp1, timestamp2] -> [interval_text]`
583    TimestampDiff,
584
585    /// Add days to timestamp: timestamp + integer
586    /// Stack: `[timestamp, days] -> [timestamp]`
587    TimestampAddDays,
588
589    /// Subtract days from timestamp: timestamp - integer
590    /// Stack: `[timestamp, days] -> [timestamp]`
591    TimestampSubDays,
592
593    // =========================================================================
594    // VECTOR DISTANCE OPERATIONS
595    // =========================================================================
596    /// L2 (Euclidean) distance between two vectors
597    /// Stack: `[vector1, vector2] -> [float]`
598    VectorDistanceL2,
599
600    /// Cosine distance between two vectors (1 - cosine_similarity)
601    /// Stack: `[vector1, vector2] -> [float]`
602    VectorDistanceCosine,
603
604    /// Negative inner product distance between two vectors
605    /// Stack: `[vector1, vector2] -> [float]`
606    VectorDistanceIP,
607
608    // =========================================================================
609    // SET OPERATIONS
610    // =========================================================================
611    /// IN set membership (pre-built FxHashSet for fast lookups)
612    /// Stack: `[value] -> [bool]`
613    InSet(CompactArc<ValueSet>, bool), // set, has_null
614
615    /// NOT IN set membership
616    /// Stack: `[value] -> [bool]`
617    NotInSet(CompactArc<ValueSet>, bool), // set, has_null
618
619    /// BETWEEN check: value BETWEEN low AND high
620    /// Stack: `[value, low, high] -> [bool]`
621    Between,
622
623    /// NOT BETWEEN check
624    /// Stack: `[value, low, high] -> [bool]`
625    NotBetween,
626
627    /// Multi-column IN: (a, b) IN ((1, 2), (3, 4))
628    /// Stack: `[val1, val2, ...valN] -> [bool]`
629    /// The tuple_values contains pre-evaluated constant tuples
630    InTupleSet {
631        tuple_size: u8,
632        values: Arc<Vec<Vec<Value>>>, // List of tuples
633        negated: bool,
634    },
635
636    // =========================================================================
637    // BOOLEAN CHECKS
638    // =========================================================================
639    /// IS TRUE check
640    /// Stack: `[value] -> [bool]`
641    IsTrue,
642
643    /// IS NOT TRUE check
644    /// Stack: `[value] -> [bool]`
645    IsNotTrue,
646
647    /// IS FALSE check
648    /// Stack: `[value] -> [bool]`
649    IsFalse,
650
651    /// IS NOT FALSE check
652    /// Stack: `[value] -> [bool]`
653    IsNotFalse,
654
655    // =========================================================================
656    // FUNCTION CALLS
657    // =========================================================================
658    /// Call scalar function with N arguments
659    /// Stack: `[arg1, arg2, ..., argN] -> [result]`
660    CallScalar {
661        func: Arc<dyn ScalarFunction>,
662        arg_count: u8,
663    },
664
665    /// Call a durable catalog Function through the request-local executor
666    /// bridge. Resolution is repeated against the pinned catalog generation.
667    CallStored {
668        name: CompactArc<str>,
669        arg_count: u8,
670    },
671
672    /// Special: COALESCE - return first non-null
673    /// Stack: `[arg1, ..., argN] -> [result]`
674    Coalesce(u8), // arg count
675
676    /// Special: NULLIF(a, b) - return NULL if a = b
677    /// Stack: `[a, b] -> [a or null]`
678    NullIf,
679
680    /// Special: GREATEST - return max of args
681    /// Stack: `[arg1, ..., argN] -> [result]`
682    Greatest(u8),
683
684    /// Special: LEAST - return min of args
685    /// Stack: `[arg1, ..., argN] -> [result]`
686    Least(u8),
687
688    // =========================================================================
689    // NATIVE SCALAR FUNCTIONS (function pointer, no dynamic dispatch)
690    // =========================================================================
691    /// Native scalar function - single argument, direct function pointer call
692    /// Stack: `[value] -> [result]`
693    NativeFn1(NativeFn1),
694
695    // =========================================================================
696    // TYPE OPERATIONS
697    // =========================================================================
698    /// Cast value to target type
699    /// Stack: `[value] -> [casted_value]`
700    Cast(DataType),
701
702    /// Cast TEXT/BYTES into a schema-qualified external type. Resolution and
703    /// codec invocation stay request-local and catalog-bound.
704    /// Stack: `[value] -> [external_value]`
705    CastExternal(CompactArc<str>),
706
707    /// Truncate timestamp to date (midnight)
708    /// Used for CAST(timestamp AS DATE) - truncates time component to 00:00:00
709    /// Stack: `[value] -> [timestamp_at_midnight]`
710    TruncateToDate,
711
712    // =========================================================================
713    // CASE EXPRESSION
714    // =========================================================================
715    /// Start of CASE - marks beginning
716    CaseStart,
717
718    /// WHEN condition: if top is false, jump to next branch
719    /// Stack: `[bool] -> [] (condition consumed)`
720    CaseWhen(u16), // Jump to next WHEN/ELSE/END if false
721
722    /// THEN result: jump to CASE end after pushing result
723    /// Stack: `[value] -> [value] (then jump)`
724    CaseThen(u16), // Jump to END
725
726    /// ELSE clause marker
727    CaseElse,
728
729    /// End of CASE
730    CaseEnd,
731
732    /// Simple CASE: compare value with WHEN value
733    /// Stack: `[case_value, when_value] -> [bool]`
734    CaseCompare,
735
736    // =========================================================================
737    // CONTROL FLOW
738    // =========================================================================
739    /// Unconditional jump
740    Jump(u16),
741
742    /// Jump if top of stack is true (doesn't pop)
743    JumpIfTrue(u16),
744
745    /// Jump if top of stack is false (doesn't pop)
746    JumpIfFalse(u16),
747
748    /// Jump if top of stack is NULL (doesn't pop)
749    JumpIfNull(u16),
750
751    /// Jump if top of stack is NOT NULL (doesn't pop)
752    /// Used for COALESCE short-circuit evaluation
753    JumpIfNotNull(u16),
754
755    /// Pop and jump if true
756    PopJumpIfTrue(u16),
757
758    /// Pop and jump if false
759    PopJumpIfFalse(u16),
760
761    /// Duplicate top of stack
762    Dup,
763
764    /// Pop top of stack (discard)
765    Pop,
766
767    /// Swap top two stack elements
768    Swap,
769
770    // =========================================================================
771    // AGGREGATE REFERENCES (post-aggregation)
772    // =========================================================================
773    /// Load pre-computed aggregate result by column index
774    /// Used in HAVING clauses where aggregates are already computed
775    /// Stack: `[] -> [value]`
776    LoadAggregateResult(u16),
777
778    /// Load current transaction ID
779    /// Returns NULL if no transaction is active
780    /// Stack: `[] -> [value]`
781    LoadTransactionId,
782
783    // =========================================================================
784    // SPECIAL
785    // =========================================================================
786    /// No operation (placeholder)
787    Nop,
788
789    /// Return current top of stack as result
790    Return,
791
792    /// Return true immediately
793    ReturnTrue,
794
795    /// Return false immediately
796    ReturnFalse,
797
798    /// Return NULL immediately
799    ReturnNull(DataType),
800}
801
802// Make Op Debug-printable (without showing full function pointers)
803impl std::fmt::Debug for Op {
804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805        match self {
806            Op::LoadColumn(idx) => write!(f, "LoadColumn({})", idx),
807            Op::LoadColumn2(idx) => write!(f, "LoadColumn2({})", idx),
808            Op::LoadOuterColumn(name) => write!(f, "LoadOuterColumn({})", name),
809            Op::LoadConst(v) => write!(f, "LoadConst({:?})", v),
810            Op::LoadParam(idx) => write!(f, "LoadParam({})", idx),
811            Op::LoadNamedParam(name) => write!(f, "LoadNamedParam({})", name),
812            Op::LoadNull(dt) => write!(f, "LoadNull({:?})", dt),
813            Op::Eq => write!(f, "Eq"),
814            Op::Ne => write!(f, "Ne"),
815            Op::Lt => write!(f, "Lt"),
816            Op::Le => write!(f, "Le"),
817            Op::Gt => write!(f, "Gt"),
818            Op::Ge => write!(f, "Ge"),
819            Op::IsNull => write!(f, "IsNull"),
820            Op::IsNotNull => write!(f, "IsNotNull"),
821            Op::IsDistinctFrom => write!(f, "IsDistinctFrom"),
822            Op::IsNotDistinctFrom => write!(f, "IsNotDistinctFrom"),
823            Op::EqColumnConst(col, val) => write!(f, "EqColumnConst({}, {:?})", col, val),
824            Op::NeColumnConst(col, val) => write!(f, "NeColumnConst({}, {:?})", col, val),
825            Op::LtColumnConst(col, val) => write!(f, "LtColumnConst({}, {:?})", col, val),
826            Op::LeColumnConst(col, val) => write!(f, "LeColumnConst({}, {:?})", col, val),
827            Op::GtColumnConst(col, val) => write!(f, "GtColumnConst({}, {:?})", col, val),
828            Op::GeColumnConst(col, val) => write!(f, "GeColumnConst({}, {:?})", col, val),
829            Op::IsNullColumn(col) => write!(f, "IsNullColumn({})", col),
830            Op::IsNotNullColumn(col) => write!(f, "IsNotNullColumn({})", col),
831            Op::LikeColumn(col, _, ci) => write!(f, "LikeColumn({}, case_insensitive={})", col, ci),
832            Op::InSetColumn(col, set, has_null) => {
833                write!(
834                    f,
835                    "InSetColumn({}, len={}, has_null={})",
836                    col,
837                    set.len(),
838                    has_null
839                )
840            }
841            Op::BetweenColumnConst(col, low, high) => {
842                write!(f, "BetweenColumnConst({}, {:?}, {:?})", col, low, high)
843            }
844            Op::And(target) => write!(f, "And(jump={})", target),
845            Op::Or(target) => write!(f, "Or(jump={})", target),
846            Op::Not => write!(f, "Not"),
847            Op::Xor => write!(f, "Xor"),
848            Op::AndFinalize => write!(f, "AndFinalize"),
849            Op::OrFinalize => write!(f, "OrFinalize"),
850            Op::Add => write!(f, "Add"),
851            Op::Sub => write!(f, "Sub"),
852            Op::Mul => write!(f, "Mul"),
853            Op::Div => write!(f, "Div"),
854            Op::Mod => write!(f, "Mod"),
855            Op::Neg => write!(f, "Neg"),
856            Op::BitAnd => write!(f, "BitAnd"),
857            Op::BitOr => write!(f, "BitOr"),
858            Op::BitXor => write!(f, "BitXor"),
859            Op::BitNot => write!(f, "BitNot"),
860            Op::Shl => write!(f, "Shl"),
861            Op::Shr => write!(f, "Shr"),
862            Op::Concat => write!(f, "Concat"),
863            Op::Like(_, ci) => write!(f, "Like(case_insensitive={})", ci),
864            Op::Glob(_) => write!(f, "Glob"),
865            Op::Regexp(_) => write!(f, "Regexp"),
866            Op::LikeEscape(_, ci, esc) => {
867                write!(f, "LikeEscape(case_insensitive={}, escape='{}')", ci, esc)
868            }
869            Op::LikeDynamic(ci) => write!(f, "LikeDynamic(case_insensitive={})", ci),
870            Op::LikeDynamicEscape(ci, esc) => {
871                write!(
872                    f,
873                    "LikeDynamicEscape(case_insensitive={}, escape='{}')",
874                    ci, esc
875                )
876            }
877            Op::GlobDynamic => write!(f, "GlobDynamic"),
878            Op::RegexpDynamic => write!(f, "RegexpDynamic"),
879            Op::JsonAccess => write!(f, "JsonAccess"),
880            Op::JsonAccessText => write!(f, "JsonAccessText"),
881            Op::TimestampAddInterval => write!(f, "TimestampAddInterval"),
882            Op::TimestampSubInterval => write!(f, "TimestampSubInterval"),
883            Op::TimestampDiff => write!(f, "TimestampDiff"),
884            Op::TimestampAddDays => write!(f, "TimestampAddDays"),
885            Op::TimestampSubDays => write!(f, "TimestampSubDays"),
886            Op::VectorDistanceL2 => write!(f, "VectorDistanceL2"),
887            Op::VectorDistanceCosine => write!(f, "VectorDistanceCosine"),
888            Op::VectorDistanceIP => write!(f, "VectorDistanceIP"),
889            Op::InSet(set, has_null) => {
890                write!(f, "InSet(len={}, has_null={})", set.len(), has_null)
891            }
892            Op::NotInSet(set, has_null) => {
893                write!(f, "NotInSet(len={}, has_null={})", set.len(), has_null)
894            }
895            Op::Between => write!(f, "Between"),
896            Op::NotBetween => write!(f, "NotBetween"),
897            Op::InTupleSet {
898                tuple_size,
899                values,
900                negated,
901            } => {
902                write!(
903                    f,
904                    "InTupleSet(size={}, tuples={}, negated={})",
905                    tuple_size,
906                    values.len(),
907                    negated
908                )
909            }
910            Op::IsTrue => write!(f, "IsTrue"),
911            Op::IsNotTrue => write!(f, "IsNotTrue"),
912            Op::IsFalse => write!(f, "IsFalse"),
913            Op::IsNotFalse => write!(f, "IsNotFalse"),
914            Op::CallScalar { arg_count, .. } => write!(f, "CallScalar(args={})", arg_count),
915            Op::CallStored { name, arg_count } => {
916                write!(f, "CallStored({name}, args={arg_count})")
917            }
918            Op::Coalesce(n) => write!(f, "Coalesce({})", n),
919            Op::NullIf => write!(f, "NullIf"),
920            Op::Greatest(n) => write!(f, "Greatest({})", n),
921            Op::Least(n) => write!(f, "Least({})", n),
922            Op::Cast(dt) => write!(f, "Cast({:?})", dt),
923            Op::CastExternal(name) => write!(f, "CastExternal({name})"),
924            Op::TruncateToDate => write!(f, "TruncateToDate"),
925            Op::CaseStart => write!(f, "CaseStart"),
926            Op::CaseWhen(target) => write!(f, "CaseWhen(jump={})", target),
927            Op::CaseThen(target) => write!(f, "CaseThen(jump={})", target),
928            Op::CaseElse => write!(f, "CaseElse"),
929            Op::CaseEnd => write!(f, "CaseEnd"),
930            Op::CaseCompare => write!(f, "CaseCompare"),
931            Op::Jump(target) => write!(f, "Jump({})", target),
932            Op::JumpIfTrue(target) => write!(f, "JumpIfTrue({})", target),
933            Op::JumpIfFalse(target) => write!(f, "JumpIfFalse({})", target),
934            Op::JumpIfNull(target) => write!(f, "JumpIfNull({})", target),
935            Op::JumpIfNotNull(target) => write!(f, "JumpIfNotNull({})", target),
936            Op::PopJumpIfTrue(target) => write!(f, "PopJumpIfTrue({})", target),
937            Op::PopJumpIfFalse(target) => write!(f, "PopJumpIfFalse({})", target),
938            Op::Dup => write!(f, "Dup"),
939            Op::Pop => write!(f, "Pop"),
940            Op::Swap => write!(f, "Swap"),
941            Op::LoadAggregateResult(idx) => write!(f, "LoadAggregateResult({})", idx),
942            Op::LoadTransactionId => write!(f, "LoadTransactionId"),
943            Op::Nop => write!(f, "Nop"),
944            Op::Return => write!(f, "Return"),
945            Op::ReturnTrue => write!(f, "ReturnTrue"),
946            Op::ReturnFalse => write!(f, "ReturnFalse"),
947            Op::ReturnNull(dt) => write!(f, "ReturnNull({:?})", dt),
948            Op::NativeFn1(_) => write!(f, "NativeFn1(...)"),
949            Op::ConcatN(n) => write!(f, "ConcatN({})", n),
950        }
951    }
952}
953
954#[cfg(test)]
955mod tests {
956    use super::*;
957    use std::mem;
958
959    #[test]
960    fn test_op_size() {
961        let size = mem::size_of::<Op>();
962        println!("Op enum size: {} bytes", size);
963        println!("Op alignment: {} bytes", mem::align_of::<Op>());
964
965        // The Op enum contains large variants like:
966        // - InTupleSet { tuple_size: u8, values: Arc<Vec<Vec<Value>>>, negated: bool }
967        // - CallScalar { func: Arc<dyn ScalarFunction>, arg_count: u8 }
968        // - GtColumnConst(u16, Value) where Value is 24 bytes
969
970        // We want to keep Op small for cache efficiency
971        // Ideally under 32 bytes, definitely under 64 bytes
972        assert!(size <= 64, "Op enum is too large: {} bytes", size);
973    }
974
975    // =========================================================================
976    // CompiledPattern::compile() tests - LIKE patterns
977    // =========================================================================
978
979    #[test]
980    fn test_pattern_exact() {
981        let pattern = CompiledPattern::compile("hello", false).unwrap();
982        assert!(matches!(pattern, CompiledPattern::Exact(ref s) if s == "hello"));
983        assert!(pattern.matches("hello", false));
984        assert!(!pattern.matches("Hello", false));
985        assert!(!pattern.matches("hello world", false));
986    }
987
988    #[test]
989    fn test_pattern_exact_case_insensitive() {
990        let pattern = CompiledPattern::compile("Hello", true).unwrap();
991        assert!(matches!(pattern, CompiledPattern::Exact(ref s) if s == "hello"));
992        assert!(pattern.matches("hello", true));
993        assert!(pattern.matches("HELLO", true));
994        assert!(pattern.matches("HeLLo", true));
995    }
996
997    #[test]
998    fn test_pattern_match_all() {
999        let pattern = CompiledPattern::compile("%", false).unwrap();
1000        assert!(matches!(pattern, CompiledPattern::MatchAll));
1001        assert!(pattern.matches("", false));
1002        assert!(pattern.matches("anything", false));
1003        assert!(pattern.matches("with spaces and 123", false));
1004    }
1005
1006    #[test]
1007    fn test_pattern_single_char() {
1008        let pattern = CompiledPattern::compile("_", false).unwrap();
1009        assert!(matches!(pattern, CompiledPattern::SingleChar));
1010        assert!(pattern.matches("a", false));
1011        assert!(pattern.matches("Z", false));
1012        assert!(!pattern.matches("", false));
1013        assert!(!pattern.matches("ab", false));
1014    }
1015
1016    #[test]
1017    fn test_pattern_prefix() {
1018        let pattern = CompiledPattern::compile("hello%", false).unwrap();
1019        assert!(matches!(pattern, CompiledPattern::Prefix(ref s) if s == "hello"));
1020        assert!(pattern.matches("hello", false));
1021        assert!(pattern.matches("hello world", false));
1022        assert!(pattern.matches("hellooooo", false));
1023        assert!(!pattern.matches("Hello", false));
1024        assert!(!pattern.matches("say hello", false));
1025    }
1026
1027    #[test]
1028    fn test_pattern_prefix_case_insensitive() {
1029        let pattern = CompiledPattern::compile("Hello%", true).unwrap();
1030        assert!(pattern.matches("hello world", true));
1031        assert!(pattern.matches("HELLO WORLD", true));
1032        assert!(!pattern.matches("say hello", true));
1033    }
1034
1035    #[test]
1036    fn test_pattern_suffix() {
1037        let pattern = CompiledPattern::compile("%world", false).unwrap();
1038        assert!(matches!(pattern, CompiledPattern::Suffix(ref s) if s == "world"));
1039        assert!(pattern.matches("world", false));
1040        assert!(pattern.matches("hello world", false));
1041        assert!(!pattern.matches("World", false));
1042        assert!(!pattern.matches("world!", false));
1043    }
1044
1045    #[test]
1046    fn test_pattern_suffix_case_insensitive() {
1047        let pattern = CompiledPattern::compile("%World", true).unwrap();
1048        assert!(pattern.matches("hello world", true));
1049        assert!(pattern.matches("HELLO WORLD", true));
1050        assert!(!pattern.matches("world!", true));
1051    }
1052
1053    #[test]
1054    fn test_pattern_contains() {
1055        let pattern = CompiledPattern::compile("%ello%", false).unwrap();
1056        assert!(matches!(pattern, CompiledPattern::Contains(ref s) if s == "ello"));
1057        assert!(pattern.matches("hello", false));
1058        assert!(pattern.matches("yellow", false));
1059        assert!(pattern.matches("hello world", false));
1060        assert!(!pattern.matches("HELLO", false));
1061    }
1062
1063    #[test]
1064    fn test_pattern_contains_case_insensitive() {
1065        let pattern = CompiledPattern::compile("%ELLO%", true).unwrap();
1066        assert!(pattern.matches("hello", true));
1067        assert!(pattern.matches("YELLOW", true));
1068        assert!(!pattern.matches("hi", true));
1069    }
1070
1071    #[test]
1072    fn test_pattern_prefix_suffix() {
1073        let pattern = CompiledPattern::compile("hello%world", false).unwrap();
1074        assert!(
1075            matches!(pattern, CompiledPattern::PrefixSuffix(ref p, ref s) if p == "hello" && s == "world")
1076        );
1077        assert!(pattern.matches("helloworld", false));
1078        assert!(pattern.matches("hello world", false));
1079        assert!(pattern.matches("hello beautiful world", false));
1080        assert!(!pattern.matches("hello", false));
1081        assert!(!pattern.matches("world", false));
1082    }
1083
1084    #[test]
1085    fn test_pattern_prefix_suffix_case_insensitive() {
1086        let pattern = CompiledPattern::compile("Hello%World", true).unwrap();
1087        assert!(pattern.matches("helloworld", true));
1088        assert!(pattern.matches("HELLO WORLD", true));
1089        assert!(!pattern.matches("hello", true));
1090    }
1091
1092    #[test]
1093    fn test_pattern_prefix_suffix_too_short() {
1094        let pattern = CompiledPattern::compile("abc%xyz", false).unwrap();
1095        // Text must be at least prefix.len() + suffix.len()
1096        assert!(!pattern.matches("abcxy", false)); // too short
1097        assert!(pattern.matches("abcxyz", false)); // exact length
1098        assert!(pattern.matches("abc123xyz", false));
1099    }
1100
1101    #[test]
1102    fn test_pattern_complex_regex() {
1103        // Pattern with multiple % or _ that requires regex
1104        let pattern = CompiledPattern::compile("a%b%c", false).unwrap();
1105        assert!(matches!(pattern, CompiledPattern::Regex(_)));
1106        assert!(pattern.matches("abc", false));
1107        assert!(pattern.matches("aXbYc", false));
1108        assert!(pattern.matches("aXXXbYYYc", false));
1109        assert!(!pattern.matches("ac", false));
1110    }
1111
1112    #[test]
1113    fn test_pattern_underscore_regex() {
1114        let pattern = CompiledPattern::compile("a_c", false).unwrap();
1115        assert!(matches!(pattern, CompiledPattern::Regex(_)));
1116        assert!(pattern.matches("abc", false));
1117        assert!(pattern.matches("aXc", false));
1118        assert!(!pattern.matches("ac", false));
1119        assert!(!pattern.matches("abbc", false));
1120    }
1121
1122    #[test]
1123    fn test_pattern_mixed_wildcards() {
1124        let pattern = CompiledPattern::compile("a_%b", false).unwrap();
1125        assert!(matches!(pattern, CompiledPattern::Regex(_)));
1126        assert!(pattern.matches("aXb", false));
1127        assert!(pattern.matches("aXYZb", false));
1128        assert!(!pattern.matches("ab", false));
1129    }
1130
1131    // =========================================================================
1132    // CompiledPattern::compile_glob() tests - GLOB patterns
1133    // =========================================================================
1134
1135    #[test]
1136    fn test_glob_exact() {
1137        let pattern = CompiledPattern::compile_glob("hello").unwrap();
1138        assert!(matches!(pattern, CompiledPattern::Exact(ref s) if s == "hello"));
1139        assert!(pattern.matches("hello", false));
1140        assert!(!pattern.matches("Hello", false));
1141    }
1142
1143    #[test]
1144    fn test_glob_match_all() {
1145        let pattern = CompiledPattern::compile_glob("*").unwrap();
1146        assert!(matches!(pattern, CompiledPattern::MatchAll));
1147        assert!(pattern.matches("anything", false));
1148    }
1149
1150    #[test]
1151    fn test_glob_single_char() {
1152        let pattern = CompiledPattern::compile_glob("?").unwrap();
1153        assert!(matches!(pattern, CompiledPattern::SingleChar));
1154        assert!(pattern.matches("a", false));
1155        assert!(!pattern.matches("ab", false));
1156    }
1157
1158    #[test]
1159    fn test_glob_prefix() {
1160        let pattern = CompiledPattern::compile_glob("hello*").unwrap();
1161        assert!(matches!(pattern, CompiledPattern::Prefix(ref s) if s == "hello"));
1162        assert!(pattern.matches("hello", false));
1163        assert!(pattern.matches("hello world", false));
1164    }
1165
1166    #[test]
1167    fn test_glob_suffix() {
1168        let pattern = CompiledPattern::compile_glob("*world").unwrap();
1169        assert!(matches!(pattern, CompiledPattern::Suffix(ref s) if s == "world"));
1170        assert!(pattern.matches("world", false));
1171        assert!(pattern.matches("hello world", false));
1172    }
1173
1174    #[test]
1175    fn test_glob_contains() {
1176        let pattern = CompiledPattern::compile_glob("*ello*").unwrap();
1177        assert!(matches!(pattern, CompiledPattern::Contains(ref s) if s == "ello"));
1178        assert!(pattern.matches("hello", false));
1179        assert!(pattern.matches("yellow", false));
1180    }
1181
1182    #[test]
1183    fn test_glob_prefix_suffix() {
1184        let pattern = CompiledPattern::compile_glob("hello*world").unwrap();
1185        assert!(
1186            matches!(pattern, CompiledPattern::PrefixSuffix(ref p, ref s) if p == "hello" && s == "world")
1187        );
1188        assert!(pattern.matches("helloworld", false));
1189        assert!(pattern.matches("hello beautiful world", false));
1190    }
1191
1192    #[test]
1193    fn test_glob_complex_regex() {
1194        let pattern = CompiledPattern::compile_glob("a*b*c").unwrap();
1195        assert!(matches!(pattern, CompiledPattern::Regex(_)));
1196        assert!(pattern.matches("abc", false));
1197        assert!(pattern.matches("aXbYc", false));
1198    }
1199
1200    #[test]
1201    fn test_glob_question_mark() {
1202        let pattern = CompiledPattern::compile_glob("a?c").unwrap();
1203        assert!(matches!(pattern, CompiledPattern::Regex(_)));
1204        assert!(pattern.matches("abc", false));
1205        assert!(!pattern.matches("ac", false));
1206    }
1207
1208    #[test]
1209    fn test_glob_character_class() {
1210        // Character class alone without * or ? is treated as exact match
1211        let pattern = CompiledPattern::compile_glob("a[bc]d").unwrap();
1212        assert!(matches!(pattern, CompiledPattern::Exact(_)));
1213
1214        // Pattern with * at end is treated as Prefix
1215        let pattern2 = CompiledPattern::compile_glob("a[bc]*").unwrap();
1216        assert!(matches!(pattern2, CompiledPattern::Prefix(ref s) if s == "a[bc]"));
1217
1218        // *[bc]* is Contains since middle has no wildcards
1219        let pattern3 = CompiledPattern::compile_glob("*[bc]*").unwrap();
1220        assert!(matches!(pattern3, CompiledPattern::Contains(ref s) if s == "[bc]"));
1221    }
1222
1223    #[test]
1224    fn test_glob_escape_edge_cases() {
1225        // `a\\*b` splits on * giving ["a\\", "b"] - treated as PrefixSuffix
1226        let pattern1 = CompiledPattern::compile_glob("a\\*b").unwrap();
1227        assert!(matches!(pattern1, CompiledPattern::PrefixSuffix(_, _)));
1228
1229        // Complex escapes with multiple wildcards go to regex
1230        let pattern2 = CompiledPattern::compile_glob("*a*b*").unwrap();
1231        assert!(matches!(pattern2, CompiledPattern::Regex(_)));
1232        assert!(pattern2.matches("XaYbZ", false));
1233        assert!(pattern2.matches("ab", false));
1234    }
1235
1236    #[test]
1237    fn invalid_regex_compilation_is_an_error_not_literal_fallback() {
1238        assert!(CompiledPattern::compile_glob("[?").is_err());
1239    }
1240
1241    // =========================================================================
1242    // Op Debug format tests
1243    // =========================================================================
1244
1245    #[test]
1246    fn test_op_debug_format() {
1247        // Test that Debug formatting works for various Op variants
1248        assert_eq!(format!("{:?}", Op::LoadColumn(5)), "LoadColumn(5)");
1249        assert_eq!(format!("{:?}", Op::LoadColumn2(3)), "LoadColumn2(3)");
1250        assert_eq!(format!("{:?}", Op::Eq), "Eq");
1251        assert_eq!(format!("{:?}", Op::Ne), "Ne");
1252        assert_eq!(format!("{:?}", Op::Lt), "Lt");
1253        assert_eq!(format!("{:?}", Op::Le), "Le");
1254        assert_eq!(format!("{:?}", Op::Gt), "Gt");
1255        assert_eq!(format!("{:?}", Op::Ge), "Ge");
1256        assert_eq!(format!("{:?}", Op::Add), "Add");
1257        assert_eq!(format!("{:?}", Op::Sub), "Sub");
1258        assert_eq!(format!("{:?}", Op::Mul), "Mul");
1259        assert_eq!(format!("{:?}", Op::Div), "Div");
1260        assert_eq!(format!("{:?}", Op::Mod), "Mod");
1261        assert_eq!(format!("{:?}", Op::Not), "Not");
1262        assert_eq!(format!("{:?}", Op::Neg), "Neg");
1263        assert_eq!(format!("{:?}", Op::IsNull), "IsNull");
1264        assert_eq!(format!("{:?}", Op::IsNotNull), "IsNotNull");
1265        assert_eq!(format!("{:?}", Op::Return), "Return");
1266        assert_eq!(format!("{:?}", Op::ReturnTrue), "ReturnTrue");
1267        assert_eq!(format!("{:?}", Op::ReturnFalse), "ReturnFalse");
1268        assert_eq!(format!("{:?}", Op::Nop), "Nop");
1269        assert_eq!(format!("{:?}", Op::Dup), "Dup");
1270        assert_eq!(format!("{:?}", Op::Pop), "Pop");
1271        assert_eq!(format!("{:?}", Op::Swap), "Swap");
1272    }
1273
1274    #[test]
1275    fn test_op_debug_format_with_values() {
1276        assert_eq!(
1277            format!("{:?}", Op::LoadConst(Value::Integer(42))),
1278            "LoadConst(Integer(42))"
1279        );
1280        assert_eq!(
1281            format!("{:?}", Op::EqColumnConst(0, Value::Integer(10))),
1282            "EqColumnConst(0, Integer(10))"
1283        );
1284        assert_eq!(format!("{:?}", Op::And(5)), "And(jump=5)");
1285        assert_eq!(format!("{:?}", Op::Or(10)), "Or(jump=10)");
1286        assert_eq!(format!("{:?}", Op::Jump(15)), "Jump(15)");
1287        assert_eq!(format!("{:?}", Op::JumpIfTrue(20)), "JumpIfTrue(20)");
1288        assert_eq!(format!("{:?}", Op::JumpIfFalse(25)), "JumpIfFalse(25)");
1289    }
1290
1291    #[test]
1292    fn test_op_debug_format_special() {
1293        assert_eq!(format!("{:?}", Op::Coalesce(3)), "Coalesce(3)");
1294        assert_eq!(format!("{:?}", Op::NullIf), "NullIf");
1295        assert_eq!(format!("{:?}", Op::Greatest(2)), "Greatest(2)");
1296        assert_eq!(format!("{:?}", Op::Least(4)), "Least(4)");
1297        assert_eq!(format!("{:?}", Op::Between), "Between");
1298        assert_eq!(format!("{:?}", Op::NotBetween), "NotBetween");
1299        assert_eq!(format!("{:?}", Op::Concat), "Concat");
1300        assert_eq!(format!("{:?}", Op::JsonAccess), "JsonAccess");
1301        assert_eq!(format!("{:?}", Op::JsonAccessText), "JsonAccessText");
1302    }
1303
1304    #[test]
1305    fn test_op_debug_bitwise() {
1306        assert_eq!(format!("{:?}", Op::BitAnd), "BitAnd");
1307        assert_eq!(format!("{:?}", Op::BitOr), "BitOr");
1308        assert_eq!(format!("{:?}", Op::BitXor), "BitXor");
1309        assert_eq!(format!("{:?}", Op::BitNot), "BitNot");
1310        assert_eq!(format!("{:?}", Op::Shl), "Shl");
1311        assert_eq!(format!("{:?}", Op::Shr), "Shr");
1312    }
1313
1314    #[test]
1315    fn test_op_debug_case() {
1316        assert_eq!(format!("{:?}", Op::CaseStart), "CaseStart");
1317        assert_eq!(format!("{:?}", Op::CaseWhen(5)), "CaseWhen(jump=5)");
1318        assert_eq!(format!("{:?}", Op::CaseThen(10)), "CaseThen(jump=10)");
1319        assert_eq!(format!("{:?}", Op::CaseElse), "CaseElse");
1320        assert_eq!(format!("{:?}", Op::CaseEnd), "CaseEnd");
1321        assert_eq!(format!("{:?}", Op::CaseCompare), "CaseCompare");
1322    }
1323
1324    #[test]
1325    fn test_op_debug_timestamp() {
1326        assert_eq!(
1327            format!("{:?}", Op::TimestampAddInterval),
1328            "TimestampAddInterval"
1329        );
1330        assert_eq!(
1331            format!("{:?}", Op::TimestampSubInterval),
1332            "TimestampSubInterval"
1333        );
1334        assert_eq!(format!("{:?}", Op::TimestampDiff), "TimestampDiff");
1335        assert_eq!(format!("{:?}", Op::TimestampAddDays), "TimestampAddDays");
1336        assert_eq!(format!("{:?}", Op::TimestampSubDays), "TimestampSubDays");
1337    }
1338
1339    #[test]
1340    fn test_op_debug_boolean_checks() {
1341        assert_eq!(format!("{:?}", Op::IsTrue), "IsTrue");
1342        assert_eq!(format!("{:?}", Op::IsNotTrue), "IsNotTrue");
1343        assert_eq!(format!("{:?}", Op::IsFalse), "IsFalse");
1344        assert_eq!(format!("{:?}", Op::IsNotFalse), "IsNotFalse");
1345        assert_eq!(format!("{:?}", Op::IsDistinctFrom), "IsDistinctFrom");
1346        assert_eq!(format!("{:?}", Op::IsNotDistinctFrom), "IsNotDistinctFrom");
1347    }
1348
1349    #[test]
1350    fn test_op_debug_sets() {
1351        use radixdb_core::ValueSet;
1352        let set = CompactArc::new(ValueSet::default());
1353        assert!(format!("{:?}", Op::InSet(set.clone(), false)).contains("InSet"));
1354        assert!(format!("{:?}", Op::NotInSet(set.clone(), true)).contains("NotInSet"));
1355        assert!(format!("{:?}", Op::InSetColumn(0, set, false)).contains("InSetColumn"));
1356    }
1357
1358    #[test]
1359    fn test_op_debug_like_glob() {
1360        let pattern = Arc::new(CompiledPattern::compile("test%", false).unwrap());
1361        assert!(format!("{:?}", Op::Like(pattern.clone(), false)).contains("Like"));
1362        assert!(format!("{:?}", Op::Glob(pattern.clone())).contains("Glob"));
1363        assert!(format!("{:?}", Op::LikeColumn(0, pattern, false)).contains("LikeColumn"));
1364    }
1365}