llkv_sql/
sql_engine.rs

1use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
2use std::cell::RefCell;
3use std::collections::VecDeque;
4use std::convert::TryFrom;
5use std::sync::{
6    Arc, OnceLock,
7    atomic::{AtomicBool, Ordering as AtomicOrdering},
8};
9
10use crate::SqlResult;
11use crate::SqlValue;
12use arrow::array::{Array, ArrayRef, UInt32Array};
13use arrow::compute::{concat_batches, take};
14use arrow::datatypes::{DataType, Field, Schema};
15use arrow::record_batch::RecordBatch;
16use arrow::row::{RowConverter, SortField};
17
18use llkv_executor::{SelectExecution, push_query_label};
19use llkv_expr::literal::Literal;
20use llkv_plan::validation::{
21    ensure_known_columns_case_insensitive, ensure_non_empty, ensure_unique_case_insensitive,
22};
23use llkv_plan::{SubqueryCorrelatedColumnTracker, SubqueryCorrelatedTracker, TransformFrame};
24use llkv_result::Error;
25use llkv_runtime::TEMPORARY_NAMESPACE_ID;
26use llkv_runtime::{
27    AggregateExpr, AssignmentValue, ColumnAssignment, CreateIndexPlan, CreateTablePlan,
28    CreateTableSource, CreateViewPlan, DeletePlan, ForeignKeyAction, ForeignKeySpec,
29    IndexColumnPlan, InsertConflictAction, InsertPlan, InsertSource, MultiColumnUniqueSpec,
30    OrderByPlan, OrderSortType, OrderTarget, PlanColumnSpec, PlanStatement, PlanValue, ReindexPlan,
31    RenameTablePlan, RuntimeContext, RuntimeEngine, RuntimeSession, RuntimeStatementResult,
32    SelectPlan, SelectProjection, TruncatePlan, UpdatePlan, extract_rows_from_range,
33};
34use llkv_storage::pager::{BoxedPager, Pager};
35use llkv_table::catalog::{ColumnResolution, IdentifierContext, IdentifierResolver};
36use llkv_table::{CatalogDdl, TriggerEventMeta, TriggerTimingMeta};
37use regex::Regex;
38use simd_r_drive_entry_handle::EntryHandle;
39use sqlparser::ast::{
40    AlterColumnOperation, AlterTableOperation, Assignment, AssignmentTarget, BeginTransactionKind,
41    BinaryOperator, ColumnOption, ColumnOptionDef, ConstraintCharacteristics, CreateTrigger,
42    DataType as SqlDataType, Delete, Distinct, DropTrigger, ExceptionWhen, Expr as SqlExpr,
43    FromTable, FunctionArg, FunctionArgExpr, FunctionArguments, GroupByExpr, Ident, JoinConstraint,
44    JoinOperator, LimitClause, NullsDistinctOption, ObjectName, ObjectNamePart, ObjectType,
45    OrderBy, OrderByKind, Query, ReferentialAction, SchemaName, Select, SelectItem,
46    SelectItemQualifiedWildcardKind, Set, SetExpr, SetOperator, SetQuantifier, SqlOption,
47    Statement, TableConstraint, TableFactor, TableObject, TableWithJoins, TransactionMode,
48    TransactionModifier, TriggerEvent, TriggerObject, TriggerPeriod, UnaryOperator,
49    UpdateTableFromKind, VacuumStatement, Value, ValueWithSpan,
50};
51use sqlparser::dialect::GenericDialect;
52use sqlparser::parser::Parser;
53use sqlparser::tokenizer::Span;
54
55type SqlPager = BoxedPager;
56type SqlRuntimePager = SqlPager;
57type SqlStatementResult = RuntimeStatementResult<SqlPager>;
58type SqlContext = RuntimeContext<SqlPager>;
59type SqlSession = RuntimeSession;
60type P = SqlRuntimePager;
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum StatementExpectation {
64    Ok,
65    Error,
66    Count(u64),
67}
68
69thread_local! {
70    static PENDING_STATEMENT_EXPECTATIONS: RefCell<VecDeque<StatementExpectation>> = const {
71        RefCell::new(VecDeque::new())
72    };
73}
74
75pub fn register_statement_expectation(expectation: StatementExpectation) {
76    PENDING_STATEMENT_EXPECTATIONS.with(|queue| {
77        queue.borrow_mut().push_back(expectation);
78    });
79}
80
81pub fn clear_pending_statement_expectations() {
82    PENDING_STATEMENT_EXPECTATIONS.with(|queue| {
83        queue.borrow_mut().clear();
84    });
85}
86
87fn next_statement_expectation() -> StatementExpectation {
88    PENDING_STATEMENT_EXPECTATIONS
89        .with(|queue| queue.borrow_mut().pop_front())
90        .unwrap_or(StatementExpectation::Ok)
91}
92
93// TODO: Extract to constants.rs
94// TODO: Rename to SQL_PARSER_RECURSION_LIMIT
95/// Maximum recursion depth for SQL parser.
96///
97/// The default in sqlparser is 50, which can be exceeded by deeply nested queries
98/// (e.g., SQLite test suite). This value allows for more complex expressions while
99/// still preventing stack overflows.
100const PARSER_RECURSION_LIMIT: usize = 200;
101
102trait ScalarSubqueryResolver {
103    fn handle_scalar_subquery(
104        &mut self,
105        subquery: &Query,
106        resolver: &IdentifierResolver<'_>,
107        context: &IdentifierContext,
108        outer_scopes: &[IdentifierContext],
109    ) -> SqlResult<llkv_expr::expr::ScalarExpr<String>>;
110}
111
112/// Helper trait for requesting placeholders directly from catalog resolutions.
113trait SubqueryCorrelatedTrackerExt {
114    fn placeholder_for_resolution(
115        &mut self,
116        resolution: &llkv_table::catalog::ColumnResolution,
117    ) -> Option<String>;
118}
119
120impl SubqueryCorrelatedTrackerExt for SubqueryCorrelatedTracker<'_> {
121    fn placeholder_for_resolution(
122        &mut self,
123        resolution: &llkv_table::catalog::ColumnResolution,
124    ) -> Option<String> {
125        self.placeholder_for_column_path(resolution.column(), resolution.field_path())
126    }
127}
128
129/// Convenience extension so optional tracker references can be reborrowed without
130/// repeating `as_mut` callers across translation helpers.
131trait SubqueryCorrelatedTrackerOptionExt {
132    fn reborrow(&mut self) -> Option<&mut SubqueryCorrelatedColumnTracker>;
133}
134
135impl SubqueryCorrelatedTrackerOptionExt for Option<&mut SubqueryCorrelatedColumnTracker> {
136    fn reborrow(&mut self) -> Option<&mut SubqueryCorrelatedColumnTracker> {
137        self.as_mut().map(|tracker| &mut **tracker)
138    }
139}
140
141/// SQL execution engine built on top of the LLKV runtime.
142///
143/// # Examples
144///
145/// ```
146/// use std::sync::Arc;
147///
148/// use arrow::array::StringArray;
149/// use llkv_sql::{RuntimeStatementResult, SqlEngine};
150/// use llkv_storage::pager::MemPager;
151///
152/// let engine = SqlEngine::new(Arc::new(MemPager::default()));
153///
154/// let setup = r#"
155///     CREATE TABLE users (id INT PRIMARY KEY, name TEXT);
156///     INSERT INTO users (id, name) VALUES (1, 'Ada');
157/// "#;
158///
159/// let results = engine.execute(setup).unwrap();
160/// assert_eq!(results.len(), 2);
161///
162/// assert!(matches!(
163///     results[0],
164///     RuntimeStatementResult::CreateTable { ref table_name } if table_name == "users"
165/// ));
166/// assert!(matches!(
167///     results[1],
168///     RuntimeStatementResult::Insert { rows_inserted, .. } if rows_inserted == 1
169/// ));
170///
171/// let batches = engine.sql("SELECT id, name FROM users ORDER BY id;").unwrap();
172/// assert_eq!(batches.len(), 1);
173///
174/// let batch = &batches[0];
175/// assert_eq!(batch.num_rows(), 1);
176/// assert_eq!(batch.schema().field(1).name(), "name");
177///
178/// let names = batch
179///     .column(1)
180///     .as_any()
181///     .downcast_ref::<StringArray>()
182///     .unwrap();
183///
184/// assert_eq!(names.value(0), "Ada");
185/// ```
186/// Maximum number of literal `VALUES` rows to accumulate before forcing a flush.
187///
188/// This keeps memory usage predictable when ingesting massive SQL scripts while still
189/// providing large batched inserts for throughput.
190const MAX_BUFFERED_INSERT_ROWS: usize = 8192;
191
192/// Accumulates literal `INSERT` payloads so multiple statements can be flushed together.
193///
194/// Each buffered statement tracks its individual row count while sharing a single literal
195/// payload vector. When the buffer flushes we can emit one `RuntimeStatementResult::Insert`
196/// per original statement without re-planning intermediate work.
197struct InsertBuffer {
198    table_name: String,
199    columns: Vec<String>,
200    /// Conflict resolution action
201    on_conflict: InsertConflictAction,
202    /// Total literal rows gathered so far (sums `statement_row_counts`).
203    total_rows: usize,
204    /// Row counts for each original INSERT statement so we can emit per-statement results.
205    statement_row_counts: Vec<usize>,
206    /// Literal row payloads in execution order.
207    rows: Vec<Vec<PlanValue>>,
208}
209
210impl InsertBuffer {
211    fn new(
212        table_name: String,
213        columns: Vec<String>,
214        rows: Vec<Vec<PlanValue>>,
215        on_conflict: InsertConflictAction,
216    ) -> Self {
217        let row_count = rows.len();
218        Self {
219            table_name,
220            columns,
221            on_conflict,
222            total_rows: row_count,
223            statement_row_counts: vec![row_count],
224            rows,
225        }
226    }
227
228    fn can_accept(
229        &self,
230        table_name: &str,
231        columns: &[String],
232        on_conflict: InsertConflictAction,
233    ) -> bool {
234        self.table_name == table_name && self.columns == columns && self.on_conflict == on_conflict
235    }
236
237    fn push_statement(&mut self, rows: Vec<Vec<PlanValue>>) {
238        let row_count = rows.len();
239        self.total_rows += row_count;
240        self.statement_row_counts.push(row_count);
241        self.rows.extend(rows);
242    }
243
244    fn should_flush(&self) -> bool {
245        self.total_rows >= MAX_BUFFERED_INSERT_ROWS
246    }
247}
248
249/// Describes how a parsed `INSERT` should flow through the execution pipeline after we
250/// canonicalize the AST.
251///
252/// Literal `VALUES` payloads (including constant folds such as `SELECT 1`) are rewritten into
253/// [`PlanValue`] rows so they can be stitched together with other buffered statements before we
254/// hit the planner. Non-literal sources stay as full [`InsertPlan`]s and execute immediately.
255enum PreparedInsert {
256    Values {
257        table_name: String,
258        columns: Vec<String>,
259        rows: Vec<Vec<PlanValue>>,
260        on_conflict: InsertConflictAction,
261    },
262    Immediate(InsertPlan),
263}
264
265/// Return value from [`SqlEngine::buffer_insert`], exposing any buffered flush results along with
266/// the row-count placeholder for the currently processed statement.
267struct BufferedInsertResult {
268    flushed: Vec<SqlStatementResult>,
269    current: Option<SqlStatementResult>,
270}
271
272pub struct SqlEngine {
273    engine: RuntimeEngine,
274    default_nulls_first: AtomicBool,
275    /// Buffer for batching INSERTs across execute() calls for massive performance gains.
276    insert_buffer: RefCell<Option<InsertBuffer>>,
277    /// Tracks whether cross-statement INSERT buffering is enabled for this engine instance.
278    ///
279    /// Batch mode is disabled by default so unit tests and non-bulk ingest callers observe the
280    /// runtime's native per-statement semantics. Long-running workloads (for example, the SLT
281    /// harness) can opt in via [`SqlEngine::set_insert_buffering`] to trade immediate visibility
282    /// for much lower planning overhead.
283    insert_buffering_enabled: AtomicBool,
284}
285
286const DROPPED_TABLE_TRANSACTION_ERR: &str = "another transaction has dropped this table";
287
288impl Drop for SqlEngine {
289    fn drop(&mut self) {
290        // Flush remaining INSERTs when engine is dropped
291        if let Err(e) = self.flush_buffer_results() {
292            tracing::warn!("Failed to flush INSERT buffer on drop: {:?}", e);
293        }
294    }
295}
296
297impl Clone for SqlEngine {
298    fn clone(&self) -> Self {
299        tracing::warn!(
300            "[SQL_ENGINE] SqlEngine::clone() called - will create new Engine with new session!"
301        );
302        // Create a new session from the same context
303        Self {
304            engine: self.engine.clone(),
305            default_nulls_first: AtomicBool::new(
306                self.default_nulls_first.load(AtomicOrdering::Relaxed),
307            ),
308            insert_buffer: RefCell::new(None),
309            insert_buffering_enabled: AtomicBool::new(
310                self.insert_buffering_enabled.load(AtomicOrdering::Relaxed),
311            ),
312        }
313    }
314}
315
316#[allow(dead_code)]
317impl SqlEngine {
318    fn from_runtime_engine(
319        engine: RuntimeEngine,
320        default_nulls_first: bool,
321        insert_buffering_enabled: bool,
322    ) -> Self {
323        Self {
324            engine,
325            default_nulls_first: AtomicBool::new(default_nulls_first),
326            insert_buffer: RefCell::new(None),
327            insert_buffering_enabled: AtomicBool::new(insert_buffering_enabled),
328        }
329    }
330
331    fn map_table_error(table_name: &str, err: Error) -> Error {
332        match err {
333            Error::NotFound => Self::table_not_found_error(table_name),
334            Error::InvalidArgumentError(msg) if msg.contains("unknown table") => {
335                Self::table_not_found_error(table_name)
336            }
337            other => other,
338        }
339    }
340
341    fn table_not_found_error(table_name: &str) -> Error {
342        Error::CatalogError(format!(
343            "Catalog Error: Table '{table_name}' does not exist"
344        ))
345    }
346
347    fn is_table_missing_error(err: &Error) -> bool {
348        match err {
349            Error::NotFound => true,
350            Error::CatalogError(msg) => {
351                msg.contains("Catalog Error: Table") || msg.contains("unknown table")
352            }
353            Error::InvalidArgumentError(msg) => {
354                msg.contains("Catalog Error: Table") || msg.contains("unknown table")
355            }
356            _ => false,
357        }
358    }
359
360    fn execute_plan_statement(&self, statement: PlanStatement) -> SqlResult<SqlStatementResult> {
361        // Don't apply table error mapping to CREATE VIEW or DROP VIEW statements
362        // because the "table" name is the view being created/dropped, not a referenced table.
363        // Any "unknown table" errors from CREATE VIEW are about tables referenced in the SELECT.
364        let should_map_error = !matches!(
365            &statement,
366            PlanStatement::CreateView(_) | PlanStatement::DropView(_)
367        );
368
369        let table = if should_map_error {
370            llkv_runtime::statement_table_name(&statement).map(str::to_string)
371        } else {
372            None
373        };
374
375        self.engine.execute_statement(statement).map_err(|err| {
376            if let Some(table_name) = table {
377                Self::map_table_error(&table_name, err)
378            } else {
379                err
380            }
381        })
382    }
383
384    /// Construct a new engine backed by the provided pager with insert buffering disabled.
385    ///
386    /// Callers that intend to stream large amounts of literal `INSERT ... VALUES` input can
387    /// enable batching later using [`SqlEngine::set_insert_buffering`].
388    pub fn new<Pg>(pager: Arc<Pg>) -> Self
389    where
390        Pg: Pager<Blob = EntryHandle> + Send + Sync + 'static,
391    {
392        let engine = RuntimeEngine::new(pager);
393        Self::from_runtime_engine(engine, false, false)
394    }
395
396    /// Preprocess SQL to handle qualified names in EXCLUDE clauses
397    /// Converts EXCLUDE (schema.table.col) to EXCLUDE ("schema.table.col")
398    /// Preprocess CREATE TYPE to CREATE DOMAIN for sqlparser compatibility.
399    ///
400    /// DuckDB uses `CREATE TYPE name AS basetype` for type aliases, but sqlparser
401    /// only supports the SQL standard `CREATE DOMAIN name AS basetype` syntax.
402    /// This method converts the DuckDB syntax to the standard syntax.
403    fn preprocess_create_type_syntax(sql: &str) -> String {
404        static CREATE_TYPE_REGEX: OnceLock<Regex> = OnceLock::new();
405        static DROP_TYPE_REGEX: OnceLock<Regex> = OnceLock::new();
406
407        // Match: CREATE TYPE name AS datatype
408        let create_re = CREATE_TYPE_REGEX.get_or_init(|| {
409            Regex::new(r"(?i)\bCREATE\s+TYPE\s+").expect("valid CREATE TYPE regex")
410        });
411
412        // Match: DROP TYPE [IF EXISTS] name
413        let drop_re = DROP_TYPE_REGEX
414            .get_or_init(|| Regex::new(r"(?i)\bDROP\s+TYPE\s+").expect("valid DROP TYPE regex"));
415
416        // First replace CREATE TYPE with CREATE DOMAIN
417        let sql = create_re.replace_all(sql, "CREATE DOMAIN ").to_string();
418
419        // Then replace DROP TYPE with DROP DOMAIN
420        drop_re.replace_all(&sql, "DROP DOMAIN ").to_string()
421    }
422
423    fn preprocess_exclude_syntax(sql: &str) -> String {
424        static EXCLUDE_REGEX: OnceLock<Regex> = OnceLock::new();
425
426        // Pattern to match EXCLUDE followed by qualified identifiers
427        // Matches: EXCLUDE (identifier.identifier.identifier)
428        let re = EXCLUDE_REGEX.get_or_init(|| {
429            Regex::new(
430                r"(?i)EXCLUDE\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\s*\)",
431            )
432            .expect("valid EXCLUDE qualifier regex")
433        });
434
435        re.replace_all(sql, |caps: &regex::Captures| {
436            let qualified_name = &caps[1];
437            format!("EXCLUDE (\"{}\")", qualified_name)
438        })
439        .to_string()
440    }
441
442    /// Preprocess SQL to remove trailing commas in VALUES clauses.
443    /// DuckDB allows trailing commas like VALUES ('v2',) but sqlparser does not.
444    fn preprocess_trailing_commas_in_values(sql: &str) -> String {
445        static TRAILING_COMMA_REGEX: OnceLock<Regex> = OnceLock::new();
446
447        // Pattern to match trailing comma before closing paren in VALUES
448        // Matches: , followed by optional whitespace and )
449        let re = TRAILING_COMMA_REGEX
450            .get_or_init(|| Regex::new(r",(\s*)\)").expect("valid trailing comma regex"));
451
452        re.replace_all(sql, "$1)").to_string()
453    }
454
455    /// Preprocess SQL to handle empty IN lists.
456    ///
457    /// SQLite permits `expr IN ()` and `expr NOT IN ()` as degenerate forms of IN expressions.
458    /// The sqlparser library rejects these, so we convert them to constant boolean expressions:
459    /// `expr IN ()` becomes `expr = NULL AND 0 = 1` (always false), and `expr NOT IN ()`
460    /// becomes `expr = NULL OR 1 = 1` (always true). The `expr = NULL` component ensures the
461    /// original expression is still evaluated (in case it has side effects), while the constant
462    /// comparison determines the final result.
463    fn preprocess_empty_in_lists(sql: &str) -> String {
464        static EMPTY_IN_REGEX: OnceLock<Regex> = OnceLock::new();
465
466        // Match: expression NOT IN () or expression IN ()
467        // Matches: parenthesized expressions, quoted strings, hex literals, identifiers, or numbers
468        let re = EMPTY_IN_REGEX.get_or_init(|| {
469            Regex::new(r"(?i)(\([^)]*\)|x'[0-9a-fA-F]*'|'(?:[^']|'')*'|[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*|\d+(?:\.\d+)?)\s+(NOT\s+)?IN\s*\(\s*\)")
470                .expect("valid empty IN regex")
471        });
472
473        re.replace_all(sql, |caps: &regex::Captures| {
474            let expr = &caps[1];
475            if caps.get(2).is_some() {
476                // expr NOT IN () → always true (but still evaluate expr)
477                format!("({} = NULL OR 1 = 1)", expr)
478            } else {
479                // expr IN () → always false (but still evaluate expr)
480                format!("({} = NULL AND 0 = 1)", expr)
481            }
482        })
483        .to_string()
484    }
485
486    /// Strip SQLite index hints (INDEXED BY, NOT INDEXED) from table references.
487    ///
488    /// SQLite supports index hints like `FROM table INDEXED BY index_name` or `FROM table NOT INDEXED`.
489    /// These are query optimization hints that guide the query planner's index selection.
490    /// Since sqlparser doesn't support this syntax and our planner makes its own index decisions,
491    /// we strip these hints during preprocessing for compatibility with SQLite SQL Logic Tests.
492    fn preprocess_index_hints(sql: &str) -> String {
493        static INDEX_HINT_REGEX: OnceLock<Regex> = OnceLock::new();
494
495        // Match: INDEXED BY index_name or NOT INDEXED
496        // Pattern captures the table reference and removes the index hint
497        let re = INDEX_HINT_REGEX.get_or_init(|| {
498            Regex::new(r"(?i)\s+(INDEXED\s+BY\s+[a-zA-Z_][a-zA-Z0-9_]*|NOT\s+INDEXED)\b")
499                .expect("valid index hint regex")
500        });
501
502        re.replace_all(sql, "").to_string()
503    }
504
505    /// Convert SQLite standalone REINDEX to VACUUM REINDEX for sqlparser.
506    ///
507    /// SQLite supports `REINDEX index_name` as a standalone statement, but sqlparser
508    /// only recognizes REINDEX as part of the VACUUM statement syntax. This preprocessor
509    /// converts the SQLite form to the VACUUM REINDEX form that sqlparser can parse.
510    fn preprocess_reindex_syntax(sql: &str) -> String {
511        static REINDEX_REGEX: OnceLock<Regex> = OnceLock::new();
512
513        // Match: REINDEX followed by an identifier (index name)
514        // Captures the full statement to replace it with VACUUM REINDEX
515        let re = REINDEX_REGEX.get_or_init(|| {
516            Regex::new(r"(?i)\bREINDEX\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\b")
517                .expect("valid reindex regex")
518        });
519
520        re.replace_all(sql, "VACUUM REINDEX $1").to_string()
521    }
522
523    /// Normalize SQLite trigger shorthand so sqlparser accepts the syntax.
524    ///
525    /// SQLite allows omitting the trigger timing (defaulting to AFTER) and the
526    /// `FOR EACH ROW` clause (defaulting to row-level triggers). sqlparser
527    /// requires both pieces to be explicit, so we inject them before parsing.
528    ///
529    /// # TODO
530    ///
531    /// This is a temporary workaround. The proper fix is to extend sqlparser's
532    /// `SQLiteDialect::parse_statement` to handle CREATE TRIGGER with optional
533    /// timing/FOR EACH ROW clauses, matching SQLite's actual grammar. That would
534    /// eliminate this fragile regex preprocessing entirely.
535    fn preprocess_sqlite_trigger_shorthand(sql: &str) -> String {
536        static IDENT_PATTERN: OnceLock<String> = OnceLock::new();
537        static COLUMN_IDENT_PATTERN: OnceLock<String> = OnceLock::new();
538        static TIMING_REGEX: OnceLock<Regex> = OnceLock::new();
539        static FOR_EACH_BEGIN_REGEX: OnceLock<Regex> = OnceLock::new();
540        static FOR_EACH_WHEN_REGEX: OnceLock<Regex> = OnceLock::new();
541
542        IDENT_PATTERN.get_or_init(|| {
543            // Matches optional dotted identifiers with standard or quoted segments.
544            r#"(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[a-zA-Z_][a-zA-Z0-9_]*)(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*"#
545                .to_string()
546        });
547        COLUMN_IDENT_PATTERN
548            .get_or_init(|| r#"(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[a-zA-Z_][a-zA-Z0-9_]*)"#.to_string());
549
550        let timing_re = TIMING_REGEX.get_or_init(|| {
551            let event = format!(
552                "UPDATE(?:\\s+OF\\s+{col}(?:\\s*,\\s*{col})*)?|DELETE|INSERT",
553                col = COLUMN_IDENT_PATTERN
554                    .get()
555                    .expect("column ident pattern initialized")
556            );
557            let pattern = format!(
558                r"(?ix)(?P<head>CREATE\s+TRIGGER\s+(?:IF\s+NOT\s+EXISTS\s+)?{ident})\s+(?P<event>{event})\s+ON",
559                ident = IDENT_PATTERN
560                    .get()
561                    .expect("ident pattern initialized"),
562                event = event
563            );
564            Regex::new(&pattern).expect("valid trigger timing regex")
565        });
566
567        let with_timing = timing_re
568            .replace_all(sql, |caps: &regex::Captures| {
569                let head = caps.name("head").unwrap().as_str();
570                let event = caps.name("event").unwrap().as_str().trim();
571                format!("{head} AFTER {event} ON")
572            })
573            .to_string();
574
575        let for_each_begin_re = FOR_EACH_BEGIN_REGEX.get_or_init(|| {
576            let pattern = format!(
577                r"(?ix)(?P<prefix>ON\s+{ident})\s+(?P<keyword>BEGIN\b)",
578                ident = IDENT_PATTERN.get().expect("ident pattern initialized"),
579            );
580            Regex::new(&pattern).expect("valid trigger FOR EACH BEGIN regex")
581        });
582
583        let with_for_each_begin = for_each_begin_re
584            .replace_all(&with_timing, |caps: &regex::Captures| {
585                let prefix = caps.name("prefix").unwrap().as_str();
586                let keyword = caps.name("keyword").unwrap().as_str();
587                format!("{prefix} FOR EACH ROW {keyword}")
588            })
589            .to_string();
590
591        let for_each_when_re = FOR_EACH_WHEN_REGEX.get_or_init(|| {
592            let pattern = format!(
593                r"(?ix)(?P<prefix>ON\s+{ident})\s+(?P<keyword>WHEN\b)",
594                ident = IDENT_PATTERN.get().expect("ident pattern initialized"),
595            );
596            Regex::new(&pattern).expect("valid trigger FOR EACH WHEN regex")
597        });
598
599        for_each_when_re
600            .replace_all(&with_for_each_begin, |caps: &regex::Captures| {
601                let prefix = caps.name("prefix").unwrap().as_str();
602                let keyword = caps.name("keyword").unwrap().as_str();
603                format!("{prefix} FOR EACH ROW {keyword}")
604            })
605            .to_string()
606    }
607
608    /// Preprocess SQL to convert bare table names in IN clauses to subqueries.
609    ///
610    /// SQLite allows `expr IN tablename` as shorthand for `expr IN (SELECT * FROM tablename)`.
611    /// The sqlparser library requires parentheses, so we convert the shorthand form.
612    fn preprocess_bare_table_in_clauses(sql: &str) -> String {
613        static BARE_TABLE_IN_REGEX: OnceLock<Regex> = OnceLock::new();
614
615        // Match: [NOT] IN identifier followed by whitespace/newline/end or specific punctuation
616        // Avoid matching "IN (" which is already a valid subquery
617        let re = BARE_TABLE_IN_REGEX.get_or_init(|| {
618            Regex::new(r"(?i)\b(NOT\s+)?IN\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)(\s|$|;|,|\))")
619                .expect("valid bare table IN regex")
620        });
621
622        re.replace_all(sql, |caps: &regex::Captures| {
623            let table_name = &caps[2];
624            let trailing = &caps[3];
625            if let Some(not_keyword) = caps.get(1) {
626                format!(
627                    "{}IN (SELECT * FROM {}){}",
628                    not_keyword.as_str(),
629                    table_name,
630                    trailing
631                )
632            } else {
633                format!("IN (SELECT * FROM {}){}", table_name, trailing)
634            }
635        })
636        .to_string()
637    }
638
639    pub(crate) fn context_arc(&self) -> Arc<SqlContext> {
640        self.engine.context()
641    }
642
643    pub fn with_context(context: Arc<SqlContext>, default_nulls_first: bool) -> Self {
644        Self::from_runtime_engine(
645            RuntimeEngine::from_context(context),
646            default_nulls_first,
647            false,
648        )
649    }
650
651    /// Toggle literal `INSERT` buffering for the engine.
652    ///
653    /// When enabled, consecutive `INSERT ... VALUES` statements that target the same table and
654    /// column list are accumulated and flushed together, dramatically lowering planning and
655    /// execution overhead for workloads that stream tens of thousands of literal inserts.
656    /// Disabling buffering reverts to SQLite-style immediate execution and is appropriate for
657    /// unit tests or workloads that rely on per-statement side effects (errors, triggers,
658    /// constraint violations) happening synchronously.
659    ///
660    /// Calling this method with `false` forces any pending batched rows to flush before
661    /// returning, guaranteeing that subsequent reads observe the latest state.
662    pub fn set_insert_buffering(&self, enabled: bool) -> SqlResult<()> {
663        if !enabled {
664            let _ = self.flush_buffer_results()?;
665        }
666        self.insert_buffering_enabled
667            .store(enabled, AtomicOrdering::Relaxed);
668        Ok(())
669    }
670
671    #[cfg(test)]
672    fn default_nulls_first_for_tests(&self) -> bool {
673        self.default_nulls_first.load(AtomicOrdering::Relaxed)
674    }
675
676    fn has_active_transaction(&self) -> bool {
677        self.engine.session().has_active_transaction()
678    }
679
680    /// Get a reference to the underlying session (for advanced use like error handling in test harnesses).
681    pub fn session(&self) -> &SqlSession {
682        self.engine.session()
683    }
684
685    /// Execute one or more SQL statements and return their raw [`RuntimeStatementResult`]s.
686    ///
687    /// This method is the general-purpose entry point for running SQL against the engine when
688    /// you need to mix statement types (e.g. `CREATE TABLE`, `INSERT`, `UPDATE`, `SELECT`) or
689    /// when you care about per-statement status information. Statements are executed in the order
690    /// they appear in the input string, and the results vector mirrors that ordering.
691    ///
692    /// For ad-hoc read queries where you only care about the resulting Arrow [`RecordBatch`]es,
693    /// prefer [`SqlEngine::sql`], which enforces a single `SELECT` statement and collects its
694    /// output for you. `execute` remains the right tool for schema migrations, transactional
695    /// scripts, or workflows that need to inspect the specific runtime response for each
696    /// statement.
697    pub fn execute(&self, sql: &str) -> SqlResult<Vec<SqlStatementResult>> {
698        tracing::trace!("DEBUG SQL execute: {}", sql);
699
700        // Preprocess SQL
701        let processed_sql = Self::preprocess_create_type_syntax(sql);
702        let processed_sql = Self::preprocess_exclude_syntax(&processed_sql);
703        let processed_sql = Self::preprocess_trailing_commas_in_values(&processed_sql);
704        let processed_sql = Self::preprocess_bare_table_in_clauses(&processed_sql);
705        let processed_sql = Self::preprocess_empty_in_lists(&processed_sql);
706        let processed_sql = Self::preprocess_index_hints(&processed_sql);
707        let processed_sql = Self::preprocess_reindex_syntax(&processed_sql);
708
709        let dialect = GenericDialect {};
710        let statements = match parse_sql_with_recursion_limit(&dialect, &processed_sql) {
711            Ok(stmts) => stmts,
712            Err(parse_err) => {
713                // SQLite allows omitting BEFORE/AFTER and FOR EACH ROW in CREATE TRIGGER.
714                // If parsing fails and the SQL contains CREATE TRIGGER, attempt to expand
715                // the shorthand form and retry. This is a workaround until sqlparser's
716                // SQLite dialect properly supports the abbreviated syntax.
717                if processed_sql.to_uppercase().contains("CREATE TRIGGER") {
718                    let expanded = Self::preprocess_sqlite_trigger_shorthand(&processed_sql);
719                    parse_sql_with_recursion_limit(&dialect, &expanded).map_err(|_| {
720                        Error::InvalidArgumentError(format!("failed to parse SQL: {parse_err}"))
721                    })?
722                } else {
723                    return Err(Error::InvalidArgumentError(format!(
724                        "failed to parse SQL: {parse_err}"
725                    )));
726                }
727            }
728        };
729        let mut results = Vec::with_capacity(statements.len());
730        for statement in statements.iter() {
731            let statement_expectation = next_statement_expectation();
732            match statement {
733                Statement::Insert(insert) => {
734                    let mut outcome = self.buffer_insert(insert.clone(), statement_expectation)?;
735                    if let Some(current) = outcome.current.take() {
736                        results.push(current);
737                    }
738                    results.append(&mut outcome.flushed);
739                }
740                Statement::StartTransaction { .. }
741                | Statement::Commit { .. }
742                | Statement::Rollback { .. } => {
743                    // Flush before transaction boundaries
744                    let mut flushed = self.flush_buffer_results()?;
745                    let current = self.execute_statement(statement.clone())?;
746                    results.push(current);
747                    results.append(&mut flushed);
748                }
749                _ => {
750                    // Flush before any non-INSERT
751                    let mut flushed = self.flush_buffer_results()?;
752                    let current = self.execute_statement(statement.clone())?;
753                    results.push(current);
754                    results.append(&mut flushed);
755                }
756            }
757        }
758
759        Ok(results)
760    }
761
762    /// Flush any buffered literal `INSERT` statements and return their per-statement results.
763    ///
764    /// Workloads that stream many INSERT statements without interleaving reads can invoke this
765    /// to force persistence without waiting for the next non-INSERT statement or the engine
766    /// drop hook.
767    pub fn flush_pending_inserts(&self) -> SqlResult<Vec<SqlStatementResult>> {
768        self.flush_buffer_results()
769    }
770
771    /// Buffer an `INSERT` statement when batching is enabled, or execute it immediately
772    /// otherwise.
773    ///
774    /// The return value includes any flushed results (when a batch boundary is crossed) as well
775    /// as the per-statement placeholder that preserves the original `RuntimeStatementResult`
776    /// ordering expected by callers like the SLT harness.
777    fn buffer_insert(
778        &self,
779        insert: sqlparser::ast::Insert,
780        expectation: StatementExpectation,
781    ) -> SqlResult<BufferedInsertResult> {
782        // Expectations serve two purposes: (a) ensure we surface synchronous errors when the
783        // SLT harness anticipates them, and (b) force a flush when the harness is validating the
784        // rows-affected count. In both situations we bypass the buffer entirely so the runtime
785        // executes the statement immediately.
786        let execute_immediately = matches!(
787            expectation,
788            StatementExpectation::Error | StatementExpectation::Count(_)
789        );
790        if execute_immediately {
791            let flushed = self.flush_buffer_results()?;
792            let current = self.handle_insert(insert)?;
793            return Ok(BufferedInsertResult {
794                flushed,
795                current: Some(current),
796            });
797        }
798
799        // When buffering is disabled for this engine (the default for unit tests and most
800        // production callers), short-circuit to immediate execution so callers see the results
801        // they expect without having to register additional expectations.
802        if !self.insert_buffering_enabled.load(AtomicOrdering::Relaxed) {
803            let flushed = self.flush_buffer_results()?;
804            let current = self.handle_insert(insert)?;
805            return Ok(BufferedInsertResult {
806                flushed,
807                current: Some(current),
808            });
809        }
810
811        let prepared = self.prepare_insert(insert)?;
812        match prepared {
813            PreparedInsert::Values {
814                table_name,
815                columns,
816                rows,
817                on_conflict,
818            } => {
819                let mut flushed = Vec::new();
820                let statement_rows = rows.len();
821                let mut buf = self.insert_buffer.borrow_mut();
822                match buf.as_mut() {
823                    Some(buffer) if buffer.can_accept(&table_name, &columns, on_conflict) => {
824                        buffer.push_statement(rows);
825                        if buffer.should_flush() {
826                            drop(buf);
827                            flushed = self.flush_buffer_results()?;
828                            return Ok(BufferedInsertResult {
829                                flushed,
830                                current: None,
831                            });
832                        }
833                        Ok(BufferedInsertResult {
834                            flushed,
835                            current: Some(RuntimeStatementResult::Insert {
836                                table_name,
837                                rows_inserted: statement_rows,
838                            }),
839                        })
840                    }
841                    Some(_) => {
842                        drop(buf);
843                        flushed = self.flush_buffer_results()?;
844                        let mut buf = self.insert_buffer.borrow_mut();
845                        *buf = Some(InsertBuffer::new(
846                            table_name.clone(),
847                            columns,
848                            rows,
849                            on_conflict,
850                        ));
851                        Ok(BufferedInsertResult {
852                            flushed,
853                            current: Some(RuntimeStatementResult::Insert {
854                                table_name,
855                                rows_inserted: statement_rows,
856                            }),
857                        })
858                    }
859                    None => {
860                        *buf = Some(InsertBuffer::new(
861                            table_name.clone(),
862                            columns,
863                            rows,
864                            on_conflict,
865                        ));
866                        Ok(BufferedInsertResult {
867                            flushed,
868                            current: Some(RuntimeStatementResult::Insert {
869                                table_name,
870                                rows_inserted: statement_rows,
871                            }),
872                        })
873                    }
874                }
875            }
876            PreparedInsert::Immediate(plan) => {
877                let flushed = self.flush_buffer_results()?;
878                let executed = self.execute_plan_statement(PlanStatement::Insert(plan))?;
879                Ok(BufferedInsertResult {
880                    flushed,
881                    current: Some(executed),
882                })
883            }
884        }
885    }
886
887    /// Flush buffered INSERTs, returning one result per original statement.
888    fn flush_buffer_results(&self) -> SqlResult<Vec<SqlStatementResult>> {
889        let mut buf = self.insert_buffer.borrow_mut();
890        let buffer = match buf.take() {
891            Some(b) => b,
892            None => return Ok(Vec::new()),
893        };
894        drop(buf);
895
896        let InsertBuffer {
897            table_name,
898            columns,
899            on_conflict,
900            total_rows,
901            statement_row_counts,
902            rows,
903        } = buffer;
904
905        if total_rows == 0 {
906            return Ok(Vec::new());
907        }
908
909        let plan = InsertPlan {
910            table: table_name.clone(),
911            columns,
912            source: InsertSource::Rows(rows),
913            on_conflict,
914        };
915
916        let executed = self.execute_plan_statement(PlanStatement::Insert(plan))?;
917        let inserted = match executed {
918            RuntimeStatementResult::Insert { rows_inserted, .. } => {
919                if rows_inserted != total_rows {
920                    tracing::warn!(
921                        "Buffered INSERT row count mismatch: expected {}, runtime inserted {}",
922                        total_rows,
923                        rows_inserted
924                    );
925                }
926                rows_inserted
927            }
928            other => {
929                return Err(Error::Internal(format!(
930                    "expected Insert result when flushing buffer, got {other:?}"
931                )));
932            }
933        };
934
935        let mut per_statement = Vec::with_capacity(statement_row_counts.len());
936        let mut assigned = 0usize;
937        for rows in statement_row_counts {
938            assigned += rows;
939            per_statement.push(RuntimeStatementResult::Insert {
940                table_name: table_name.clone(),
941                rows_inserted: rows,
942            });
943        }
944
945        if inserted != assigned {
946            tracing::warn!(
947                "Buffered INSERT per-statement totals ({}) do not match runtime ({}).",
948                assigned,
949                inserted
950            );
951        }
952
953        Ok(per_statement)
954    }
955
956    /// Canonicalizes an `INSERT` statement so buffered workloads can share literal payloads while
957    /// complex sources still execute eagerly.
958    ///
959    /// The translation enforces dialect constraints up front, rewrites `VALUES` clauses (and any
960    /// constant `SELECT` forms) into `PlanValue` rows, and returns them under
961    /// [`PreparedInsert::Values`] so [`Self::buffer_insert`] can append them to the rolling
962    /// batch. Statements whose payload must be evaluated at runtime fall back to a fully planned
963    /// [`InsertPlan`].
964    ///
965    /// # Errors
966    ///
967    /// Returns [`Error::InvalidArgumentError`] whenever the incoming AST uses syntactic forms we
968    /// do not currently support or when the literal payload is empty.
969    fn prepare_insert(&self, stmt: sqlparser::ast::Insert) -> SqlResult<PreparedInsert> {
970        let table_name_debug =
971            Self::table_name_from_insert(&stmt).unwrap_or_else(|_| "unknown".to_string());
972        tracing::trace!(
973            "DEBUG SQL prepare_insert called for table={}",
974            table_name_debug
975        );
976
977        if !self.engine.session().has_active_transaction()
978            && self.is_table_marked_dropped(&table_name_debug)?
979        {
980            return Err(Error::TransactionContextError(
981                DROPPED_TABLE_TRANSACTION_ERR.into(),
982            ));
983        }
984
985        // Extract conflict resolution action
986        use sqlparser::ast::SqliteOnConflict;
987        let on_conflict = if stmt.replace_into {
988            InsertConflictAction::Replace
989        } else if stmt.ignore {
990            InsertConflictAction::Ignore
991        } else if let Some(or_clause) = stmt.or {
992            match or_clause {
993                SqliteOnConflict::Replace => InsertConflictAction::Replace,
994                SqliteOnConflict::Ignore => InsertConflictAction::Ignore,
995                SqliteOnConflict::Abort => InsertConflictAction::Abort,
996                SqliteOnConflict::Fail => InsertConflictAction::Fail,
997                SqliteOnConflict::Rollback => InsertConflictAction::Rollback,
998            }
999        } else {
1000            InsertConflictAction::None
1001        };
1002
1003        if stmt.overwrite {
1004            return Err(Error::InvalidArgumentError(
1005                "INSERT OVERWRITE is not supported".into(),
1006            ));
1007        }
1008        if !stmt.assignments.is_empty() {
1009            return Err(Error::InvalidArgumentError(
1010                "INSERT ... SET is not supported".into(),
1011            ));
1012        }
1013        if stmt.partitioned.is_some() || !stmt.after_columns.is_empty() {
1014            return Err(Error::InvalidArgumentError(
1015                "partitioned INSERT is not supported".into(),
1016            ));
1017        }
1018        if stmt.returning.is_some() {
1019            return Err(Error::InvalidArgumentError(
1020                "INSERT ... RETURNING is not supported".into(),
1021            ));
1022        }
1023        if stmt.format_clause.is_some() || stmt.settings.is_some() {
1024            return Err(Error::InvalidArgumentError(
1025                "INSERT with FORMAT or SETTINGS is not supported".into(),
1026            ));
1027        }
1028
1029        let (display_name, _canonical_name) = match &stmt.table {
1030            TableObject::TableName(name) => canonical_object_name(name)?,
1031            _ => {
1032                return Err(Error::InvalidArgumentError(
1033                    "INSERT requires a plain table name".into(),
1034                ));
1035            }
1036        };
1037
1038        let columns: Vec<String> = stmt
1039            .columns
1040            .iter()
1041            .map(|ident| ident.value.clone())
1042            .collect();
1043
1044        let source_expr = stmt
1045            .source
1046            .as_ref()
1047            .ok_or_else(|| Error::InvalidArgumentError("INSERT requires a VALUES clause".into()))?;
1048        validate_simple_query(source_expr)?;
1049
1050        match source_expr.body.as_ref() {
1051            SetExpr::Values(values) => {
1052                if values.rows.is_empty() {
1053                    return Err(Error::InvalidArgumentError(
1054                        "INSERT VALUES list must contain at least one row".into(),
1055                    ));
1056                }
1057                let mut rows: Vec<Vec<PlanValue>> = Vec::with_capacity(values.rows.len());
1058                for row in &values.rows {
1059                    let mut converted = Vec::with_capacity(row.len());
1060                    for expr in row {
1061                        converted.push(PlanValue::from(SqlValue::try_from_expr(expr)?));
1062                    }
1063                    rows.push(converted);
1064                }
1065                Ok(PreparedInsert::Values {
1066                    table_name: display_name,
1067                    columns,
1068                    rows,
1069                    on_conflict,
1070                })
1071            }
1072            SetExpr::Select(select) => {
1073                if let Some(rows) = extract_constant_select_rows(select.as_ref())? {
1074                    return Ok(PreparedInsert::Values {
1075                        table_name: display_name,
1076                        columns,
1077                        rows,
1078                        on_conflict,
1079                    });
1080                }
1081                if let Some(range_rows) = extract_rows_from_range(select.as_ref())? {
1082                    return Ok(PreparedInsert::Values {
1083                        table_name: display_name,
1084                        columns,
1085                        rows: range_rows.into_rows(),
1086                        on_conflict,
1087                    });
1088                }
1089
1090                let select_plan = self.build_select_plan((**source_expr).clone())?;
1091                Ok(PreparedInsert::Immediate(InsertPlan {
1092                    table: display_name,
1093                    columns,
1094                    source: InsertSource::Select {
1095                        plan: Box::new(select_plan),
1096                    },
1097                    on_conflict,
1098                }))
1099            }
1100            _ => Err(Error::InvalidArgumentError(
1101                "unsupported INSERT source".into(),
1102            )),
1103        }
1104    }
1105
1106    /// Execute a single SELECT statement and return its results as Arrow [`RecordBatch`]es.
1107    ///
1108    /// The SQL passed to this method must contain exactly one statement, and that statement must
1109    /// be a `SELECT`. Statements that modify data (e.g. `INSERT`) should be executed up front
1110    /// using [`SqlEngine::execute`] before calling this helper.
1111    ///
1112    /// # Examples
1113    ///
1114    /// ```
1115    /// use std::sync::Arc;
1116    ///
1117    /// use arrow::array::StringArray;
1118    /// use llkv_sql::SqlEngine;
1119    /// use llkv_storage::pager::MemPager;
1120    ///
1121    /// let engine = SqlEngine::new(Arc::new(MemPager::default()));
1122    /// let _ = engine
1123    ///     .execute(
1124    ///         "CREATE TABLE users (id INT PRIMARY KEY, name TEXT);\n         \
1125    ///          INSERT INTO users (id, name) VALUES (1, 'Ada');",
1126    ///     )
1127    ///     .unwrap();
1128    ///
1129    /// let batches = engine.sql("SELECT id, name FROM users ORDER BY id;").unwrap();
1130    /// assert_eq!(batches.len(), 1);
1131    ///
1132    /// let batch = &batches[0];
1133    /// assert_eq!(batch.num_rows(), 1);
1134    ///
1135    /// let names = batch
1136    ///     .column(1)
1137    ///     .as_any()
1138    ///     .downcast_ref::<StringArray>()
1139    ///     .unwrap();
1140    /// assert_eq!(names.value(0), "Ada");
1141    /// ```
1142    pub fn sql(&self, sql: &str) -> SqlResult<Vec<RecordBatch>> {
1143        let mut results = self.execute(sql)?;
1144        if results.is_empty() {
1145            return Err(Error::InvalidArgumentError(
1146                "SqlEngine::sql expects a SELECT statement".into(),
1147            ));
1148        }
1149
1150        let primary = results.remove(0);
1151
1152        match primary {
1153            RuntimeStatementResult::Select { execution, .. } => execution.collect(),
1154            other => Err(Error::InvalidArgumentError(format!(
1155                "SqlEngine::sql requires a SELECT statement, got {other:?}",
1156            ))),
1157        }
1158    }
1159
1160    fn execute_statement(&self, statement: Statement) -> SqlResult<SqlStatementResult> {
1161        let statement_sql = statement.to_string();
1162        let _query_label_guard = push_query_label(statement_sql.clone());
1163        tracing::debug!("SQL execute_statement: {}", statement_sql.trim());
1164        tracing::trace!(
1165            "DEBUG SQL execute_statement: {:?}",
1166            match &statement {
1167                Statement::Insert(insert) =>
1168                    format!("Insert(table={:?})", Self::table_name_from_insert(insert)),
1169                Statement::Query(_) => "Query".to_string(),
1170                Statement::StartTransaction { .. } => "StartTransaction".to_string(),
1171                Statement::Commit { .. } => "Commit".to_string(),
1172                Statement::Rollback { .. } => "Rollback".to_string(),
1173                Statement::CreateTable(_) => "CreateTable".to_string(),
1174                Statement::Update { .. } => "Update".to_string(),
1175                Statement::Delete(_) => "Delete".to_string(),
1176                other => format!("Other({:?})", other),
1177            }
1178        );
1179        match statement {
1180            Statement::StartTransaction {
1181                modes,
1182                begin,
1183                transaction,
1184                modifier,
1185                statements,
1186                exception,
1187                has_end_keyword,
1188            } => self.handle_start_transaction(
1189                modes,
1190                begin,
1191                transaction,
1192                modifier,
1193                statements,
1194                exception,
1195                has_end_keyword,
1196            ),
1197            Statement::Commit {
1198                chain,
1199                end,
1200                modifier,
1201            } => self.handle_commit(chain, end, modifier),
1202            Statement::Rollback { chain, savepoint } => self.handle_rollback(chain, savepoint),
1203            other => self.execute_statement_non_transactional(other),
1204        }
1205    }
1206
1207    fn execute_statement_non_transactional(
1208        &self,
1209        statement: Statement,
1210    ) -> SqlResult<SqlStatementResult> {
1211        tracing::trace!("DEBUG SQL execute_statement_non_transactional called");
1212        match statement {
1213            Statement::CreateTable(stmt) => {
1214                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateTable");
1215                self.handle_create_table(stmt)
1216            }
1217            Statement::CreateIndex(stmt) => {
1218                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateIndex");
1219                self.handle_create_index(stmt)
1220            }
1221            Statement::CreateSchema {
1222                schema_name,
1223                if_not_exists,
1224                with,
1225                options,
1226                default_collate_spec,
1227                clone,
1228            } => {
1229                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateSchema");
1230                self.handle_create_schema(
1231                    schema_name,
1232                    if_not_exists,
1233                    with,
1234                    options,
1235                    default_collate_spec,
1236                    clone,
1237                )
1238            }
1239            Statement::CreateView {
1240                name,
1241                columns,
1242                query,
1243                materialized,
1244                or_replace,
1245                or_alter,
1246                options,
1247                cluster_by,
1248                comment,
1249                with_no_schema_binding,
1250                if_not_exists,
1251                temporary,
1252                to,
1253                params,
1254                secure,
1255                name_before_not_exists,
1256            } => {
1257                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateView");
1258                self.handle_create_view(
1259                    name,
1260                    columns,
1261                    query,
1262                    materialized,
1263                    or_replace,
1264                    or_alter,
1265                    options,
1266                    cluster_by,
1267                    comment,
1268                    with_no_schema_binding,
1269                    if_not_exists,
1270                    temporary,
1271                    to,
1272                    params,
1273                    secure,
1274                    name_before_not_exists,
1275                )
1276            }
1277            Statement::CreateDomain(create_domain) => {
1278                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateDomain");
1279                self.handle_create_domain(create_domain)
1280            }
1281            Statement::CreateTrigger(create_trigger) => {
1282                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateTrigger");
1283                self.handle_create_trigger(create_trigger)
1284            }
1285            Statement::DropDomain(drop_domain) => {
1286                tracing::trace!("DEBUG SQL execute_statement_non_transactional: DropDomain");
1287                self.handle_drop_domain(drop_domain)
1288            }
1289            Statement::Insert(stmt) => {
1290                let table_name =
1291                    Self::table_name_from_insert(&stmt).unwrap_or_else(|_| "unknown".to_string());
1292                tracing::trace!(
1293                    "DEBUG SQL execute_statement_non_transactional: Insert(table={})",
1294                    table_name
1295                );
1296                self.handle_insert(stmt)
1297            }
1298            Statement::Query(query) => {
1299                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Query");
1300                self.handle_query(*query)
1301            }
1302            Statement::Update {
1303                table,
1304                assignments,
1305                from,
1306                selection,
1307                returning,
1308                ..
1309            } => {
1310                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Update");
1311                self.handle_update(table, assignments, from, selection, returning)
1312            }
1313            Statement::Delete(delete) => {
1314                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Delete");
1315                self.handle_delete(delete)
1316            }
1317            Statement::Truncate {
1318                ref table_names,
1319                ref partitions,
1320                table,
1321                ref identity,
1322                cascade,
1323                ref on_cluster,
1324            } => {
1325                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Truncate");
1326                self.handle_truncate(
1327                    table_names,
1328                    partitions,
1329                    table,
1330                    identity,
1331                    cascade,
1332                    on_cluster,
1333                )
1334            }
1335            Statement::Drop {
1336                object_type,
1337                if_exists,
1338                names,
1339                cascade,
1340                restrict,
1341                purge,
1342                temporary,
1343                ..
1344            } => {
1345                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Drop");
1346                self.handle_drop(
1347                    object_type,
1348                    if_exists,
1349                    names,
1350                    cascade,
1351                    restrict,
1352                    purge,
1353                    temporary,
1354                )
1355            }
1356            Statement::DropTrigger(drop_trigger) => {
1357                tracing::trace!("DEBUG SQL execute_statement_non_transactional: DropTrigger");
1358                self.handle_drop_trigger(drop_trigger)
1359            }
1360            Statement::AlterTable {
1361                name,
1362                if_exists,
1363                only,
1364                operations,
1365                ..
1366            } => {
1367                tracing::trace!("DEBUG SQL execute_statement_non_transactional: AlterTable");
1368                self.handle_alter_table(name, if_exists, only, operations)
1369            }
1370            Statement::Set(set_stmt) => {
1371                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Set");
1372                self.handle_set(set_stmt)
1373            }
1374            Statement::Pragma { name, value, is_eq } => {
1375                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Pragma");
1376                self.handle_pragma(name, value, is_eq)
1377            }
1378            Statement::Vacuum(vacuum) => {
1379                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Vacuum");
1380                self.handle_vacuum(vacuum)
1381            }
1382            other => {
1383                tracing::trace!(
1384                    "DEBUG SQL execute_statement_non_transactional: Other({:?})",
1385                    other
1386                );
1387                Err(Error::InvalidArgumentError(format!(
1388                    "unsupported SQL statement: {other:?}"
1389                )))
1390            }
1391        }
1392    }
1393
1394    fn table_name_from_insert(insert: &sqlparser::ast::Insert) -> SqlResult<String> {
1395        match &insert.table {
1396            TableObject::TableName(name) => Self::object_name_to_string(name),
1397            _ => Err(Error::InvalidArgumentError(
1398                "INSERT requires a plain table name".into(),
1399            )),
1400        }
1401    }
1402
1403    fn table_name_from_update(table: &TableWithJoins) -> SqlResult<Option<String>> {
1404        if !table.joins.is_empty() {
1405            return Err(Error::InvalidArgumentError(
1406                "UPDATE with JOIN targets is not supported yet".into(),
1407            ));
1408        }
1409        Self::table_with_joins_name(table)
1410    }
1411
1412    fn table_name_from_delete(delete: &Delete) -> SqlResult<Option<String>> {
1413        if !delete.tables.is_empty() {
1414            return Err(Error::InvalidArgumentError(
1415                "multi-table DELETE is not supported yet".into(),
1416            ));
1417        }
1418        let from_tables = match &delete.from {
1419            FromTable::WithFromKeyword(tables) | FromTable::WithoutKeyword(tables) => tables,
1420        };
1421        if from_tables.is_empty() {
1422            return Ok(None);
1423        }
1424        if from_tables.len() != 1 {
1425            return Err(Error::InvalidArgumentError(
1426                "DELETE over multiple tables is not supported yet".into(),
1427            ));
1428        }
1429        Self::table_with_joins_name(&from_tables[0])
1430    }
1431
1432    fn object_name_to_string(name: &ObjectName) -> SqlResult<String> {
1433        let (display, _) = canonical_object_name(name)?;
1434        Ok(display)
1435    }
1436
1437    #[allow(dead_code)]
1438    fn table_object_to_name(table: &TableObject) -> SqlResult<Option<String>> {
1439        match table {
1440            TableObject::TableName(name) => Ok(Some(Self::object_name_to_string(name)?)),
1441            TableObject::TableFunction(_) => Ok(None),
1442        }
1443    }
1444
1445    fn table_with_joins_name(table: &TableWithJoins) -> SqlResult<Option<String>> {
1446        match &table.relation {
1447            TableFactor::Table { name, .. } => Ok(Some(Self::object_name_to_string(name)?)),
1448            _ => Ok(None),
1449        }
1450    }
1451
1452    fn tables_in_query(query: &Query) -> SqlResult<Vec<String>> {
1453        let mut tables = Vec::new();
1454        if let sqlparser::ast::SetExpr::Select(select) = query.body.as_ref() {
1455            for table in &select.from {
1456                if let TableFactor::Table { name, .. } = &table.relation {
1457                    tables.push(Self::object_name_to_string(name)?);
1458                }
1459            }
1460        }
1461        Ok(tables)
1462    }
1463
1464    fn collect_known_columns(
1465        &self,
1466        display_name: &str,
1467        canonical_name: &str,
1468    ) -> SqlResult<FxHashSet<String>> {
1469        let context = self.engine.context();
1470
1471        if context.is_table_marked_dropped(canonical_name) {
1472            return Err(Self::table_not_found_error(display_name));
1473        }
1474
1475        // First, check if the table was created in the current transaction
1476        if let Some(specs) = self
1477            .engine
1478            .session()
1479            .table_column_specs_from_transaction(canonical_name)
1480        {
1481            return Ok(specs
1482                .into_iter()
1483                .map(|spec| spec.name.to_ascii_lowercase())
1484                .collect());
1485        }
1486
1487        // Otherwise, look it up in the committed catalog
1488        let (_, canonical_name) = llkv_table::canonical_table_name(display_name)
1489            .map_err(|e| arrow::error::ArrowError::ExternalError(Box::new(e)))?;
1490
1491        match context.catalog().table_column_specs(&canonical_name) {
1492            Ok(specs) if !specs.is_empty() => Ok(specs
1493                .into_iter()
1494                .map(|spec| spec.name.to_ascii_lowercase())
1495                .collect()),
1496            Ok(_) => {
1497                if let Some(table_id) = context.catalog().table_id(&canonical_name)
1498                    && let Some(resolver) = context.catalog().field_resolver(table_id)
1499                {
1500                    let fallback: FxHashSet<String> = resolver
1501                        .field_names()
1502                        .into_iter()
1503                        .map(|name| name.to_ascii_lowercase())
1504                        .collect();
1505                    tracing::debug!(
1506                        "collect_known_columns: using resolver fallback for '{}': {:?}",
1507                        display_name,
1508                        fallback
1509                    );
1510                    return Ok(fallback);
1511                }
1512                Ok(FxHashSet::default())
1513            }
1514            Err(err) => {
1515                if Self::is_table_missing_error(&err) {
1516                    Ok(FxHashSet::default())
1517                } else {
1518                    Err(Self::map_table_error(display_name, err))
1519                }
1520            }
1521        }
1522    }
1523
1524    fn parse_view_query(sql: &str) -> SqlResult<Query> {
1525        use sqlparser::ast::Statement;
1526
1527        let dialect = GenericDialect {};
1528        let mut statements = Parser::parse_sql(&dialect, sql).map_err(|err| {
1529            Error::InvalidArgumentError(format!("failed to parse view definition: {}", err))
1530        })?;
1531
1532        if statements.len() != 1 {
1533            return Err(Error::InvalidArgumentError(
1534                "view definition must contain a single SELECT statement".into(),
1535            ));
1536        }
1537
1538        match statements.pop().unwrap() {
1539            Statement::Query(query) => Ok(*query),
1540            _ => Err(Error::InvalidArgumentError(
1541                "view definition must be expressed as a SELECT query".into(),
1542            )),
1543        }
1544    }
1545
1546    fn is_table_marked_dropped(&self, table_name: &str) -> SqlResult<bool> {
1547        let canonical = table_name.to_ascii_lowercase();
1548        Ok(self.engine.context().is_table_marked_dropped(&canonical))
1549    }
1550
1551    fn handle_create_table(
1552        &self,
1553        mut stmt: sqlparser::ast::CreateTable,
1554    ) -> SqlResult<SqlStatementResult> {
1555        validate_create_table_common(&stmt)?;
1556
1557        let (mut schema_name, table_name) = parse_schema_qualified_name(&stmt.name)?;
1558
1559        let namespace = if stmt.temporary {
1560            if schema_name.is_some() {
1561                return Err(Error::InvalidArgumentError(
1562                    "temporary tables cannot specify an explicit schema".into(),
1563                ));
1564            }
1565            schema_name = None;
1566            Some(TEMPORARY_NAMESPACE_ID.to_string())
1567        } else {
1568            None
1569        };
1570
1571        // Validate schema exists if specified
1572        if let Some(ref schema) = schema_name {
1573            let catalog = self.engine.context().table_catalog();
1574            if !catalog.schema_exists(schema) {
1575                return Err(Error::CatalogError(format!(
1576                    "Schema '{}' does not exist",
1577                    schema
1578                )));
1579            }
1580        }
1581
1582        // Use full qualified name (schema.table or just table)
1583        let display_name = match &schema_name {
1584            Some(schema) => format!("{}.{}", schema, table_name),
1585            None => table_name.clone(),
1586        };
1587        let canonical_name = display_name.to_ascii_lowercase();
1588        tracing::trace!(
1589            "\n=== HANDLE_CREATE_TABLE: table='{}' columns={} ===",
1590            display_name,
1591            stmt.columns.len()
1592        );
1593        if display_name.is_empty() {
1594            return Err(Error::InvalidArgumentError(
1595                "table name must not be empty".into(),
1596            ));
1597        }
1598
1599        if let Some(query) = stmt.query.take() {
1600            validate_create_table_as(&stmt)?;
1601            if let Some(result) = self.try_handle_range_ctas(
1602                &display_name,
1603                &canonical_name,
1604                &query,
1605                stmt.if_not_exists,
1606                stmt.or_replace,
1607                namespace.clone(),
1608            )? {
1609                return Ok(result);
1610            }
1611            return self.handle_create_table_as(
1612                display_name,
1613                canonical_name,
1614                *query,
1615                stmt.if_not_exists,
1616                stmt.or_replace,
1617                namespace.clone(),
1618            );
1619        }
1620
1621        if stmt.columns.is_empty() {
1622            return Err(Error::InvalidArgumentError(
1623                "CREATE TABLE requires at least one column".into(),
1624            ));
1625        }
1626
1627        validate_create_table_definition(&stmt)?;
1628
1629        let column_defs_ast = std::mem::take(&mut stmt.columns);
1630        let constraints = std::mem::take(&mut stmt.constraints);
1631
1632        let column_names: Vec<String> = column_defs_ast
1633            .iter()
1634            .map(|column_def| column_def.name.value.clone())
1635            .collect();
1636        ensure_unique_case_insensitive(column_names.iter().map(|name| name.as_str()), |dup| {
1637            format!(
1638                "duplicate column name '{}' in table '{}'",
1639                dup, display_name
1640            )
1641        })?;
1642        let column_names_lower: FxHashSet<String> = column_names
1643            .iter()
1644            .map(|name| name.to_ascii_lowercase())
1645            .collect();
1646
1647        let mut columns: Vec<PlanColumnSpec> = Vec::with_capacity(column_defs_ast.len());
1648        let mut primary_key_columns: FxHashSet<String> = FxHashSet::default();
1649        let mut foreign_keys: Vec<ForeignKeySpec> = Vec::new();
1650        let mut multi_column_uniques: Vec<MultiColumnUniqueSpec> = Vec::new();
1651
1652        // Second pass: process columns including CHECK validation and column-level FKs
1653        for column_def in column_defs_ast {
1654            let is_nullable = column_def
1655                .options
1656                .iter()
1657                .all(|opt| !matches!(opt.option, ColumnOption::NotNull));
1658
1659            let is_primary_key = column_def.options.iter().any(|opt| {
1660                matches!(
1661                    opt.option,
1662                    ColumnOption::Unique {
1663                        is_primary: true,
1664                        characteristics: _
1665                    }
1666                )
1667            });
1668
1669            let has_unique_constraint = column_def
1670                .options
1671                .iter()
1672                .any(|opt| matches!(opt.option, ColumnOption::Unique { .. }));
1673
1674            // Extract CHECK constraint if present and validate it
1675            let check_expr = column_def.options.iter().find_map(|opt| {
1676                if let ColumnOption::Check(expr) = &opt.option {
1677                    Some(expr)
1678                } else {
1679                    None
1680                }
1681            });
1682
1683            // Validate CHECK constraint if present (now we have all column names)
1684            if let Some(check_expr) = check_expr {
1685                let all_col_refs: Vec<&str> = column_names.iter().map(|s| s.as_str()).collect();
1686                validate_check_constraint(check_expr, &display_name, &all_col_refs)?;
1687            }
1688
1689            let check_expr_str = check_expr.map(|e| e.to_string());
1690
1691            // Extract column-level FOREIGN KEY (REFERENCES clause)
1692            for opt in &column_def.options {
1693                if let ColumnOption::ForeignKey {
1694                    foreign_table,
1695                    referred_columns,
1696                    on_delete,
1697                    on_update,
1698                    characteristics,
1699                } = &opt.option
1700                {
1701                    let spec = self.build_foreign_key_spec(
1702                        &display_name,
1703                        &canonical_name,
1704                        vec![column_def.name.value.clone()],
1705                        foreign_table,
1706                        referred_columns,
1707                        *on_delete,
1708                        *on_update,
1709                        characteristics,
1710                        &column_names_lower,
1711                        None,
1712                    )?;
1713                    foreign_keys.push(spec);
1714                }
1715            }
1716
1717            tracing::trace!(
1718                "DEBUG CREATE TABLE column '{}' is_primary_key={} has_unique={} check_expr={:?}",
1719                column_def.name.value,
1720                is_primary_key,
1721                has_unique_constraint,
1722                check_expr_str
1723            );
1724
1725            // Resolve custom type aliases to their base types
1726            let resolved_data_type = self.engine.context().resolve_type(&column_def.data_type);
1727
1728            let mut column = PlanColumnSpec::new(
1729                column_def.name.value.clone(),
1730                arrow_type_from_sql(&resolved_data_type)?,
1731                is_nullable,
1732            );
1733            tracing::trace!(
1734                "DEBUG PlanColumnSpec after new(): primary_key={} unique={}",
1735                column.primary_key,
1736                column.unique
1737            );
1738
1739            column = column
1740                .with_primary_key(is_primary_key)
1741                .with_unique(has_unique_constraint)
1742                .with_check(check_expr_str);
1743
1744            if is_primary_key {
1745                column.nullable = false;
1746                primary_key_columns.insert(column.name.to_ascii_lowercase());
1747            }
1748            tracing::trace!(
1749                "DEBUG PlanColumnSpec after with_primary_key({})/with_unique({}): primary_key={} unique={} check_expr={:?}",
1750                is_primary_key,
1751                has_unique_constraint,
1752                column.primary_key,
1753                column.unique,
1754                column.check_expr
1755            );
1756
1757            columns.push(column);
1758        }
1759
1760        // Apply supported table-level constraints (e.g., PRIMARY KEY)
1761        if !constraints.is_empty() {
1762            let mut column_lookup: FxHashMap<String, usize> =
1763                FxHashMap::with_capacity_and_hasher(columns.len(), Default::default());
1764            for (idx, column) in columns.iter().enumerate() {
1765                column_lookup.insert(column.name.to_ascii_lowercase(), idx);
1766            }
1767
1768            for constraint in constraints {
1769                match constraint {
1770                    TableConstraint::PrimaryKey {
1771                        columns: constraint_columns,
1772                        ..
1773                    } => {
1774                        if !primary_key_columns.is_empty() {
1775                            return Err(Error::InvalidArgumentError(
1776                                "multiple PRIMARY KEY constraints are not supported".into(),
1777                            ));
1778                        }
1779
1780                        ensure_non_empty(&constraint_columns, || {
1781                            "PRIMARY KEY requires at least one column".into()
1782                        })?;
1783
1784                        let mut pk_column_names: Vec<String> =
1785                            Vec::with_capacity(constraint_columns.len());
1786
1787                        for index_col in &constraint_columns {
1788                            let column_ident = extract_index_column_name(
1789                                index_col,
1790                                "PRIMARY KEY",
1791                                false, // no sort options allowed
1792                                false, // only simple identifiers
1793                            )?;
1794                            pk_column_names.push(column_ident);
1795                        }
1796
1797                        ensure_unique_case_insensitive(
1798                            pk_column_names.iter().map(|name| name.as_str()),
1799                            |dup| format!("duplicate column '{}' in PRIMARY KEY constraint", dup),
1800                        )?;
1801
1802                        ensure_known_columns_case_insensitive(
1803                            pk_column_names.iter().map(|name| name.as_str()),
1804                            &column_names_lower,
1805                            |unknown| {
1806                                format!("unknown column '{}' in PRIMARY KEY constraint", unknown)
1807                            },
1808                        )?;
1809
1810                        for column_ident in pk_column_names {
1811                            let normalized = column_ident.to_ascii_lowercase();
1812                            let idx = column_lookup.get(&normalized).copied().ok_or_else(|| {
1813                                Error::InvalidArgumentError(format!(
1814                                    "unknown column '{}' in PRIMARY KEY constraint",
1815                                    column_ident
1816                                ))
1817                            })?;
1818
1819                            let column = columns.get_mut(idx).expect("column index valid");
1820                            column.primary_key = true;
1821                            column.unique = true;
1822                            column.nullable = false;
1823
1824                            primary_key_columns.insert(normalized);
1825                        }
1826                    }
1827                    TableConstraint::Unique {
1828                        columns: constraint_columns,
1829                        index_type,
1830                        index_options,
1831                        characteristics,
1832                        nulls_distinct,
1833                        name,
1834                        ..
1835                    } => {
1836                        if !matches!(nulls_distinct, NullsDistinctOption::None) {
1837                            return Err(Error::InvalidArgumentError(
1838                                "UNIQUE constraints with NULLS DISTINCT/NOT DISTINCT are not supported yet".into(),
1839                            ));
1840                        }
1841
1842                        if index_type.is_some() {
1843                            return Err(Error::InvalidArgumentError(
1844                                "UNIQUE constraints with index types are not supported yet".into(),
1845                            ));
1846                        }
1847
1848                        if !index_options.is_empty() {
1849                            return Err(Error::InvalidArgumentError(
1850                                "UNIQUE constraints with index options are not supported yet"
1851                                    .into(),
1852                            ));
1853                        }
1854
1855                        if characteristics.is_some() {
1856                            return Err(Error::InvalidArgumentError(
1857                                "UNIQUE constraint characteristics are not supported yet".into(),
1858                            ));
1859                        }
1860
1861                        ensure_non_empty(&constraint_columns, || {
1862                            "UNIQUE constraint requires at least one column".into()
1863                        })?;
1864
1865                        let mut unique_column_names: Vec<String> =
1866                            Vec::with_capacity(constraint_columns.len());
1867
1868                        for index_column in &constraint_columns {
1869                            let column_ident = extract_index_column_name(
1870                                index_column,
1871                                "UNIQUE constraint",
1872                                false, // no sort options allowed
1873                                false, // only simple identifiers
1874                            )?;
1875                            unique_column_names.push(column_ident);
1876                        }
1877
1878                        ensure_unique_case_insensitive(
1879                            unique_column_names.iter().map(|name| name.as_str()),
1880                            |dup| format!("duplicate column '{}' in UNIQUE constraint", dup),
1881                        )?;
1882
1883                        ensure_known_columns_case_insensitive(
1884                            unique_column_names.iter().map(|name| name.as_str()),
1885                            &column_names_lower,
1886                            |unknown| format!("unknown column '{}' in UNIQUE constraint", unknown),
1887                        )?;
1888
1889                        if unique_column_names.len() > 1 {
1890                            // Multi-column UNIQUE constraint
1891                            multi_column_uniques.push(MultiColumnUniqueSpec {
1892                                name: name.map(|n| n.value),
1893                                columns: unique_column_names,
1894                            });
1895                        } else {
1896                            // Single-column UNIQUE constraint
1897                            let column_ident = unique_column_names
1898                                .into_iter()
1899                                .next()
1900                                .expect("unique constraint checked for emptiness");
1901                            let normalized = column_ident.to_ascii_lowercase();
1902                            let idx = column_lookup.get(&normalized).copied().ok_or_else(|| {
1903                                Error::InvalidArgumentError(format!(
1904                                    "unknown column '{}' in UNIQUE constraint",
1905                                    column_ident
1906                                ))
1907                            })?;
1908
1909                            let column = columns
1910                                .get_mut(idx)
1911                                .expect("column index from lookup must be valid");
1912                            column.unique = true;
1913                        }
1914                    }
1915                    TableConstraint::ForeignKey {
1916                        name,
1917                        index_name,
1918                        columns: fk_columns,
1919                        foreign_table,
1920                        referred_columns,
1921                        on_delete,
1922                        on_update,
1923                        characteristics,
1924                        ..
1925                    } => {
1926                        if index_name.is_some() {
1927                            return Err(Error::InvalidArgumentError(
1928                                "FOREIGN KEY index clauses are not supported yet".into(),
1929                            ));
1930                        }
1931
1932                        let referencing_columns: Vec<String> =
1933                            fk_columns.into_iter().map(|ident| ident.value).collect();
1934                        let spec = self.build_foreign_key_spec(
1935                            &display_name,
1936                            &canonical_name,
1937                            referencing_columns,
1938                            &foreign_table,
1939                            &referred_columns,
1940                            on_delete,
1941                            on_update,
1942                            &characteristics,
1943                            &column_names_lower,
1944                            name.map(|ident| ident.value),
1945                        )?;
1946
1947                        foreign_keys.push(spec);
1948                    }
1949                    unsupported => {
1950                        return Err(Error::InvalidArgumentError(format!(
1951                            "table-level constraint {:?} is not supported",
1952                            unsupported
1953                        )));
1954                    }
1955                }
1956            }
1957        }
1958
1959        let plan = CreateTablePlan {
1960            name: display_name,
1961            if_not_exists: stmt.if_not_exists,
1962            or_replace: stmt.or_replace,
1963            columns,
1964            source: None,
1965            namespace,
1966            foreign_keys,
1967            multi_column_uniques,
1968        };
1969        self.execute_plan_statement(PlanStatement::CreateTable(plan))
1970    }
1971
1972    fn handle_create_index(
1973        &self,
1974        stmt: sqlparser::ast::CreateIndex,
1975    ) -> SqlResult<RuntimeStatementResult<P>> {
1976        let sqlparser::ast::CreateIndex {
1977            name,
1978            table_name,
1979            using,
1980            columns,
1981            unique,
1982            concurrently,
1983            if_not_exists,
1984            include,
1985            nulls_distinct,
1986            with,
1987            predicate,
1988            index_options,
1989            alter_options,
1990            ..
1991        } = stmt;
1992
1993        if concurrently {
1994            return Err(Error::InvalidArgumentError(
1995                "CREATE INDEX CONCURRENTLY is not supported".into(),
1996            ));
1997        }
1998        if using.is_some() {
1999            return Err(Error::InvalidArgumentError(
2000                "CREATE INDEX USING clauses are not supported".into(),
2001            ));
2002        }
2003        if !include.is_empty() {
2004            return Err(Error::InvalidArgumentError(
2005                "CREATE INDEX INCLUDE columns are not supported".into(),
2006            ));
2007        }
2008        if nulls_distinct.is_some() {
2009            return Err(Error::InvalidArgumentError(
2010                "CREATE INDEX NULLS DISTINCT is not supported".into(),
2011            ));
2012        }
2013        if !with.is_empty() {
2014            return Err(Error::InvalidArgumentError(
2015                "CREATE INDEX WITH options are not supported".into(),
2016            ));
2017        }
2018        if predicate.is_some() {
2019            return Err(Error::InvalidArgumentError(
2020                "partial CREATE INDEX is not supported".into(),
2021            ));
2022        }
2023        if !index_options.is_empty() {
2024            return Err(Error::InvalidArgumentError(
2025                "CREATE INDEX options are not supported".into(),
2026            ));
2027        }
2028        if !alter_options.is_empty() {
2029            return Err(Error::InvalidArgumentError(
2030                "CREATE INDEX ALTER options are not supported".into(),
2031            ));
2032        }
2033        if columns.is_empty() {
2034            return Err(Error::InvalidArgumentError(
2035                "CREATE INDEX requires at least one column".into(),
2036            ));
2037        }
2038
2039        let (schema_name, base_table_name) = parse_schema_qualified_name(&table_name)?;
2040        if let Some(ref schema) = schema_name {
2041            let catalog = self.engine.context().table_catalog();
2042            if !catalog.schema_exists(schema) {
2043                return Err(Error::CatalogError(format!(
2044                    "Schema '{}' does not exist",
2045                    schema
2046                )));
2047            }
2048        }
2049
2050        let display_table_name = schema_name
2051            .as_ref()
2052            .map(|schema| format!("{}.{}", schema, base_table_name))
2053            .unwrap_or_else(|| base_table_name.clone());
2054        let canonical_table_name = display_table_name.to_ascii_lowercase();
2055
2056        let known_columns =
2057            self.collect_known_columns(&display_table_name, &canonical_table_name)?;
2058        let enforce_known_columns = !known_columns.is_empty();
2059
2060        let index_name = match name {
2061            Some(name_obj) => Some(Self::object_name_to_string(&name_obj)?),
2062            None => None,
2063        };
2064
2065        let mut index_columns: Vec<IndexColumnPlan> = Vec::with_capacity(columns.len());
2066        let mut seen_column_names: FxHashSet<String> = FxHashSet::default();
2067        for item in columns {
2068            // Check WITH FILL before calling helper (not part of standard validation)
2069            if item.column.with_fill.is_some() {
2070                return Err(Error::InvalidArgumentError(
2071                    "CREATE INDEX column WITH FILL is not supported".into(),
2072                ));
2073            }
2074
2075            let column_name = extract_index_column_name(
2076                &item,
2077                "CREATE INDEX",
2078                true, // allow and validate sort options
2079                true, // allow compound identifiers
2080            )?;
2081
2082            // Get sort options (already validated by helper)
2083            let order_expr = &item.column;
2084            let ascending = order_expr.options.asc.unwrap_or(true);
2085            let nulls_first = order_expr.options.nulls_first.unwrap_or(false);
2086
2087            let normalized = column_name.to_ascii_lowercase();
2088            if !seen_column_names.insert(normalized.clone()) {
2089                return Err(Error::InvalidArgumentError(format!(
2090                    "duplicate column '{}' in CREATE INDEX",
2091                    column_name
2092                )));
2093            }
2094
2095            if enforce_known_columns && !known_columns.contains(&normalized) {
2096                return Err(Error::InvalidArgumentError(format!(
2097                    "column '{}' does not exist in table '{}'",
2098                    column_name, display_table_name
2099                )));
2100            }
2101
2102            let column_plan = IndexColumnPlan::new(column_name).with_sort(ascending, nulls_first);
2103            index_columns.push(column_plan);
2104        }
2105
2106        let plan = CreateIndexPlan::new(display_table_name)
2107            .with_name(index_name)
2108            .with_unique(unique)
2109            .with_if_not_exists(if_not_exists)
2110            .with_columns(index_columns);
2111
2112        self.execute_plan_statement(PlanStatement::CreateIndex(plan))
2113    }
2114
2115    fn map_referential_action(
2116        action: Option<ReferentialAction>,
2117        kind: &str,
2118    ) -> SqlResult<ForeignKeyAction> {
2119        match action {
2120            None | Some(ReferentialAction::NoAction) => Ok(ForeignKeyAction::NoAction),
2121            Some(ReferentialAction::Restrict) => Ok(ForeignKeyAction::Restrict),
2122            Some(other) => Err(Error::InvalidArgumentError(format!(
2123                "FOREIGN KEY ON {kind} {:?} is not supported yet",
2124                other
2125            ))),
2126        }
2127    }
2128
2129    #[allow(clippy::too_many_arguments)]
2130    fn build_foreign_key_spec(
2131        &self,
2132        _referencing_display: &str,
2133        referencing_canonical: &str,
2134        referencing_columns: Vec<String>,
2135        foreign_table: &ObjectName,
2136        referenced_columns: &[Ident],
2137        on_delete: Option<ReferentialAction>,
2138        on_update: Option<ReferentialAction>,
2139        characteristics: &Option<ConstraintCharacteristics>,
2140        known_columns_lower: &FxHashSet<String>,
2141        name: Option<String>,
2142    ) -> SqlResult<ForeignKeySpec> {
2143        if characteristics.is_some() {
2144            return Err(Error::InvalidArgumentError(
2145                "FOREIGN KEY constraint characteristics are not supported yet".into(),
2146            ));
2147        }
2148
2149        ensure_non_empty(&referencing_columns, || {
2150            "FOREIGN KEY constraint requires at least one referencing column".into()
2151        })?;
2152        ensure_unique_case_insensitive(
2153            referencing_columns.iter().map(|name| name.as_str()),
2154            |dup| format!("duplicate column '{}' in FOREIGN KEY constraint", dup),
2155        )?;
2156        ensure_known_columns_case_insensitive(
2157            referencing_columns.iter().map(|name| name.as_str()),
2158            known_columns_lower,
2159            |unknown| format!("unknown column '{}' in FOREIGN KEY constraint", unknown),
2160        )?;
2161
2162        let referenced_columns_vec: Vec<String> = referenced_columns
2163            .iter()
2164            .map(|ident| ident.value.clone())
2165            .collect();
2166        ensure_unique_case_insensitive(
2167            referenced_columns_vec.iter().map(|name| name.as_str()),
2168            |dup| {
2169                format!(
2170                    "duplicate referenced column '{}' in FOREIGN KEY constraint",
2171                    dup
2172                )
2173            },
2174        )?;
2175
2176        if !referenced_columns_vec.is_empty()
2177            && referenced_columns_vec.len() != referencing_columns.len()
2178        {
2179            return Err(Error::InvalidArgumentError(
2180                "FOREIGN KEY referencing and referenced column counts must match".into(),
2181            ));
2182        }
2183
2184        let (referenced_display, referenced_canonical) = canonical_object_name(foreign_table)?;
2185
2186        // Check if the referenced table is a VIEW - VIEWs cannot be referenced by foreign keys
2187        let catalog = self.engine.context().table_catalog();
2188        if let Some(table_id) = catalog.table_id(&referenced_canonical) {
2189            let context = self.engine.context();
2190            if context.is_view(table_id)? {
2191                return Err(Error::CatalogError(format!(
2192                    "Binder Error: cannot reference a VIEW with a FOREIGN KEY: {}",
2193                    referenced_display
2194                )));
2195            }
2196        }
2197
2198        if referenced_canonical == referencing_canonical {
2199            ensure_known_columns_case_insensitive(
2200                referenced_columns_vec.iter().map(|name| name.as_str()),
2201                known_columns_lower,
2202                |unknown| {
2203                    format!(
2204                        "Binder Error: table '{}' does not have a column named '{}'",
2205                        referenced_display, unknown
2206                    )
2207                },
2208            )?;
2209        } else {
2210            let known_columns =
2211                self.collect_known_columns(&referenced_display, &referenced_canonical)?;
2212            if !known_columns.is_empty() {
2213                ensure_known_columns_case_insensitive(
2214                    referenced_columns_vec.iter().map(|name| name.as_str()),
2215                    &known_columns,
2216                    |unknown| {
2217                        format!(
2218                            "Binder Error: table '{}' does not have a column named '{}'",
2219                            referenced_display, unknown
2220                        )
2221                    },
2222                )?;
2223            }
2224        }
2225
2226        let on_delete_action = Self::map_referential_action(on_delete, "DELETE")?;
2227        let on_update_action = Self::map_referential_action(on_update, "UPDATE")?;
2228
2229        Ok(ForeignKeySpec {
2230            name,
2231            columns: referencing_columns,
2232            referenced_table: referenced_display,
2233            referenced_columns: referenced_columns_vec,
2234            on_delete: on_delete_action,
2235            on_update: on_update_action,
2236        })
2237    }
2238
2239    fn handle_create_schema(
2240        &self,
2241        schema_name: SchemaName,
2242        _if_not_exists: bool,
2243        with: Option<Vec<SqlOption>>,
2244        options: Option<Vec<SqlOption>>,
2245        default_collate_spec: Option<SqlExpr>,
2246        clone: Option<ObjectName>,
2247    ) -> SqlResult<RuntimeStatementResult<P>> {
2248        if clone.is_some() {
2249            return Err(Error::InvalidArgumentError(
2250                "CREATE SCHEMA ... CLONE is not supported".into(),
2251            ));
2252        }
2253        if with.as_ref().is_some_and(|opts| !opts.is_empty()) {
2254            return Err(Error::InvalidArgumentError(
2255                "CREATE SCHEMA ... WITH options are not supported".into(),
2256            ));
2257        }
2258        if options.as_ref().is_some_and(|opts| !opts.is_empty()) {
2259            return Err(Error::InvalidArgumentError(
2260                "CREATE SCHEMA options are not supported".into(),
2261            ));
2262        }
2263        if default_collate_spec.is_some() {
2264            return Err(Error::InvalidArgumentError(
2265                "CREATE SCHEMA DEFAULT COLLATE is not supported".into(),
2266            ));
2267        }
2268
2269        let schema_name = match schema_name {
2270            SchemaName::Simple(name) => name,
2271            _ => {
2272                return Err(Error::InvalidArgumentError(
2273                    "CREATE SCHEMA authorization is not supported".into(),
2274                ));
2275            }
2276        };
2277
2278        let (display_name, canonical) = canonical_object_name(&schema_name)?;
2279        if display_name.is_empty() {
2280            return Err(Error::InvalidArgumentError(
2281                "schema name must not be empty".into(),
2282            ));
2283        }
2284
2285        // Register schema in the catalog
2286        let catalog = self.engine.context().table_catalog();
2287
2288        if _if_not_exists && catalog.schema_exists(&canonical) {
2289            return Ok(RuntimeStatementResult::NoOp);
2290        }
2291
2292        catalog.register_schema(&canonical).map_err(|err| {
2293            Error::CatalogError(format!(
2294                "Failed to create schema '{}': {}",
2295                display_name, err
2296            ))
2297        })?;
2298
2299        Ok(RuntimeStatementResult::NoOp)
2300    }
2301
2302    #[allow(clippy::too_many_arguments)]
2303    fn handle_create_view(
2304        &self,
2305        name: ObjectName,
2306        columns: Vec<sqlparser::ast::ViewColumnDef>,
2307        query: Box<sqlparser::ast::Query>,
2308        materialized: bool,
2309        or_replace: bool,
2310        or_alter: bool,
2311        _options: sqlparser::ast::CreateTableOptions,
2312        _cluster_by: Vec<sqlparser::ast::Ident>,
2313        _comment: Option<String>,
2314        _with_no_schema_binding: bool,
2315        if_not_exists: bool,
2316        temporary: bool,
2317        _to: Option<ObjectName>,
2318        _params: Option<sqlparser::ast::CreateViewParams>,
2319        _secure: bool,
2320        _name_before_not_exists: bool,
2321    ) -> SqlResult<RuntimeStatementResult<P>> {
2322        // Validate unsupported features
2323        if materialized {
2324            return Err(Error::InvalidArgumentError(
2325                "MATERIALIZED VIEWS are not supported".into(),
2326            ));
2327        }
2328        if or_replace {
2329            return Err(Error::InvalidArgumentError(
2330                "CREATE OR REPLACE VIEW is not supported".into(),
2331            ));
2332        }
2333        if or_alter {
2334            return Err(Error::InvalidArgumentError(
2335                "CREATE OR ALTER VIEW is not supported".into(),
2336            ));
2337        }
2338
2339        // Parse view name (same as table parsing)
2340        let (schema_name, view_name) = parse_schema_qualified_name(&name)?;
2341
2342        // Validate schema exists if specified
2343        if let Some(ref schema) = schema_name {
2344            let catalog = self.engine.context().table_catalog();
2345            if !catalog.schema_exists(schema) {
2346                return Err(Error::CatalogError(format!(
2347                    "Schema '{}' does not exist",
2348                    schema
2349                )));
2350            }
2351        }
2352
2353        // Use full qualified name (schema.view or just view)
2354        let display_name = match &schema_name {
2355            Some(schema) => format!("{}.{}", schema, view_name),
2356            None => view_name.clone(),
2357        };
2358        let canonical_name = display_name.to_ascii_lowercase();
2359
2360        // Check if view already exists
2361        let catalog = self.engine.context().table_catalog();
2362        if catalog.table_exists(&canonical_name) {
2363            if if_not_exists {
2364                return Ok(RuntimeStatementResult::NoOp);
2365            }
2366            return Err(Error::CatalogError(format!(
2367                "Table or view '{}' already exists",
2368                display_name
2369            )));
2370        }
2371
2372        // Capture and normalize optional column list definitions.
2373        let column_names: Vec<String> = columns
2374            .into_iter()
2375            .map(|column| column.name.value)
2376            .collect();
2377
2378        if !column_names.is_empty() {
2379            ensure_unique_case_insensitive(column_names.iter().map(|name| name.as_str()), |dup| {
2380                format!("duplicate column name '{}' in CREATE VIEW column list", dup)
2381            })?;
2382        }
2383
2384        let mut query_ast = *query;
2385
2386        if !column_names.is_empty() {
2387            let select = match query_ast.body.as_mut() {
2388                sqlparser::ast::SetExpr::Select(select) => select,
2389                _ => {
2390                    return Err(Error::InvalidArgumentError(
2391                        "CREATE VIEW column list requires a SELECT query".into(),
2392                    ));
2393                }
2394            };
2395
2396            for item in &select.projection {
2397                if matches!(
2398                    item,
2399                    SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _)
2400                ) {
2401                    return Err(Error::InvalidArgumentError(
2402                        "CREATE VIEW column lists with wildcard projections are not supported yet"
2403                            .into(),
2404                    ));
2405                }
2406            }
2407
2408            if select.projection.len() != column_names.len() {
2409                return Err(Error::InvalidArgumentError(format!(
2410                    "CREATE VIEW column list specifies {} column(s) but SELECT projection yields {}",
2411                    column_names.len(),
2412                    select.projection.len()
2413                )));
2414            }
2415
2416            for (item, column_name) in select.projection.iter_mut().zip(column_names.iter()) {
2417                match item {
2418                    SelectItem::ExprWithAlias { alias, .. } => {
2419                        alias.value = column_name.clone();
2420                    }
2421                    SelectItem::UnnamedExpr(expr) => {
2422                        *item = SelectItem::ExprWithAlias {
2423                            expr: expr.clone(),
2424                            alias: Ident::new(column_name.clone()),
2425                        };
2426                    }
2427                    _ => {
2428                        return Err(Error::InvalidArgumentError(
2429                            "CREATE VIEW column list requires simple SELECT projections".into(),
2430                        ));
2431                    }
2432                }
2433            }
2434        }
2435
2436        // Validate the view SELECT plan after applying optional column aliases and capture it.
2437        let select_plan = self.build_select_plan(query_ast.clone())?;
2438
2439        // Convert query to SQL string for storage (after applying column aliases when present)
2440        let view_definition = query_ast.to_string();
2441
2442        // Build CreateViewPlan with namespace routing (same pattern as CREATE TABLE)
2443        let namespace = if temporary {
2444            Some(TEMPORARY_NAMESPACE_ID.to_string())
2445        } else {
2446            None
2447        };
2448
2449        let plan = CreateViewPlan {
2450            name: display_name.clone(),
2451            if_not_exists,
2452            view_definition,
2453            select_plan: Box::new(select_plan),
2454            namespace,
2455        };
2456
2457        self.execute_plan_statement(PlanStatement::CreateView(plan))?;
2458
2459        tracing::debug!("Created view: {}", display_name);
2460        Ok(RuntimeStatementResult::NoOp)
2461    }
2462
2463    fn handle_create_domain(
2464        &self,
2465        create_domain: sqlparser::ast::CreateDomain,
2466    ) -> SqlResult<RuntimeStatementResult<P>> {
2467        use llkv_table::CustomTypeMeta;
2468        use std::time::{SystemTime, UNIX_EPOCH};
2469
2470        // Extract the type alias name
2471        let type_name = create_domain.name.to_string();
2472
2473        // Convert the data type to SQL string for persistence
2474        let base_type_sql = create_domain.data_type.to_string();
2475
2476        // Register the type alias in the runtime context (in-memory)
2477        self.engine
2478            .context()
2479            .register_type(type_name.clone(), create_domain.data_type.clone());
2480
2481        // Persist to catalog
2482        let context = self.engine.context();
2483        let catalog = llkv_table::SysCatalog::new(context.store());
2484
2485        let created_at_micros = SystemTime::now()
2486            .duration_since(UNIX_EPOCH)
2487            .unwrap_or_default()
2488            .as_micros() as u64;
2489
2490        let meta = CustomTypeMeta {
2491            name: type_name.clone(),
2492            base_type_sql,
2493            created_at_micros,
2494        };
2495
2496        catalog.put_custom_type_meta(&meta)?;
2497
2498        tracing::debug!("Created and persisted type alias: {}", type_name);
2499        Ok(RuntimeStatementResult::NoOp)
2500    }
2501
2502    fn handle_create_trigger(
2503        &self,
2504        create_trigger: CreateTrigger,
2505    ) -> SqlResult<RuntimeStatementResult<P>> {
2506        let CreateTrigger {
2507            or_alter,
2508            or_replace,
2509            is_constraint,
2510            name,
2511            period,
2512            period_before_table: _,
2513            events,
2514            table_name,
2515            referenced_table_name,
2516            referencing,
2517            trigger_object,
2518            include_each: _,
2519            condition,
2520            exec_body,
2521            statements_as,
2522            statements,
2523            characteristics,
2524        } = create_trigger;
2525
2526        if or_alter {
2527            return Err(Error::InvalidArgumentError(
2528                "CREATE OR ALTER TRIGGER is not supported".into(),
2529            ));
2530        }
2531
2532        if or_replace {
2533            return Err(Error::InvalidArgumentError(
2534                "CREATE OR REPLACE TRIGGER is not supported".into(),
2535            ));
2536        }
2537
2538        if is_constraint {
2539            return Err(Error::InvalidArgumentError(
2540                "CREATE TRIGGER ... CONSTRAINT is not supported".into(),
2541            ));
2542        }
2543
2544        if referenced_table_name.is_some() {
2545            return Err(Error::InvalidArgumentError(
2546                "CREATE TRIGGER referencing another table is not supported".into(),
2547            ));
2548        }
2549
2550        if !referencing.is_empty() {
2551            return Err(Error::InvalidArgumentError(
2552                "CREATE TRIGGER REFERENCING clauses are not supported".into(),
2553            ));
2554        }
2555
2556        if characteristics.is_some() {
2557            return Err(Error::InvalidArgumentError(
2558                "CREATE TRIGGER constraint characteristics are not supported".into(),
2559            ));
2560        }
2561
2562        if events.is_empty() {
2563            return Err(Error::InvalidArgumentError(
2564                "CREATE TRIGGER requires at least one event".into(),
2565            ));
2566        }
2567
2568        if events.len() != 1 {
2569            return Err(Error::InvalidArgumentError(
2570                "CREATE TRIGGER currently supports exactly one trigger event".into(),
2571            ));
2572        }
2573
2574        let timing = match period {
2575            TriggerPeriod::Before => TriggerTimingMeta::Before,
2576            TriggerPeriod::After | TriggerPeriod::For => TriggerTimingMeta::After,
2577            TriggerPeriod::InsteadOf => TriggerTimingMeta::InsteadOf,
2578        };
2579
2580        let event_meta = match events.into_iter().next().expect("checked length") {
2581            TriggerEvent::Insert => TriggerEventMeta::Insert,
2582            TriggerEvent::Delete => TriggerEventMeta::Delete,
2583            TriggerEvent::Update(columns) => TriggerEventMeta::Update {
2584                columns: columns
2585                    .into_iter()
2586                    .map(|ident| ident.value.to_ascii_lowercase())
2587                    .collect(),
2588            },
2589            TriggerEvent::Truncate => {
2590                return Err(Error::InvalidArgumentError(
2591                    "CREATE TRIGGER for TRUNCATE events is not supported".into(),
2592                ));
2593            }
2594        };
2595
2596        let (trigger_display_name, canonical_trigger_name) = canonical_object_name(&name)?;
2597        let (table_display_name, canonical_table_name) = canonical_object_name(&table_name)?;
2598
2599        let condition_sql = condition.map(|expr| expr.to_string());
2600
2601        let body_sql = if let Some(exec_body) = exec_body {
2602            format!("EXECUTE {exec_body}")
2603        } else if let Some(statements) = statements {
2604            let rendered = statements.to_string();
2605            if statements_as {
2606                format!("AS {rendered}")
2607            } else {
2608                rendered
2609            }
2610        } else {
2611            return Err(Error::InvalidArgumentError(
2612                "CREATE TRIGGER requires a trigger body".into(),
2613            ));
2614        };
2615
2616        let for_each_row = matches!(trigger_object, TriggerObject::Row);
2617
2618        self.engine.context().create_trigger(
2619            &trigger_display_name,
2620            &canonical_trigger_name,
2621            &table_display_name,
2622            &canonical_table_name,
2623            timing,
2624            event_meta,
2625            for_each_row,
2626            condition_sql,
2627            body_sql,
2628            false,
2629        )?;
2630
2631        Ok(RuntimeStatementResult::NoOp)
2632    }
2633
2634    fn handle_drop_domain(
2635        &self,
2636        drop_domain: sqlparser::ast::DropDomain,
2637    ) -> SqlResult<RuntimeStatementResult<P>> {
2638        let if_exists = drop_domain.if_exists;
2639        let type_name = drop_domain.name.to_string();
2640
2641        // Drop the type from the registry (in-memory)
2642        let result = self.engine.context().drop_type(&type_name);
2643
2644        if let Err(err) = result {
2645            if !if_exists {
2646                return Err(err);
2647            }
2648            // if_exists = true, so ignore the error
2649        } else {
2650            // Persist deletion to catalog
2651            let context = self.engine.context();
2652            let catalog = llkv_table::SysCatalog::new(context.store());
2653            catalog.delete_custom_type_meta(&type_name)?;
2654
2655            tracing::debug!("Dropped and removed from catalog type alias: {}", type_name);
2656        }
2657
2658        Ok(RuntimeStatementResult::NoOp)
2659    }
2660
2661    fn handle_drop_trigger(
2662        &self,
2663        drop_trigger: DropTrigger,
2664    ) -> SqlResult<RuntimeStatementResult<P>> {
2665        let DropTrigger {
2666            if_exists,
2667            trigger_name,
2668            table_name,
2669            option,
2670        } = drop_trigger;
2671
2672        if option.is_some() {
2673            return Err(Error::InvalidArgumentError(
2674                "DROP TRIGGER CASCADE/RESTRICT options are not supported".into(),
2675            ));
2676        }
2677
2678        let (trigger_display_name, canonical_trigger_name) = canonical_object_name(&trigger_name)?;
2679
2680        let (table_display, table_canonical) = if let Some(table_name) = table_name {
2681            let (display, canonical) = canonical_object_name(&table_name)?;
2682            (Some(display), Some(canonical))
2683        } else {
2684            (None, None)
2685        };
2686
2687        let table_display_hint = table_display.as_deref();
2688        let table_canonical_hint = table_canonical.as_deref();
2689
2690        self.engine.context().drop_trigger(
2691            &trigger_display_name,
2692            &canonical_trigger_name,
2693            table_display_hint,
2694            table_canonical_hint,
2695            if_exists,
2696        )?;
2697
2698        Ok(RuntimeStatementResult::NoOp)
2699    }
2700
2701    fn try_handle_range_ctas(
2702        &self,
2703        display_name: &str,
2704        _canonical_name: &str,
2705        query: &Query,
2706        if_not_exists: bool,
2707        or_replace: bool,
2708        namespace: Option<String>,
2709    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
2710        let select = match query.body.as_ref() {
2711            SetExpr::Select(select) => select,
2712            _ => return Ok(None),
2713        };
2714        if select.from.len() != 1 {
2715            return Ok(None);
2716        }
2717        let table_with_joins = &select.from[0];
2718        if !table_with_joins.joins.is_empty() {
2719            return Ok(None);
2720        }
2721        let (range_size, range_alias) = match &table_with_joins.relation {
2722            TableFactor::Table {
2723                name,
2724                args: Some(args),
2725                alias,
2726                ..
2727            } => {
2728                let func_name = name.to_string().to_ascii_lowercase();
2729                if func_name != "range" {
2730                    return Ok(None);
2731                }
2732                if args.args.len() != 1 {
2733                    return Err(Error::InvalidArgumentError(
2734                        "range table function expects a single argument".into(),
2735                    ));
2736                }
2737                let size_expr = &args.args[0];
2738                let range_size = match size_expr {
2739                    FunctionArg::Unnamed(FunctionArgExpr::Expr(SqlExpr::Value(value))) => {
2740                        match &value.value {
2741                            Value::Number(raw, _) => raw.parse::<i64>().map_err(|e| {
2742                                Error::InvalidArgumentError(format!(
2743                                    "invalid range size literal {}: {}",
2744                                    raw, e
2745                                ))
2746                            })?,
2747                            other => {
2748                                return Err(Error::InvalidArgumentError(format!(
2749                                    "unsupported range size value: {:?}",
2750                                    other
2751                                )));
2752                            }
2753                        }
2754                    }
2755                    _ => {
2756                        return Err(Error::InvalidArgumentError(
2757                            "unsupported range argument".into(),
2758                        ));
2759                    }
2760                };
2761                (range_size, alias.as_ref().map(|a| a.name.value.clone()))
2762            }
2763            _ => return Ok(None),
2764        };
2765
2766        if range_size < 0 {
2767            return Err(Error::InvalidArgumentError(
2768                "range size must be non-negative".into(),
2769            ));
2770        }
2771
2772        if select.projection.is_empty() {
2773            return Err(Error::InvalidArgumentError(
2774                "CREATE TABLE AS SELECT requires at least one projected column".into(),
2775            ));
2776        }
2777
2778        let mut column_specs = Vec::with_capacity(select.projection.len());
2779        let mut column_names = Vec::with_capacity(select.projection.len());
2780        let mut row_template = Vec::with_capacity(select.projection.len());
2781        for item in &select.projection {
2782            match item {
2783                SelectItem::ExprWithAlias { expr, alias } => {
2784                    let (value, data_type) = match expr {
2785                        SqlExpr::Value(value_with_span) => match &value_with_span.value {
2786                            Value::Number(raw, _) => {
2787                                let parsed = raw.parse::<i64>().map_err(|e| {
2788                                    Error::InvalidArgumentError(format!(
2789                                        "invalid numeric literal {}: {}",
2790                                        raw, e
2791                                    ))
2792                                })?;
2793                                (
2794                                    PlanValue::Integer(parsed),
2795                                    arrow::datatypes::DataType::Int64,
2796                                )
2797                            }
2798                            Value::SingleQuotedString(s) => (
2799                                PlanValue::String(s.clone()),
2800                                arrow::datatypes::DataType::Utf8,
2801                            ),
2802                            other => {
2803                                return Err(Error::InvalidArgumentError(format!(
2804                                    "unsupported SELECT expression in range CTAS: {:?}",
2805                                    other
2806                                )));
2807                            }
2808                        },
2809                        SqlExpr::Identifier(ident) => {
2810                            let ident_lower = ident.value.to_ascii_lowercase();
2811                            if range_alias
2812                                .as_ref()
2813                                .map(|a| a.eq_ignore_ascii_case(&ident_lower))
2814                                .unwrap_or(false)
2815                                || ident_lower == "range"
2816                            {
2817                                return Err(Error::InvalidArgumentError(
2818                                    "range() table function columns are not supported yet".into(),
2819                                ));
2820                            }
2821                            return Err(Error::InvalidArgumentError(format!(
2822                                "unsupported identifier '{}' in range CTAS projection",
2823                                ident.value
2824                            )));
2825                        }
2826                        other => {
2827                            return Err(Error::InvalidArgumentError(format!(
2828                                "unsupported SELECT expression in range CTAS: {:?}",
2829                                other
2830                            )));
2831                        }
2832                    };
2833                    let column_name = alias.value.clone();
2834                    column_specs.push(PlanColumnSpec::new(column_name.clone(), data_type, true));
2835                    column_names.push(column_name);
2836                    row_template.push(value);
2837                }
2838                other => {
2839                    return Err(Error::InvalidArgumentError(format!(
2840                        "unsupported projection {:?} in range CTAS",
2841                        other
2842                    )));
2843                }
2844            }
2845        }
2846
2847        let plan = CreateTablePlan {
2848            name: display_name.to_string(),
2849            if_not_exists,
2850            or_replace,
2851            columns: column_specs,
2852            source: None,
2853            namespace,
2854            foreign_keys: Vec::new(),
2855            multi_column_uniques: Vec::new(),
2856        };
2857        let create_result = self.execute_plan_statement(PlanStatement::CreateTable(plan))?;
2858
2859        let row_count = range_size
2860            .try_into()
2861            .map_err(|_| Error::InvalidArgumentError("range size exceeds usize".into()))?;
2862        if row_count > 0 {
2863            let rows = vec![row_template; row_count];
2864            let insert_plan = InsertPlan {
2865                table: display_name.to_string(),
2866                columns: column_names,
2867                source: InsertSource::Rows(rows),
2868                on_conflict: InsertConflictAction::None,
2869            };
2870            self.execute_plan_statement(PlanStatement::Insert(insert_plan))?;
2871        }
2872
2873        Ok(Some(create_result))
2874    }
2875
2876    // TODO: Refactor into runtime or executor layer?
2877    // NOTE: PRAGMA handling lives in the SQL layer until a shared runtime hook exists.
2878    /// Try to handle pragma_table_info('table_name') table function
2879    fn try_handle_pragma_table_info(
2880        &self,
2881        query: &Query,
2882    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
2883        let select = match query.body.as_ref() {
2884            SetExpr::Select(select) => select,
2885            _ => return Ok(None),
2886        };
2887
2888        if select.from.len() != 1 {
2889            return Ok(None);
2890        }
2891
2892        let table_with_joins = &select.from[0];
2893        if !table_with_joins.joins.is_empty() {
2894            return Ok(None);
2895        }
2896
2897        // Check if this is pragma_table_info function call
2898        let table_name = match &table_with_joins.relation {
2899            TableFactor::Table {
2900                name,
2901                args: Some(args),
2902                ..
2903            } => {
2904                let func_name = name.to_string().to_ascii_lowercase();
2905                if func_name != "pragma_table_info" {
2906                    return Ok(None);
2907                }
2908
2909                // Extract table name from argument
2910                if args.args.len() != 1 {
2911                    return Err(Error::InvalidArgumentError(
2912                        "pragma_table_info expects exactly one argument".into(),
2913                    ));
2914                }
2915
2916                match &args.args[0] {
2917                    FunctionArg::Unnamed(FunctionArgExpr::Expr(SqlExpr::Value(value))) => {
2918                        match &value.value {
2919                            Value::SingleQuotedString(s) => s.clone(),
2920                            Value::DoubleQuotedString(s) => s.clone(),
2921                            _ => {
2922                                return Err(Error::InvalidArgumentError(
2923                                    "pragma_table_info argument must be a string".into(),
2924                                ));
2925                            }
2926                        }
2927                    }
2928                    _ => {
2929                        return Err(Error::InvalidArgumentError(
2930                            "pragma_table_info argument must be a string literal".into(),
2931                        ));
2932                    }
2933                }
2934            }
2935            _ => return Ok(None),
2936        };
2937
2938        // Get table column specs from runtime context
2939        let context = self.engine.context();
2940        let (_, canonical_name) = llkv_table::canonical_table_name(&table_name)?;
2941        let columns = context.catalog().table_column_specs(&canonical_name)?;
2942
2943        // Build RecordBatch with table column information
2944        use arrow::array::{BooleanArray, Int32Array, StringArray};
2945        use arrow::datatypes::{DataType, Field, Schema};
2946
2947        let mut cid_values = Vec::new();
2948        let mut name_values = Vec::new();
2949        let mut type_values = Vec::new();
2950        let mut notnull_values = Vec::new();
2951        let mut dflt_value_values: Vec<Option<String>> = Vec::new();
2952        let mut pk_values = Vec::new();
2953
2954        for (idx, col) in columns.iter().enumerate() {
2955            cid_values.push(idx as i32);
2956            name_values.push(col.name.clone());
2957            type_values.push(format!("{:?}", col.data_type)); // Simple type representation
2958            notnull_values.push(!col.nullable);
2959            dflt_value_values.push(None); // We don't track default values yet
2960            pk_values.push(col.primary_key);
2961        }
2962
2963        let schema = Arc::new(Schema::new(vec![
2964            Field::new("cid", DataType::Int32, false),
2965            Field::new("name", DataType::Utf8, false),
2966            Field::new("type", DataType::Utf8, false),
2967            Field::new("notnull", DataType::Boolean, false),
2968            Field::new("dflt_value", DataType::Utf8, true),
2969            Field::new("pk", DataType::Boolean, false),
2970        ]));
2971
2972        use arrow::array::ArrayRef;
2973        let mut batch = RecordBatch::try_new(
2974            Arc::clone(&schema),
2975            vec![
2976                Arc::new(Int32Array::from(cid_values)) as ArrayRef,
2977                Arc::new(StringArray::from(name_values)) as ArrayRef,
2978                Arc::new(StringArray::from(type_values)) as ArrayRef,
2979                Arc::new(BooleanArray::from(notnull_values)) as ArrayRef,
2980                Arc::new(StringArray::from(dflt_value_values)) as ArrayRef,
2981                Arc::new(BooleanArray::from(pk_values)) as ArrayRef,
2982            ],
2983        )
2984        .map_err(|e| Error::Internal(format!("failed to create pragma_table_info batch: {}", e)))?;
2985
2986        // Apply SELECT projections: extract only requested columns
2987        let projection_indices: Vec<usize> = select
2988            .projection
2989            .iter()
2990            .filter_map(|item| {
2991                match item {
2992                    SelectItem::UnnamedExpr(SqlExpr::Identifier(ident)) => {
2993                        schema.index_of(&ident.value).ok()
2994                    }
2995                    SelectItem::ExprWithAlias { expr, .. } => {
2996                        if let SqlExpr::Identifier(ident) = expr {
2997                            schema.index_of(&ident.value).ok()
2998                        } else {
2999                            None
3000                        }
3001                    }
3002                    SelectItem::Wildcard(_) => None, // Handle * separately
3003                    _ => None,
3004                }
3005            })
3006            .collect();
3007
3008        // Apply projections if not SELECT *
3009        let projected_schema;
3010        if !projection_indices.is_empty() {
3011            let projected_fields: Vec<Field> = projection_indices
3012                .iter()
3013                .map(|&idx| schema.field(idx).clone())
3014                .collect();
3015            projected_schema = Arc::new(Schema::new(projected_fields));
3016
3017            let projected_columns: Vec<ArrayRef> = projection_indices
3018                .iter()
3019                .map(|&idx| Arc::clone(batch.column(idx)))
3020                .collect();
3021
3022            batch = RecordBatch::try_new(Arc::clone(&projected_schema), projected_columns)
3023                .map_err(|e| Error::Internal(format!("failed to project columns: {}", e)))?;
3024        } else {
3025            // SELECT * or complex projections - use original schema
3026            projected_schema = schema;
3027        }
3028
3029        // Apply ORDER BY using Arrow compute kernels
3030        if let Some(order_by) = &query.order_by {
3031            use arrow::compute::SortColumn;
3032            use arrow::compute::lexsort_to_indices;
3033            use sqlparser::ast::OrderByKind;
3034
3035            let exprs = match &order_by.kind {
3036                OrderByKind::Expressions(exprs) => exprs,
3037                _ => {
3038                    return Err(Error::InvalidArgumentError(
3039                        "unsupported ORDER BY clause".into(),
3040                    ));
3041                }
3042            };
3043
3044            let mut sort_columns = Vec::new();
3045            for order_expr in exprs {
3046                if let SqlExpr::Identifier(ident) = &order_expr.expr
3047                    && let Ok(col_idx) = projected_schema.index_of(&ident.value)
3048                {
3049                    let options = arrow::compute::SortOptions {
3050                        descending: !order_expr.options.asc.unwrap_or(true),
3051                        nulls_first: order_expr.options.nulls_first.unwrap_or(false),
3052                    };
3053                    sort_columns.push(SortColumn {
3054                        values: Arc::clone(batch.column(col_idx)),
3055                        options: Some(options),
3056                    });
3057                }
3058            }
3059
3060            if !sort_columns.is_empty() {
3061                let indices = lexsort_to_indices(&sort_columns, None)
3062                    .map_err(|e| Error::Internal(format!("failed to sort: {}", e)))?;
3063
3064                use arrow::compute::take;
3065                let sorted_columns: Result<Vec<ArrayRef>, _> = batch
3066                    .columns()
3067                    .iter()
3068                    .map(|col| take(col.as_ref(), &indices, None))
3069                    .collect();
3070
3071                batch = RecordBatch::try_new(
3072                    Arc::clone(&projected_schema),
3073                    sorted_columns
3074                        .map_err(|e| Error::Internal(format!("failed to apply sort: {}", e)))?,
3075                )
3076                .map_err(|e| Error::Internal(format!("failed to create sorted batch: {}", e)))?;
3077            }
3078        }
3079
3080        let execution = SelectExecution::new_single_batch(
3081            table_name.clone(),
3082            Arc::clone(&projected_schema),
3083            batch,
3084        );
3085
3086        Ok(Some(RuntimeStatementResult::Select {
3087            table_name,
3088            schema: projected_schema,
3089            execution: Box::new(execution),
3090        }))
3091    }
3092
3093    fn handle_create_table_as(
3094        &self,
3095        display_name: String,
3096        _canonical_name: String,
3097        query: Query,
3098        if_not_exists: bool,
3099        or_replace: bool,
3100        namespace: Option<String>,
3101    ) -> SqlResult<RuntimeStatementResult<P>> {
3102        // Check if this is a SELECT from VALUES in a derived table
3103        // Pattern: SELECT * FROM (VALUES ...) alias(col1, col2, ...)
3104        if let SetExpr::Select(select) = query.body.as_ref()
3105            && let Some((rows, column_names)) = extract_values_from_derived_table(&select.from)?
3106        {
3107            // Convert VALUES rows to Arrow RecordBatches
3108            return self.handle_create_table_from_values(
3109                display_name,
3110                rows,
3111                column_names,
3112                if_not_exists,
3113                or_replace,
3114                namespace,
3115            );
3116        }
3117
3118        // Regular CTAS with SELECT from existing tables
3119        let select_plan = self.build_select_plan(query)?;
3120
3121        if select_plan.projections.is_empty() && select_plan.aggregates.is_empty() {
3122            return Err(Error::InvalidArgumentError(
3123                "CREATE TABLE AS SELECT requires at least one projected column".into(),
3124            ));
3125        }
3126
3127        let plan = CreateTablePlan {
3128            name: display_name,
3129            if_not_exists,
3130            or_replace,
3131            columns: Vec::new(),
3132            source: Some(CreateTableSource::Select {
3133                plan: Box::new(select_plan),
3134            }),
3135            namespace,
3136            foreign_keys: Vec::new(),
3137            multi_column_uniques: Vec::new(),
3138        };
3139        self.execute_plan_statement(PlanStatement::CreateTable(plan))
3140    }
3141
3142    fn handle_create_table_from_values(
3143        &self,
3144        display_name: String,
3145        rows: Vec<Vec<PlanValue>>,
3146        column_names: Vec<String>,
3147        if_not_exists: bool,
3148        or_replace: bool,
3149        namespace: Option<String>,
3150    ) -> SqlResult<RuntimeStatementResult<P>> {
3151        use arrow::array::{ArrayRef, Float64Builder, Int64Builder, StringBuilder};
3152        use arrow::datatypes::{DataType, Field, Schema};
3153        use arrow::record_batch::RecordBatch;
3154        use std::sync::Arc;
3155
3156        if rows.is_empty() {
3157            return Err(Error::InvalidArgumentError(
3158                "VALUES must have at least one row".into(),
3159            ));
3160        }
3161
3162        let num_cols = column_names.len();
3163
3164        // Infer schema from first row
3165        let first_row = &rows[0];
3166        if first_row.len() != num_cols {
3167            return Err(Error::InvalidArgumentError(
3168                "VALUES row column count mismatch".into(),
3169            ));
3170        }
3171
3172        let mut fields = Vec::with_capacity(num_cols);
3173        let mut column_types = Vec::with_capacity(num_cols);
3174
3175        for (idx, value) in first_row.iter().enumerate() {
3176            let (data_type, nullable) = match value {
3177                PlanValue::Integer(_) => (DataType::Int64, false),
3178                PlanValue::Float(_) => (DataType::Float64, false),
3179                PlanValue::String(_) => (DataType::Utf8, false),
3180                PlanValue::Null => (DataType::Utf8, true), // Default NULL to string type
3181                _ => {
3182                    return Err(Error::InvalidArgumentError(format!(
3183                        "unsupported value type in VALUES for column '{}'",
3184                        column_names.get(idx).unwrap_or(&format!("column{}", idx))
3185                    )));
3186                }
3187            };
3188
3189            column_types.push(data_type.clone());
3190            fields.push(Field::new(&column_names[idx], data_type, nullable));
3191        }
3192
3193        let schema = Arc::new(Schema::new(fields));
3194
3195        // Build Arrow arrays for each column
3196        let mut arrays: Vec<ArrayRef> = Vec::with_capacity(num_cols);
3197
3198        for col_idx in 0..num_cols {
3199            let col_type = &column_types[col_idx];
3200
3201            match col_type {
3202                DataType::Int64 => {
3203                    let mut builder = Int64Builder::with_capacity(rows.len());
3204                    for row in &rows {
3205                        match &row[col_idx] {
3206                            PlanValue::Integer(v) => builder.append_value(*v),
3207                            PlanValue::Null => builder.append_null(),
3208                            other => {
3209                                return Err(Error::InvalidArgumentError(format!(
3210                                    "type mismatch in VALUES: expected Integer, got {:?}",
3211                                    other
3212                                )));
3213                            }
3214                        }
3215                    }
3216                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3217                }
3218                DataType::Float64 => {
3219                    let mut builder = Float64Builder::with_capacity(rows.len());
3220                    for row in &rows {
3221                        match &row[col_idx] {
3222                            PlanValue::Float(v) => builder.append_value(*v),
3223                            PlanValue::Null => builder.append_null(),
3224                            other => {
3225                                return Err(Error::InvalidArgumentError(format!(
3226                                    "type mismatch in VALUES: expected Float, got {:?}",
3227                                    other
3228                                )));
3229                            }
3230                        }
3231                    }
3232                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3233                }
3234                DataType::Utf8 => {
3235                    let mut builder = StringBuilder::with_capacity(rows.len(), 1024);
3236                    for row in &rows {
3237                        match &row[col_idx] {
3238                            PlanValue::String(v) => builder.append_value(v),
3239                            PlanValue::Null => builder.append_null(),
3240                            other => {
3241                                return Err(Error::InvalidArgumentError(format!(
3242                                    "type mismatch in VALUES: expected String, got {:?}",
3243                                    other
3244                                )));
3245                            }
3246                        }
3247                    }
3248                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3249                }
3250                other => {
3251                    return Err(Error::InvalidArgumentError(format!(
3252                        "unsupported column type in VALUES: {:?}",
3253                        other
3254                    )));
3255                }
3256            }
3257        }
3258
3259        let batch = RecordBatch::try_new(Arc::clone(&schema), arrays).map_err(|e| {
3260            Error::Internal(format!("failed to create RecordBatch from VALUES: {}", e))
3261        })?;
3262
3263        let plan = CreateTablePlan {
3264            name: display_name.clone(),
3265            if_not_exists,
3266            or_replace,
3267            columns: Vec::new(),
3268            source: Some(CreateTableSource::Batches {
3269                schema: Arc::clone(&schema),
3270                batches: vec![batch],
3271            }),
3272            namespace,
3273            foreign_keys: Vec::new(),
3274            multi_column_uniques: Vec::new(),
3275        };
3276
3277        self.execute_plan_statement(PlanStatement::CreateTable(plan))
3278    }
3279
3280    fn handle_insert(&self, stmt: sqlparser::ast::Insert) -> SqlResult<RuntimeStatementResult<P>> {
3281        match self.prepare_insert(stmt)? {
3282            PreparedInsert::Values {
3283                table_name,
3284                columns,
3285                rows,
3286                on_conflict,
3287            } => {
3288                tracing::trace!(
3289                    "DEBUG SQL handle_insert executing buffered-values insert for table={}",
3290                    table_name
3291                );
3292                let plan = InsertPlan {
3293                    table: table_name,
3294                    columns,
3295                    source: InsertSource::Rows(rows),
3296                    on_conflict,
3297                };
3298                self.execute_plan_statement(PlanStatement::Insert(plan))
3299            }
3300            PreparedInsert::Immediate(plan) => {
3301                let table_name = plan.table.clone();
3302                tracing::trace!(
3303                    "DEBUG SQL handle_insert executing immediate insert for table={}",
3304                    table_name
3305                );
3306                self.execute_plan_statement(PlanStatement::Insert(plan))
3307            }
3308        }
3309    }
3310
3311    fn handle_update(
3312        &self,
3313        table: TableWithJoins,
3314        assignments: Vec<Assignment>,
3315        from: Option<UpdateTableFromKind>,
3316        selection: Option<SqlExpr>,
3317        returning: Option<Vec<SelectItem>>,
3318    ) -> SqlResult<RuntimeStatementResult<P>> {
3319        if from.is_some() {
3320            return Err(Error::InvalidArgumentError(
3321                "UPDATE ... FROM is not supported yet".into(),
3322            ));
3323        }
3324        if returning.is_some() {
3325            return Err(Error::InvalidArgumentError(
3326                "UPDATE ... RETURNING is not supported".into(),
3327            ));
3328        }
3329        if assignments.is_empty() {
3330            return Err(Error::InvalidArgumentError(
3331                "UPDATE requires at least one assignment".into(),
3332            ));
3333        }
3334
3335        let (display_name, canonical_name) = extract_single_table(std::slice::from_ref(&table))?;
3336
3337        if !self.engine.session().has_active_transaction()
3338            && self
3339                .engine
3340                .context()
3341                .is_table_marked_dropped(&canonical_name)
3342        {
3343            return Err(Error::TransactionContextError(
3344                DROPPED_TABLE_TRANSACTION_ERR.into(),
3345            ));
3346        }
3347
3348        let catalog = self.engine.context().table_catalog();
3349        let resolver = catalog.identifier_resolver();
3350        let table_id = catalog.table_id(&canonical_name);
3351
3352        // Use a HashMap to track column assignments. If a column appears multiple times,
3353        // the last assignment wins (SQLite-compatible behavior).
3354        let mut assignments_map: FxHashMap<String, (String, sqlparser::ast::Expr)> =
3355            FxHashMap::with_capacity_and_hasher(assignments.len(), FxBuildHasher);
3356        for assignment in assignments {
3357            let column_name = resolve_assignment_column_name(&assignment.target)?;
3358            let normalized = column_name.to_ascii_lowercase();
3359            // Store in map - last assignment wins
3360            assignments_map.insert(normalized, (column_name, assignment.value.clone()));
3361        }
3362
3363        let mut column_assignments = Vec::with_capacity(assignments_map.len());
3364        for (_normalized, (column_name, expr)) in assignments_map {
3365            let value = match SqlValue::try_from_expr(&expr) {
3366                Ok(literal) => AssignmentValue::Literal(PlanValue::from(literal)),
3367                Err(Error::InvalidArgumentError(msg))
3368                    if msg.contains("unsupported literal expression") =>
3369                {
3370                    let normalized_expr = self.materialize_in_subquery(expr.clone())?;
3371                    let translated = translate_scalar_with_context(
3372                        &resolver,
3373                        IdentifierContext::new(table_id),
3374                        &normalized_expr,
3375                    )?;
3376                    AssignmentValue::Expression(translated)
3377                }
3378                Err(err) => return Err(err),
3379            };
3380            column_assignments.push(ColumnAssignment {
3381                column: column_name,
3382                value,
3383            });
3384        }
3385
3386        let filter = match selection {
3387            Some(expr) => {
3388                let materialized_expr = self.materialize_in_subquery(expr)?;
3389                let mut subqueries = Vec::new();
3390                let predicate = translate_condition_with_context(
3391                    self,
3392                    &resolver,
3393                    IdentifierContext::new(table_id),
3394                    &materialized_expr,
3395                    &[],
3396                    &mut subqueries,
3397                    None,
3398                )?;
3399                if subqueries.is_empty() {
3400                    Some(predicate)
3401                } else {
3402                    return Err(Error::InvalidArgumentError(
3403                        "EXISTS subqueries are not supported in UPDATE WHERE clauses".into(),
3404                    ));
3405                }
3406            }
3407            None => None,
3408        };
3409
3410        let plan = UpdatePlan {
3411            table: display_name.clone(),
3412            assignments: column_assignments,
3413            filter,
3414        };
3415        self.execute_plan_statement(PlanStatement::Update(plan))
3416    }
3417
3418    #[allow(clippy::collapsible_if)]
3419    fn handle_delete(&self, delete: Delete) -> SqlResult<RuntimeStatementResult<P>> {
3420        let Delete {
3421            tables,
3422            from,
3423            using,
3424            selection,
3425            returning,
3426            order_by,
3427            limit,
3428        } = delete;
3429
3430        if !tables.is_empty() {
3431            return Err(Error::InvalidArgumentError(
3432                "multi-table DELETE is not supported yet".into(),
3433            ));
3434        }
3435        if let Some(using_tables) = using {
3436            if !using_tables.is_empty() {
3437                return Err(Error::InvalidArgumentError(
3438                    "DELETE ... USING is not supported yet".into(),
3439                ));
3440            }
3441        }
3442        if returning.is_some() {
3443            return Err(Error::InvalidArgumentError(
3444                "DELETE ... RETURNING is not supported".into(),
3445            ));
3446        }
3447        if !order_by.is_empty() {
3448            return Err(Error::InvalidArgumentError(
3449                "DELETE ... ORDER BY is not supported yet".into(),
3450            ));
3451        }
3452        if limit.is_some() {
3453            return Err(Error::InvalidArgumentError(
3454                "DELETE ... LIMIT is not supported yet".into(),
3455            ));
3456        }
3457
3458        let from_tables = match from {
3459            FromTable::WithFromKeyword(tables) | FromTable::WithoutKeyword(tables) => tables,
3460        };
3461        let (display_name, canonical_name) = extract_single_table(&from_tables)?;
3462
3463        if !self.engine.session().has_active_transaction()
3464            && self
3465                .engine
3466                .context()
3467                .is_table_marked_dropped(&canonical_name)
3468        {
3469            return Err(Error::TransactionContextError(
3470                DROPPED_TABLE_TRANSACTION_ERR.into(),
3471            ));
3472        }
3473
3474        let catalog = self.engine.context().table_catalog();
3475        let resolver = catalog.identifier_resolver();
3476        let table_id = catalog.table_id(&canonical_name);
3477
3478        let filter = if let Some(expr) = selection {
3479            let materialized_expr = self.materialize_in_subquery(expr)?;
3480            let mut subqueries = Vec::new();
3481            let predicate = translate_condition_with_context(
3482                self,
3483                &resolver,
3484                IdentifierContext::new(table_id),
3485                &materialized_expr,
3486                &[],
3487                &mut subqueries,
3488                None,
3489            )?;
3490            if !subqueries.is_empty() {
3491                return Err(Error::InvalidArgumentError(
3492                    "EXISTS subqueries are not supported in DELETE WHERE clauses".into(),
3493                ));
3494            }
3495            Some(predicate)
3496        } else {
3497            None
3498        };
3499
3500        let plan = DeletePlan {
3501            table: display_name.clone(),
3502            filter,
3503        };
3504        self.execute_plan_statement(PlanStatement::Delete(plan))
3505    }
3506
3507    fn handle_truncate(
3508        &self,
3509        table_names: &[sqlparser::ast::TruncateTableTarget],
3510        partitions: &Option<Vec<SqlExpr>>,
3511        _table: bool, // boolean field in sqlparser, not the table name
3512        identity: &Option<sqlparser::ast::TruncateIdentityOption>,
3513        cascade: Option<sqlparser::ast::CascadeOption>,
3514        on_cluster: &Option<Ident>,
3515    ) -> SqlResult<RuntimeStatementResult<P>> {
3516        // Validate unsupported features
3517        if table_names.len() > 1 {
3518            return Err(Error::InvalidArgumentError(
3519                "TRUNCATE with multiple tables is not supported yet".into(),
3520            ));
3521        }
3522        if partitions.is_some() {
3523            return Err(Error::InvalidArgumentError(
3524                "TRUNCATE ... PARTITION is not supported".into(),
3525            ));
3526        }
3527        if identity.is_some() {
3528            return Err(Error::InvalidArgumentError(
3529                "TRUNCATE ... RESTART/CONTINUE IDENTITY is not supported".into(),
3530            ));
3531        }
3532        use sqlparser::ast::CascadeOption;
3533        if matches!(cascade, Some(CascadeOption::Cascade)) {
3534            return Err(Error::InvalidArgumentError(
3535                "TRUNCATE ... CASCADE is not supported".into(),
3536            ));
3537        }
3538        if on_cluster.is_some() {
3539            return Err(Error::InvalidArgumentError(
3540                "TRUNCATE ... ON CLUSTER is not supported".into(),
3541            ));
3542        }
3543
3544        // Extract table name from table_names
3545        let table_name = if let Some(target) = table_names.first() {
3546            // TruncateTableTarget has a name field
3547            let table_obj = &target.name;
3548            let display_name = table_obj.to_string();
3549            let canonical_name = display_name.to_ascii_lowercase();
3550
3551            // Check if table is dropped in transaction
3552            if !self.engine.session().has_active_transaction()
3553                && self
3554                    .engine
3555                    .context()
3556                    .is_table_marked_dropped(&canonical_name)
3557            {
3558                return Err(Error::TransactionContextError(
3559                    DROPPED_TABLE_TRANSACTION_ERR.into(),
3560                ));
3561            }
3562
3563            display_name
3564        } else {
3565            return Err(Error::InvalidArgumentError(
3566                "TRUNCATE requires a table name".into(),
3567            ));
3568        };
3569
3570        let plan = TruncatePlan {
3571            table: table_name.clone(),
3572        };
3573        self.execute_plan_statement(PlanStatement::Truncate(plan))
3574    }
3575
3576    #[allow(clippy::too_many_arguments)] // NOTE: Signature mirrors SQL grammar; keep grouped until command builder is introduced.
3577    fn handle_drop(
3578        &self,
3579        object_type: ObjectType,
3580        if_exists: bool,
3581        names: Vec<ObjectName>,
3582        cascade: bool,
3583        restrict: bool,
3584        purge: bool,
3585        temporary: bool,
3586    ) -> SqlResult<RuntimeStatementResult<P>> {
3587        if purge || temporary {
3588            return Err(Error::InvalidArgumentError(
3589                "DROP purge/temporary options are not supported".into(),
3590            ));
3591        }
3592
3593        match object_type {
3594            ObjectType::Table => {
3595                if cascade || restrict {
3596                    return Err(Error::InvalidArgumentError(
3597                        "DROP TABLE CASCADE/RESTRICT is not supported".into(),
3598                    ));
3599                }
3600
3601                for name in names {
3602                    let table_name = Self::object_name_to_string(&name)?;
3603                    let mut plan = llkv_plan::DropTablePlan::new(table_name.clone());
3604                    plan.if_exists = if_exists;
3605
3606                    self.execute_plan_statement(llkv_plan::PlanStatement::DropTable(plan))
3607                        .map_err(|err| Self::map_table_error(&table_name, err))?;
3608                }
3609
3610                Ok(RuntimeStatementResult::NoOp)
3611            }
3612            ObjectType::View => {
3613                if cascade || restrict {
3614                    return Err(Error::InvalidArgumentError(
3615                        "DROP VIEW CASCADE/RESTRICT is not supported".into(),
3616                    ));
3617                }
3618
3619                for name in names {
3620                    let view_name = Self::object_name_to_string(&name)?;
3621                    let plan = llkv_plan::DropViewPlan::new(view_name).if_exists(if_exists);
3622                    self.execute_plan_statement(llkv_plan::PlanStatement::DropView(plan))?;
3623                }
3624
3625                Ok(RuntimeStatementResult::NoOp)
3626            }
3627            ObjectType::Index => {
3628                if cascade || restrict {
3629                    return Err(Error::InvalidArgumentError(
3630                        "DROP INDEX CASCADE/RESTRICT is not supported".into(),
3631                    ));
3632                }
3633
3634                for name in names {
3635                    let index_name = Self::object_name_to_string(&name)?;
3636                    let plan = llkv_plan::DropIndexPlan::new(index_name).if_exists(if_exists);
3637                    self.execute_plan_statement(llkv_plan::PlanStatement::DropIndex(plan))?;
3638                }
3639
3640                Ok(RuntimeStatementResult::NoOp)
3641            }
3642            ObjectType::Schema => {
3643                if restrict {
3644                    return Err(Error::InvalidArgumentError(
3645                        "DROP SCHEMA RESTRICT is not supported".into(),
3646                    ));
3647                }
3648
3649                let catalog = self.engine.context().table_catalog();
3650
3651                for name in names {
3652                    let (display_name, canonical_name) = canonical_object_name(&name)?;
3653
3654                    if !catalog.schema_exists(&canonical_name) {
3655                        if if_exists {
3656                            continue;
3657                        }
3658                        return Err(Error::CatalogError(format!(
3659                            "Schema '{}' does not exist",
3660                            display_name
3661                        )));
3662                    }
3663
3664                    if cascade {
3665                        // Drop all tables in this schema
3666                        let all_tables = catalog.table_names();
3667                        let schema_prefix = format!("{}.", canonical_name);
3668
3669                        for table in all_tables {
3670                            if table.to_ascii_lowercase().starts_with(&schema_prefix) {
3671                                let mut plan = llkv_plan::DropTablePlan::new(table.clone());
3672                                plan.if_exists = false;
3673                                self.execute_plan_statement(llkv_plan::PlanStatement::DropTable(
3674                                    plan,
3675                                ))?;
3676                            }
3677                        }
3678                    } else {
3679                        // Check if schema has any tables
3680                        let all_tables = catalog.table_names();
3681                        let schema_prefix = format!("{}.", canonical_name);
3682                        let has_tables = all_tables
3683                            .iter()
3684                            .any(|t| t.to_ascii_lowercase().starts_with(&schema_prefix));
3685
3686                        if has_tables {
3687                            return Err(Error::CatalogError(format!(
3688                                "Schema '{}' is not empty. Use CASCADE to drop schema and all its tables",
3689                                display_name
3690                            )));
3691                        }
3692                    }
3693
3694                    // Drop the schema
3695                    if !catalog.unregister_schema(&canonical_name) && !if_exists {
3696                        return Err(Error::CatalogError(format!(
3697                            "Schema '{}' does not exist",
3698                            display_name
3699                        )));
3700                    }
3701                }
3702
3703                Ok(RuntimeStatementResult::NoOp)
3704            }
3705            _ => Err(Error::InvalidArgumentError(format!(
3706                "DROP {} is not supported",
3707                object_type
3708            ))),
3709        }
3710    }
3711
3712    fn handle_alter_table(
3713        &self,
3714        name: ObjectName,
3715        if_exists: bool,
3716        only: bool,
3717        operations: Vec<AlterTableOperation>,
3718    ) -> SqlResult<RuntimeStatementResult<P>> {
3719        if only {
3720            return Err(Error::InvalidArgumentError(
3721                "ALTER TABLE ONLY is not supported yet".into(),
3722            ));
3723        }
3724
3725        if operations.is_empty() {
3726            return Ok(RuntimeStatementResult::NoOp);
3727        }
3728
3729        if operations.len() != 1 {
3730            return Err(Error::InvalidArgumentError(
3731                "ALTER TABLE currently supports exactly one operation".into(),
3732            ));
3733        }
3734
3735        let operation = operations.into_iter().next().expect("checked length");
3736        match operation {
3737            AlterTableOperation::RenameTable { table_name } => {
3738                let new_name = table_name.to_string();
3739                self.handle_alter_table_rename(name, new_name, if_exists)
3740            }
3741            AlterTableOperation::RenameColumn {
3742                old_column_name,
3743                new_column_name,
3744            } => {
3745                let plan = llkv_plan::AlterTablePlan {
3746                    table_name: name.to_string(),
3747                    if_exists,
3748                    operation: llkv_plan::AlterTableOperation::RenameColumn {
3749                        old_column_name: old_column_name.to_string(),
3750                        new_column_name: new_column_name.to_string(),
3751                    },
3752                };
3753                self.execute_plan_statement(PlanStatement::AlterTable(plan))
3754            }
3755            AlterTableOperation::AlterColumn { column_name, op } => {
3756                // Only support SET DATA TYPE for now
3757                if let AlterColumnOperation::SetDataType {
3758                    data_type,
3759                    using,
3760                    had_set: _,
3761                } = op
3762                {
3763                    if using.is_some() {
3764                        return Err(Error::InvalidArgumentError(
3765                            "ALTER COLUMN SET DATA TYPE USING clause is not yet supported".into(),
3766                        ));
3767                    }
3768
3769                    let plan = llkv_plan::AlterTablePlan {
3770                        table_name: name.to_string(),
3771                        if_exists,
3772                        operation: llkv_plan::AlterTableOperation::SetColumnDataType {
3773                            column_name: column_name.to_string(),
3774                            new_data_type: data_type.to_string(),
3775                        },
3776                    };
3777                    self.execute_plan_statement(PlanStatement::AlterTable(plan))
3778                } else {
3779                    Err(Error::InvalidArgumentError(format!(
3780                        "unsupported ALTER COLUMN operation: {:?}",
3781                        op
3782                    )))
3783                }
3784            }
3785            AlterTableOperation::DropColumn {
3786                has_column_keyword: _,
3787                column_names,
3788                if_exists: column_if_exists,
3789                drop_behavior,
3790            } => {
3791                if column_names.len() != 1 {
3792                    return Err(Error::InvalidArgumentError(
3793                        "DROP COLUMN currently supports dropping one column at a time".into(),
3794                    ));
3795                }
3796
3797                let column_name = column_names.into_iter().next().unwrap().to_string();
3798                let cascade = matches!(drop_behavior, Some(sqlparser::ast::DropBehavior::Cascade));
3799
3800                let plan = llkv_plan::AlterTablePlan {
3801                    table_name: name.to_string(),
3802                    if_exists,
3803                    operation: llkv_plan::AlterTableOperation::DropColumn {
3804                        column_name,
3805                        if_exists: column_if_exists,
3806                        cascade,
3807                    },
3808                };
3809                self.execute_plan_statement(PlanStatement::AlterTable(plan))
3810            }
3811            other => Err(Error::InvalidArgumentError(format!(
3812                "unsupported ALTER TABLE operation: {:?}",
3813                other
3814            ))),
3815        }
3816    }
3817
3818    fn handle_alter_table_rename(
3819        &self,
3820        original_name: ObjectName,
3821        new_table_name: String,
3822        if_exists: bool,
3823    ) -> SqlResult<RuntimeStatementResult<P>> {
3824        let (schema_opt, table_name) = parse_schema_qualified_name(&original_name)?;
3825
3826        let new_table_name_clean = new_table_name.trim();
3827
3828        if new_table_name_clean.is_empty() {
3829            return Err(Error::InvalidArgumentError(
3830                "ALTER TABLE RENAME requires a non-empty table name".into(),
3831            ));
3832        }
3833
3834        let (raw_new_schema_opt, raw_new_table) =
3835            if let Some((schema_part, table_part)) = new_table_name_clean.split_once('.') {
3836                (
3837                    Some(schema_part.trim().to_string()),
3838                    table_part.trim().to_string(),
3839                )
3840            } else {
3841                (None, new_table_name_clean.to_string())
3842            };
3843
3844        if schema_opt.is_none() && raw_new_schema_opt.is_some() {
3845            return Err(Error::InvalidArgumentError(
3846                "ALTER TABLE RENAME cannot add a schema qualifier".into(),
3847            ));
3848        }
3849
3850        let new_table_trimmed = raw_new_table.trim_matches('"');
3851        if new_table_trimmed.is_empty() {
3852            return Err(Error::InvalidArgumentError(
3853                "ALTER TABLE RENAME requires a non-empty table name".into(),
3854            ));
3855        }
3856
3857        if let (Some(existing_schema), Some(new_schema_raw)) =
3858            (schema_opt.as_ref(), raw_new_schema_opt.as_ref())
3859        {
3860            let new_schema_trimmed = new_schema_raw.trim_matches('"');
3861            if !existing_schema.eq_ignore_ascii_case(new_schema_trimmed) {
3862                return Err(Error::InvalidArgumentError(
3863                    "ALTER TABLE RENAME cannot change table schema".into(),
3864                ));
3865            }
3866        }
3867
3868        let new_table_display = raw_new_table;
3869        let new_schema_opt = raw_new_schema_opt;
3870
3871        fn join_schema_table(schema: &str, table: &str) -> String {
3872            let mut qualified = String::with_capacity(schema.len() + table.len() + 1);
3873            qualified.push_str(schema);
3874            qualified.push('.');
3875            qualified.push_str(table);
3876            qualified
3877        }
3878
3879        let current_display = schema_opt
3880            .as_ref()
3881            .map(|schema| join_schema_table(schema, &table_name))
3882            .unwrap_or_else(|| table_name.clone());
3883
3884        let new_display = if let Some(new_schema_raw) = new_schema_opt.clone() {
3885            join_schema_table(&new_schema_raw, &new_table_display)
3886        } else if let Some(schema) = schema_opt.as_ref() {
3887            join_schema_table(schema, &new_table_display)
3888        } else {
3889            new_table_display.clone()
3890        };
3891
3892        let plan = RenameTablePlan::new(&current_display, &new_display).if_exists(if_exists);
3893
3894        match CatalogDdl::rename_table(self.engine.session(), plan) {
3895            Ok(()) => Ok(RuntimeStatementResult::NoOp),
3896            Err(err) => Err(Self::map_table_error(&current_display, err)),
3897        }
3898    }
3899
3900    /// Materialize IN (SELECT ...) subqueries by executing them and converting to IN lists.
3901    ///
3902    /// This preprocesses SQL expressions to execute subqueries and replace them with
3903    /// their materialized results before translation to the execution plan.
3904    ///
3905    /// Uses iterative traversal with an explicit work stack to handle deeply nested
3906    /// expressions without stack overflow.
3907    fn try_materialize_avg_subquery(&self, query: &Query) -> SqlResult<Option<SqlExpr>> {
3908        use sqlparser::ast::{
3909            DuplicateTreatment, FunctionArg, FunctionArgExpr, FunctionArguments, ObjectName,
3910            ObjectNamePart, SelectItem, SetExpr,
3911        };
3912
3913        let select = match query.body.as_ref() {
3914            SetExpr::Select(select) => select.as_ref(),
3915            _ => return Ok(None),
3916        };
3917
3918        if select.projection.len() != 1
3919            || select.distinct.is_some()
3920            || select.top.is_some()
3921            || select.value_table_mode.is_some()
3922            || select.having.is_some()
3923            || !group_by_is_empty(&select.group_by)
3924            || select.into.is_some()
3925            || !select.lateral_views.is_empty()
3926        {
3927            return Ok(None);
3928        }
3929
3930        let func = match &select.projection[0] {
3931            SelectItem::UnnamedExpr(SqlExpr::Function(func)) => func,
3932            _ => return Ok(None),
3933        };
3934
3935        if func.uses_odbc_syntax
3936            || func.filter.is_some()
3937            || func.null_treatment.is_some()
3938            || func.over.is_some()
3939            || !func.within_group.is_empty()
3940        {
3941            return Ok(None);
3942        }
3943
3944        let func_name = func.name.to_string().to_ascii_lowercase();
3945        if func_name != "avg" {
3946            return Ok(None);
3947        }
3948
3949        let args = match &func.args {
3950            FunctionArguments::List(list) => {
3951                if matches!(list.duplicate_treatment, Some(DuplicateTreatment::Distinct))
3952                    || !list.clauses.is_empty()
3953                {
3954                    return Ok(None);
3955                }
3956                &list.args
3957            }
3958            _ => return Ok(None),
3959        };
3960
3961        if args.len() != 1 {
3962            return Ok(None);
3963        }
3964
3965        match &args[0] {
3966            FunctionArg::Unnamed(FunctionArgExpr::Expr(_)) => {}
3967            _ => return Ok(None),
3968        };
3969
3970        let mut sum_query = query.clone();
3971        let mut count_query = query.clone();
3972
3973        let build_replacement = |target_query: &mut Query, name: &str| -> SqlResult<()> {
3974            let select = match target_query.body.as_mut() {
3975                SetExpr::Select(select) => select,
3976                _ => {
3977                    return Err(Error::Internal(
3978                        "expected SELECT query in AVG materialization".into(),
3979                    ));
3980                }
3981            };
3982
3983            let mut replacement_func = func.clone();
3984            replacement_func.name = ObjectName(vec![ObjectNamePart::Identifier(Ident {
3985                value: name.to_string(),
3986                quote_style: None,
3987                span: Span::empty(),
3988            })]);
3989            select.projection = vec![SelectItem::UnnamedExpr(SqlExpr::Function(replacement_func))];
3990            Ok(())
3991        };
3992
3993        build_replacement(&mut sum_query, "sum")?;
3994        build_replacement(&mut count_query, "count")?;
3995
3996        let sum_value = self.execute_scalar_int64(sum_query)?;
3997        let count_value = self.execute_scalar_int64(count_query)?;
3998
3999        let Some(count_value) = count_value else {
4000            return Ok(Some(SqlExpr::Value(ValueWithSpan {
4001                value: Value::Null,
4002                span: Span::empty(),
4003            })));
4004        };
4005
4006        if count_value == 0 {
4007            return Ok(Some(SqlExpr::Value(ValueWithSpan {
4008                value: Value::Null,
4009                span: Span::empty(),
4010            })));
4011        }
4012
4013        let sum_value = match sum_value {
4014            Some(value) => value,
4015            None => {
4016                return Ok(Some(SqlExpr::Value(ValueWithSpan {
4017                    value: Value::Null,
4018                    span: Span::empty(),
4019                })));
4020            }
4021        };
4022
4023        let avg = (sum_value as f64) / (count_value as f64);
4024        let value = ValueWithSpan {
4025            value: Value::Number(avg.to_string(), false),
4026            span: Span::empty(),
4027        };
4028        Ok(Some(SqlExpr::Value(value)))
4029    }
4030
4031    fn execute_scalar_int64(&self, query: Query) -> SqlResult<Option<i64>> {
4032        let result = self.handle_query(query)?;
4033        let execution = match result {
4034            RuntimeStatementResult::Select { execution, .. } => execution,
4035            _ => {
4036                return Err(Error::InvalidArgumentError(
4037                    "scalar aggregate must be a SELECT statement".into(),
4038                ));
4039            }
4040        };
4041
4042        let batches = execution.collect()?;
4043        let mut captured: Option<Option<i64>> = None;
4044
4045        for batch in batches {
4046            if batch.num_columns() == 0 {
4047                continue;
4048            }
4049            if batch.num_columns() != 1 {
4050                return Err(Error::InvalidArgumentError(
4051                    "scalar aggregate must return exactly one column".into(),
4052                ));
4053            }
4054
4055            let array = batch.column(0);
4056            let values = array
4057                .as_any()
4058                .downcast_ref::<arrow::array::Int64Array>()
4059                .ok_or_else(|| {
4060                    Error::InvalidArgumentError(
4061                        "scalar aggregate result must be an INT64 value".into(),
4062                    )
4063                })?;
4064
4065            for idx in 0..values.len() {
4066                if captured.is_some() {
4067                    return Err(Error::InvalidArgumentError(
4068                        "scalar aggregate returned more than one row".into(),
4069                    ));
4070                }
4071                if values.is_null(idx) {
4072                    captured = Some(None);
4073                } else {
4074                    captured = Some(Some(values.value(idx)));
4075                }
4076            }
4077        }
4078
4079        Ok(captured.unwrap_or(None))
4080    }
4081
4082    fn materialize_scalar_subquery(&self, subquery: Query) -> SqlResult<SqlExpr> {
4083        if let Some(avg_literal) = self.try_materialize_avg_subquery(&subquery)? {
4084            return Ok(avg_literal);
4085        }
4086
4087        let result = self.handle_query(subquery)?;
4088        let execution = match result {
4089            RuntimeStatementResult::Select { execution, .. } => execution,
4090            _ => {
4091                return Err(Error::InvalidArgumentError(
4092                    "scalar subquery must be a SELECT statement".into(),
4093                ));
4094            }
4095        };
4096
4097        let batches = execution.collect()?;
4098        let mut captured_value: Option<ValueWithSpan> = None;
4099
4100        for batch in batches {
4101            if batch.num_columns() == 0 {
4102                continue;
4103            }
4104            if batch.num_columns() != 1 {
4105                return Err(Error::InvalidArgumentError(
4106                    "scalar subquery must return exactly one column".into(),
4107                ));
4108            }
4109
4110            let column = batch.column(0);
4111            for row_idx in 0..batch.num_rows() {
4112                if captured_value.is_some() {
4113                    return Err(Error::InvalidArgumentError(
4114                        "scalar subquery returned more than one row".into(),
4115                    ));
4116                }
4117
4118                let value = if column.is_null(row_idx) {
4119                    Value::Null
4120                } else {
4121                    use arrow::array::{BooleanArray, Float64Array, Int64Array, StringArray};
4122                    match column.data_type() {
4123                        arrow::datatypes::DataType::Int64 => {
4124                            let array =
4125                                column
4126                                    .as_any()
4127                                    .downcast_ref::<Int64Array>()
4128                                    .ok_or_else(|| {
4129                                        Error::Internal(
4130                                            "expected Int64 array for scalar subquery".into(),
4131                                        )
4132                                    })?;
4133                            Value::Number(array.value(row_idx).to_string(), false)
4134                        }
4135                        arrow::datatypes::DataType::Float64 => {
4136                            let array = column.as_any().downcast_ref::<Float64Array>().ok_or_else(
4137                                || {
4138                                    Error::Internal(
4139                                        "expected Float64 array for scalar subquery".into(),
4140                                    )
4141                                },
4142                            )?;
4143                            Value::Number(array.value(row_idx).to_string(), false)
4144                        }
4145                        arrow::datatypes::DataType::Utf8 => {
4146                            let array =
4147                                column
4148                                    .as_any()
4149                                    .downcast_ref::<StringArray>()
4150                                    .ok_or_else(|| {
4151                                        Error::Internal(
4152                                            "expected String array for scalar subquery".into(),
4153                                        )
4154                                    })?;
4155                            Value::SingleQuotedString(array.value(row_idx).to_string())
4156                        }
4157                        arrow::datatypes::DataType::Boolean => {
4158                            let array = column.as_any().downcast_ref::<BooleanArray>().ok_or_else(
4159                                || {
4160                                    Error::Internal(
4161                                        "expected Boolean array for scalar subquery".into(),
4162                                    )
4163                                },
4164                            )?;
4165                            Value::Boolean(array.value(row_idx))
4166                        }
4167                        other => {
4168                            return Err(Error::InvalidArgumentError(format!(
4169                                "unsupported data type in scalar subquery result: {other:?}"
4170                            )));
4171                        }
4172                    }
4173                };
4174
4175                captured_value = Some(ValueWithSpan {
4176                    value,
4177                    span: Span::empty(),
4178                });
4179            }
4180        }
4181
4182        let final_value = captured_value.unwrap_or(ValueWithSpan {
4183            value: Value::Null,
4184            span: Span::empty(),
4185        });
4186        Ok(SqlExpr::Value(final_value))
4187    }
4188
4189    fn materialize_in_subquery(&self, root_expr: SqlExpr) -> SqlResult<SqlExpr> {
4190        // Stack-based iterative traversal to avoid recursion
4191        enum WorkItem {
4192            Process(Box<SqlExpr>),
4193            BuildBinaryOp {
4194                op: BinaryOperator,
4195                left: Box<SqlExpr>,
4196                right_done: bool,
4197            },
4198            BuildUnaryOp {
4199                op: UnaryOperator,
4200            },
4201            BuildNested,
4202            BuildIsNull,
4203            BuildIsNotNull,
4204            FinishBetween {
4205                negated: bool,
4206            },
4207        }
4208
4209        let mut work_stack: Vec<WorkItem> = vec![WorkItem::Process(Box::new(root_expr))];
4210        let mut result_stack: Vec<SqlExpr> = Vec::new();
4211
4212        while let Some(item) = work_stack.pop() {
4213            match item {
4214                WorkItem::Process(expr) => {
4215                    match *expr {
4216                        SqlExpr::InSubquery {
4217                            expr: left_expr,
4218                            subquery,
4219                            negated,
4220                        } => {
4221                            // Execute the subquery
4222                            let result = self.handle_query(*subquery)?;
4223
4224                            // Extract values from first column
4225                            let values = match result {
4226                                RuntimeStatementResult::Select { execution, .. } => {
4227                                    let batches = execution.collect()?;
4228                                    let mut collected_values = Vec::new();
4229
4230                                    for batch in batches {
4231                                        if batch.num_columns() == 0 {
4232                                            continue;
4233                                        }
4234                                        if batch.num_columns() > 1 {
4235                                            return Err(Error::InvalidArgumentError(format!(
4236                                                "IN subquery must return exactly one column, got {}",
4237                                                batch.num_columns()
4238                                            )));
4239                                        }
4240                                        let column = batch.column(0);
4241
4242                                        for row_idx in 0..column.len() {
4243                                            use arrow::datatypes::DataType;
4244                                            let value = if column.is_null(row_idx) {
4245                                                Value::Null
4246                                            } else {
4247                                                match column.data_type() {
4248                                                    DataType::Int64 => {
4249                                                        let arr = column
4250                                                        .as_any()
4251                                                        .downcast_ref::<arrow::array::Int64Array>()
4252                                                        .unwrap();
4253                                                        Value::Number(
4254                                                            arr.value(row_idx).to_string(),
4255                                                            false,
4256                                                        )
4257                                                    }
4258                                                    DataType::Float64 => {
4259                                                        let arr = column
4260                                                        .as_any()
4261                                                        .downcast_ref::<arrow::array::Float64Array>()
4262                                                        .unwrap();
4263                                                        Value::Number(
4264                                                            arr.value(row_idx).to_string(),
4265                                                            false,
4266                                                        )
4267                                                    }
4268                                                    DataType::Utf8 => {
4269                                                        let arr = column
4270                                                        .as_any()
4271                                                        .downcast_ref::<arrow::array::StringArray>()
4272                                                        .unwrap();
4273                                                        Value::SingleQuotedString(
4274                                                            arr.value(row_idx).to_string(),
4275                                                        )
4276                                                    }
4277                                                    DataType::Boolean => {
4278                                                        let arr = column
4279                                                        .as_any()
4280                                                        .downcast_ref::<arrow::array::BooleanArray>()
4281                                                        .unwrap();
4282                                                        Value::Boolean(arr.value(row_idx))
4283                                                    }
4284                                                    other => {
4285                                                        return Err(Error::InvalidArgumentError(
4286                                                            format!(
4287                                                                "unsupported data type in IN subquery: {other:?}"
4288                                                            ),
4289                                                        ));
4290                                                    }
4291                                                }
4292                                            };
4293                                            collected_values.push(ValueWithSpan {
4294                                                value,
4295                                                span: Span::empty(),
4296                                            });
4297                                        }
4298                                    }
4299
4300                                    collected_values
4301                                }
4302                                _ => {
4303                                    return Err(Error::InvalidArgumentError(
4304                                        "IN subquery must be a SELECT statement".into(),
4305                                    ));
4306                                }
4307                            };
4308
4309                            // Convert to IN list with materialized values
4310                            result_stack.push(SqlExpr::InList {
4311                                expr: left_expr,
4312                                list: values.into_iter().map(SqlExpr::Value).collect(),
4313                                negated,
4314                            });
4315                        }
4316                        SqlExpr::Subquery(subquery) => {
4317                            let scalar_expr = self.materialize_scalar_subquery(*subquery)?;
4318                            result_stack.push(scalar_expr);
4319                        }
4320                        SqlExpr::Case {
4321                            case_token,
4322                            end_token,
4323                            operand,
4324                            conditions,
4325                            else_result,
4326                        } => {
4327                            let new_operand = match operand {
4328                                Some(expr) => Some(Box::new(self.materialize_in_subquery(*expr)?)),
4329                                None => None,
4330                            };
4331                            let mut new_conditions = Vec::with_capacity(conditions.len());
4332                            for branch in conditions {
4333                                let condition = self.materialize_in_subquery(branch.condition)?;
4334                                let result = self.materialize_in_subquery(branch.result)?;
4335                                new_conditions.push(sqlparser::ast::CaseWhen { condition, result });
4336                            }
4337                            let new_else = match else_result {
4338                                Some(expr) => Some(Box::new(self.materialize_in_subquery(*expr)?)),
4339                                None => None,
4340                            };
4341                            result_stack.push(SqlExpr::Case {
4342                                case_token,
4343                                end_token,
4344                                operand: new_operand,
4345                                conditions: new_conditions,
4346                                else_result: new_else,
4347                            });
4348                        }
4349                        SqlExpr::BinaryOp { left, op, right } => {
4350                            // Push builder, then schedule the left operand followed by the right.
4351                            // Because the work stack is LIFO, the right-hand side executes first,
4352                            // leaving the left result on top for the builder to consume without
4353                            // swapping operand order.
4354                            work_stack.push(WorkItem::BuildBinaryOp {
4355                                op,
4356                                left: left.clone(),
4357                                right_done: false,
4358                            });
4359                            // Evaluate the right-hand side first so the left result remains on top
4360                            // of the result stack when the builder consumes it. This preserves the
4361                            // original operand ordering when reconstructing the expression tree.
4362                            work_stack.push(WorkItem::Process(left));
4363                            work_stack.push(WorkItem::Process(right));
4364                        }
4365                        SqlExpr::UnaryOp { op, expr } => {
4366                            work_stack.push(WorkItem::BuildUnaryOp { op });
4367                            work_stack.push(WorkItem::Process(expr));
4368                        }
4369                        SqlExpr::Nested(inner) => {
4370                            work_stack.push(WorkItem::BuildNested);
4371                            work_stack.push(WorkItem::Process(inner));
4372                        }
4373                        SqlExpr::IsNull(inner) => {
4374                            work_stack.push(WorkItem::BuildIsNull);
4375                            work_stack.push(WorkItem::Process(inner));
4376                        }
4377                        SqlExpr::IsNotNull(inner) => {
4378                            work_stack.push(WorkItem::BuildIsNotNull);
4379                            work_stack.push(WorkItem::Process(inner));
4380                        }
4381                        SqlExpr::Between {
4382                            expr,
4383                            negated,
4384                            low,
4385                            high,
4386                        } => {
4387                            work_stack.push(WorkItem::FinishBetween { negated });
4388                            work_stack.push(WorkItem::Process(high));
4389                            work_stack.push(WorkItem::Process(low));
4390                            work_stack.push(WorkItem::Process(expr));
4391                        }
4392                        // All other expressions: push as-is to result stack
4393                        other => {
4394                            result_stack.push(other);
4395                        }
4396                    }
4397                }
4398                WorkItem::BuildBinaryOp {
4399                    op,
4400                    left,
4401                    right_done,
4402                } => {
4403                    if !right_done {
4404                        // Left done, now mark that right will be done next
4405                        let left_result = result_stack.pop().unwrap();
4406                        work_stack.push(WorkItem::BuildBinaryOp {
4407                            op,
4408                            left: Box::new(left_result),
4409                            right_done: true,
4410                        });
4411                    } else {
4412                        // Both done, build the BinaryOp
4413                        let right_result = result_stack.pop().unwrap();
4414                        let left_result = *left;
4415                        result_stack.push(SqlExpr::BinaryOp {
4416                            left: Box::new(left_result),
4417                            op,
4418                            right: Box::new(right_result),
4419                        });
4420                    }
4421                }
4422                WorkItem::BuildUnaryOp { op } => {
4423                    let inner = result_stack.pop().unwrap();
4424                    result_stack.push(SqlExpr::UnaryOp {
4425                        op,
4426                        expr: Box::new(inner),
4427                    });
4428                }
4429                WorkItem::BuildNested => {
4430                    let inner = result_stack.pop().unwrap();
4431                    result_stack.push(SqlExpr::Nested(Box::new(inner)));
4432                }
4433                WorkItem::BuildIsNull => {
4434                    let inner = result_stack.pop().unwrap();
4435                    result_stack.push(SqlExpr::IsNull(Box::new(inner)));
4436                }
4437                WorkItem::BuildIsNotNull => {
4438                    let inner = result_stack.pop().unwrap();
4439                    result_stack.push(SqlExpr::IsNotNull(Box::new(inner)));
4440                }
4441                WorkItem::FinishBetween { negated } => {
4442                    let high_result = result_stack.pop().unwrap();
4443                    let low_result = result_stack.pop().unwrap();
4444                    let expr_result = result_stack.pop().unwrap();
4445                    result_stack.push(SqlExpr::Between {
4446                        expr: Box::new(expr_result),
4447                        negated,
4448                        low: Box::new(low_result),
4449                        high: Box::new(high_result),
4450                    });
4451                }
4452            }
4453        }
4454
4455        // Final result should be the only item on the result stack
4456        Ok(result_stack
4457            .pop()
4458            .expect("result stack should have exactly one item"))
4459    }
4460
4461    fn handle_query(&self, query: Query) -> SqlResult<RuntimeStatementResult<P>> {
4462        let mut visited_views = FxHashSet::default();
4463        self.execute_query_with_view_support(query, &mut visited_views)
4464    }
4465
4466    fn execute_query_with_view_support(
4467        &self,
4468        query: Query,
4469        visited_views: &mut FxHashSet<String>,
4470    ) -> SqlResult<RuntimeStatementResult<P>> {
4471        if let Some(result) = self.try_execute_simple_view_select(&query, visited_views)? {
4472            return Ok(result);
4473        }
4474
4475        if let Some(result) = self.try_execute_view_set_operation(&query, visited_views)? {
4476            return Ok(result);
4477        }
4478
4479        if let Some(result) = self.try_execute_simple_derived_select(&query, visited_views)? {
4480            return Ok(result);
4481        }
4482
4483        // Check for pragma_table_info() table function first
4484        if let Some(result) = self.try_handle_pragma_table_info(&query)? {
4485            return Ok(result);
4486        }
4487
4488        let select_plan = self.build_select_plan(query)?;
4489        self.execute_plan_statement(PlanStatement::Select(Box::new(select_plan)))
4490    }
4491
4492    fn try_execute_simple_view_select(
4493        &self,
4494        query: &Query,
4495        visited_views: &mut FxHashSet<String>,
4496    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
4497        use sqlparser::ast::SetExpr;
4498
4499        // Reject complex query forms upfront.
4500        if query.with.is_some() || query.order_by.is_some() || query.limit_clause.is_some() {
4501            return Ok(None);
4502        }
4503
4504        let select = match query.body.as_ref() {
4505            SetExpr::Select(select) => select,
4506            _ => return Ok(None),
4507        };
4508
4509        if select.distinct.is_some()
4510            || select.selection.is_some()
4511            || !group_by_is_empty(&select.group_by)
4512            || select.having.is_some()
4513            || !select.cluster_by.is_empty()
4514            || !select.distribute_by.is_empty()
4515            || !select.sort_by.is_empty()
4516            || select.top.is_some()
4517            || select.value_table_mode.is_some()
4518            || !select.named_window.is_empty()
4519            || select.qualify.is_some()
4520            || select.connect_by.is_some()
4521        {
4522            return Ok(None);
4523        }
4524
4525        if select.from.len() != 1 {
4526            return Ok(None);
4527        }
4528
4529        let table_with_joins = &select.from[0];
4530        if table_with_joins_has_join(table_with_joins) {
4531            return Ok(None);
4532        }
4533
4534        let (view_display_name, view_canonical_name, table_alias) = match &table_with_joins.relation
4535        {
4536            TableFactor::Table { name, alias, .. } => {
4537                let (display, canonical) = canonical_object_name(name)?;
4538                let catalog = self.engine.context().table_catalog();
4539                let Some(table_id) = catalog.table_id(&canonical) else {
4540                    return Ok(None);
4541                };
4542                if !self.engine.context().is_view(table_id)? {
4543                    return Ok(None);
4544                }
4545                let alias_name = alias.as_ref().map(|a| a.name.value.clone());
4546                (display, canonical, alias_name)
4547            }
4548            _ => return Ok(None),
4549        };
4550
4551        // Gather projection mapping for simple column selection.
4552        enum ProjectionKind {
4553            All,
4554            Columns(Vec<(String, String)>), // (source column, output name)
4555        }
4556
4557        let projection = if select
4558            .projection
4559            .iter()
4560            .any(|item| matches!(item, SelectItem::Wildcard(_)))
4561        {
4562            if select.projection.len() != 1 {
4563                return Ok(None);
4564            }
4565            ProjectionKind::All
4566        } else {
4567            let mut columns = Vec::with_capacity(select.projection.len());
4568            for item in &select.projection {
4569                match item {
4570                    SelectItem::UnnamedExpr(SqlExpr::Identifier(ident)) => {
4571                        let name = ident.value.clone();
4572                        columns.push((name.clone(), name));
4573                    }
4574                    SelectItem::ExprWithAlias { expr, alias } => {
4575                        let source = match expr {
4576                            SqlExpr::Identifier(ident) => ident.value.clone(),
4577                            SqlExpr::CompoundIdentifier(idents) => {
4578                                if idents.is_empty() {
4579                                    return Ok(None);
4580                                }
4581                                idents.last().unwrap().value.clone()
4582                            }
4583                            _ => return Ok(None),
4584                        };
4585                        columns.push((source, alias.value.clone()));
4586                    }
4587                    SelectItem::UnnamedExpr(SqlExpr::CompoundIdentifier(parts)) => {
4588                        if parts.is_empty() {
4589                            return Ok(None);
4590                        }
4591                        // Allow optional table qualifier matching alias or view name.
4592                        if parts.len() == 2 {
4593                            let qualifier = parts[0].value.to_ascii_lowercase();
4594                            if let Some(ref alias_name) = table_alias {
4595                                if qualifier != alias_name.to_ascii_lowercase() {
4596                                    return Ok(None);
4597                                }
4598                            } else if qualifier != view_display_name.to_ascii_lowercase() {
4599                                return Ok(None);
4600                            }
4601                        } else if parts.len() != 1 {
4602                            return Ok(None);
4603                        }
4604                        let column_name = parts.last().unwrap().value.clone();
4605                        columns.push((column_name.clone(), column_name));
4606                    }
4607                    _ => return Ok(None),
4608                }
4609            }
4610            ProjectionKind::Columns(columns)
4611        };
4612
4613        let context = self.engine.context();
4614        let definition = context
4615            .view_definition(&view_canonical_name)?
4616            .ok_or_else(|| {
4617                Error::CatalogError(format!(
4618                    "Binder Error: view '{}' does not have a stored definition",
4619                    view_display_name
4620                ))
4621            })?;
4622
4623        if !visited_views.insert(view_canonical_name.clone()) {
4624            return Err(Error::CatalogError(format!(
4625                "Binder Error: cyclic view reference involving '{}'",
4626                view_display_name
4627            )));
4628        }
4629
4630        let view_query = Self::parse_view_query(&definition)?;
4631        let view_result = self.execute_query_with_view_support(view_query, visited_views);
4632        visited_views.remove(&view_canonical_name);
4633        let view_result = view_result?;
4634
4635        let RuntimeStatementResult::Select {
4636            execution: view_execution,
4637            schema: view_schema,
4638            ..
4639        } = view_result
4640        else {
4641            return Err(Error::InvalidArgumentError(format!(
4642                "view '{}' definition did not produce a SELECT result",
4643                view_display_name
4644            )));
4645        };
4646
4647        match projection {
4648            ProjectionKind::All => {
4649                // Reuse the original execution and schema.
4650                let select_result = RuntimeStatementResult::Select {
4651                    execution: view_execution,
4652                    table_name: view_display_name,
4653                    schema: view_schema,
4654                };
4655                Ok(Some(select_result))
4656            }
4657            ProjectionKind::Columns(columns) => {
4658                let exec = *view_execution;
4659                let batches = exec.collect()?;
4660                let view_fields = view_schema.fields();
4661                let mut name_to_index =
4662                    FxHashMap::with_capacity_and_hasher(view_fields.len(), Default::default());
4663                for (idx, field) in view_fields.iter().enumerate() {
4664                    name_to_index.insert(field.name().to_ascii_lowercase(), idx);
4665                }
4666
4667                let mut column_indices = Vec::with_capacity(columns.len());
4668                let mut projected_fields = Vec::with_capacity(columns.len());
4669
4670                for (source, output) in columns {
4671                    let lookup = source.to_ascii_lowercase();
4672                    let Some(&idx) = name_to_index.get(&lookup) else {
4673                        return Err(Error::InvalidArgumentError(format!(
4674                            "Binder Error: view '{}' does not have a column named '{}'",
4675                            view_display_name, source
4676                        )));
4677                    };
4678                    column_indices.push(idx);
4679                    let origin_field = view_fields[idx].clone();
4680                    let projected_field = Field::new(
4681                        &output,
4682                        origin_field.data_type().clone(),
4683                        origin_field.is_nullable(),
4684                    )
4685                    .with_metadata(origin_field.metadata().clone());
4686                    projected_fields.push(projected_field);
4687                }
4688
4689                let projected_schema = Arc::new(Schema::new(projected_fields));
4690
4691                let mut projected_batches = Vec::with_capacity(batches.len());
4692                for batch in batches {
4693                    let mut arrays: Vec<ArrayRef> = Vec::with_capacity(column_indices.len());
4694                    for idx in &column_indices {
4695                        arrays.push(Arc::clone(batch.column(*idx)));
4696                    }
4697                    let projected = RecordBatch::try_new(Arc::clone(&projected_schema), arrays)?;
4698                    projected_batches.push(projected);
4699                }
4700
4701                let combined_batch = if projected_batches.is_empty() {
4702                    RecordBatch::new_empty(Arc::clone(&projected_schema))
4703                } else if projected_batches.len() == 1 {
4704                    projected_batches.remove(0)
4705                } else {
4706                    concat_batches(&projected_schema, projected_batches.iter())?
4707                };
4708
4709                let select_execution = SelectExecution::from_batch(
4710                    view_display_name.clone(),
4711                    Arc::clone(&projected_schema),
4712                    combined_batch,
4713                );
4714
4715                Ok(Some(RuntimeStatementResult::Select {
4716                    execution: Box::new(select_execution),
4717                    table_name: view_display_name,
4718                    schema: projected_schema,
4719                }))
4720            }
4721        }
4722    }
4723
4724    fn try_execute_simple_derived_select(
4725        &self,
4726        query: &Query,
4727        visited_views: &mut FxHashSet<String>,
4728    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
4729        use sqlparser::ast::{Expr as SqlExpr, SelectItem, SetExpr};
4730
4731        // Support only plain SELECT queries without WITH, ORDER BY, LIMIT, or FETCH clauses.
4732        if query.with.is_some() || query.order_by.is_some() || query.limit_clause.is_some() {
4733            return Ok(None);
4734        }
4735        if query.fetch.is_some() {
4736            return Ok(None);
4737        }
4738
4739        let select = match query.body.as_ref() {
4740            SetExpr::Select(select) => select,
4741            _ => return Ok(None),
4742        };
4743
4744        if select.from.len() != 1 {
4745            return Ok(None);
4746        }
4747
4748        let table_with_joins = &select.from[0];
4749        let (subquery, alias, lateral) = match &table_with_joins.relation {
4750            TableFactor::Derived {
4751                subquery,
4752                alias,
4753                lateral,
4754                ..
4755            } => (subquery, alias.as_ref(), *lateral),
4756            _ => return Ok(None),
4757        };
4758
4759        if table_with_joins_has_join(table_with_joins) {
4760            return Err(Error::InvalidArgumentError(
4761                "Binder Error: derived table queries with JOINs are not supported yet".into(),
4762            ));
4763        }
4764
4765        if lateral {
4766            return Err(Error::InvalidArgumentError(
4767                "Binder Error: LATERAL derived tables are not supported yet".into(),
4768            ));
4769        }
4770        if select.distinct.is_some()
4771            || select.selection.is_some()
4772            || !group_by_is_empty(&select.group_by)
4773            || select.having.is_some()
4774            || !select.cluster_by.is_empty()
4775            || !select.distribute_by.is_empty()
4776            || !select.sort_by.is_empty()
4777            || select.top.is_some()
4778            || select.value_table_mode.is_some()
4779            || !select.named_window.is_empty()
4780            || select.qualify.is_some()
4781            || select.connect_by.is_some()
4782        {
4783            return Err(Error::InvalidArgumentError(
4784                "Binder Error: advanced clauses are not supported for derived table queries yet"
4785                    .into(),
4786            ));
4787        }
4788
4789        let inner_query = *subquery.clone();
4790        let inner_result = self.execute_query_with_view_support(inner_query, visited_views)?;
4791        let (inner_exec, inner_schema, inner_table_name) =
4792            self.extract_select_result(inner_result)?;
4793
4794        let alias_name = alias.map(|a| a.name.value.clone());
4795        let alias_columns = alias.and_then(|a| {
4796            if a.columns.is_empty() {
4797                None
4798            } else {
4799                Some(
4800                    a.columns
4801                        .iter()
4802                        .map(|col| col.name.value.clone())
4803                        .collect::<Vec<_>>(),
4804                )
4805            }
4806        });
4807
4808        if let Some(ref columns) = alias_columns
4809            && columns.len() != inner_schema.fields().len()
4810        {
4811            return Err(Error::InvalidArgumentError(
4812                "Binder Error: derived table column alias count must match projection".into(),
4813            ));
4814        }
4815
4816        let alias_lower = alias_name.as_ref().map(|name| name.to_ascii_lowercase());
4817        let inner_lower = inner_table_name.to_ascii_lowercase();
4818
4819        enum DerivedProjection {
4820            All,
4821            Columns(Vec<(String, String)>),
4822        }
4823
4824        let resolve_compound_identifier = |parts: &[Ident]| -> SqlResult<String> {
4825            if parts.is_empty() {
4826                return Err(Error::InvalidArgumentError(
4827                    "Binder Error: empty identifier in derived table projection".into(),
4828                ));
4829            }
4830            if parts.len() == 1 {
4831                return Ok(parts[0].value.clone());
4832            }
4833            if parts.len() == 2 {
4834                let qualifier_lower = parts[0].value.to_ascii_lowercase();
4835                let qualifier_display = parts[0].value.clone();
4836                if let Some(ref alias_lower) = alias_lower {
4837                    if qualifier_lower != *alias_lower {
4838                        return Err(Error::InvalidArgumentError(format!(
4839                            "Binder Error: derived table qualifier '{}' does not match alias '{}'",
4840                            qualifier_display,
4841                            alias_name.as_deref().unwrap_or(""),
4842                        )));
4843                    }
4844                } else if qualifier_lower != inner_lower {
4845                    return Err(Error::InvalidArgumentError(format!(
4846                        "Binder Error: derived table qualifier '{}' does not match subquery name '{}'",
4847                        qualifier_display, inner_table_name
4848                    )));
4849                }
4850                return Ok(parts[1].value.clone());
4851            }
4852            Err(Error::InvalidArgumentError(
4853                "Binder Error: multi-part qualified identifiers are not supported for derived tables yet"
4854                    .into(),
4855            ))
4856        };
4857
4858        let build_projection_columns = |items: &[SelectItem]| -> SqlResult<Vec<(String, String)>> {
4859            let mut columns = Vec::with_capacity(items.len());
4860            for item in items {
4861                match item {
4862                    SelectItem::UnnamedExpr(SqlExpr::Identifier(ident)) => {
4863                        let name = ident.value.clone();
4864                        columns.push((name.clone(), name));
4865                    }
4866                    SelectItem::ExprWithAlias { expr, alias } => {
4867                        let source = match expr {
4868                            SqlExpr::Identifier(ident) => ident.value.clone(),
4869                            SqlExpr::CompoundIdentifier(parts) => {
4870                                resolve_compound_identifier(parts)?
4871                            }
4872                            _ => {
4873                                return Err(Error::InvalidArgumentError(
4874                                    "Binder Error: complex expressions in derived table projections are not supported yet"
4875                                        .into(),
4876                                ));
4877                            }
4878                        };
4879                        columns.push((source, alias.value.clone()))
4880                    }
4881                    SelectItem::UnnamedExpr(SqlExpr::CompoundIdentifier(parts)) => {
4882                        let column = resolve_compound_identifier(parts)?;
4883                        columns.push((column.clone(), column));
4884                    }
4885                    other => {
4886                        return Err(Error::InvalidArgumentError(format!(
4887                            "Binder Error: unsupported derived table projection {:?}",
4888                            other
4889                        )));
4890                    }
4891                }
4892            }
4893            Ok(columns)
4894        };
4895
4896        let projection = if select.projection.len() == 1 {
4897            match &select.projection[0] {
4898                SelectItem::Wildcard(_) => DerivedProjection::All,
4899                SelectItem::QualifiedWildcard(kind, _) => match kind {
4900                    SelectItemQualifiedWildcardKind::ObjectName(name) => {
4901                        let qualifier = Self::object_name_to_string(name)?;
4902                        let qualifier_lower = qualifier.to_ascii_lowercase();
4903                        if let Some(ref alias_lower) = alias_lower {
4904                            if qualifier_lower != *alias_lower {
4905                                return Err(Error::InvalidArgumentError(format!(
4906                                    "Binder Error: derived table qualifier '{}' does not match alias '{}'",
4907                                    qualifier,
4908                                    alias_name.as_deref().unwrap_or(""),
4909                                )));
4910                            }
4911                        } else if qualifier_lower != inner_lower {
4912                            return Err(Error::InvalidArgumentError(format!(
4913                                "Binder Error: derived table qualifier '{}' does not match subquery name '{}'",
4914                                qualifier, inner_table_name
4915                            )));
4916                        }
4917                        DerivedProjection::All
4918                    }
4919                    SelectItemQualifiedWildcardKind::Expr(_) => {
4920                        return Err(Error::InvalidArgumentError(
4921                                "Binder Error: expression-qualified wildcards are not supported for derived tables yet"
4922                                    .into(),
4923                            ));
4924                    }
4925                },
4926                _ => DerivedProjection::Columns(build_projection_columns(&select.projection)?),
4927            }
4928        } else {
4929            if select.projection.iter().any(|item| {
4930                matches!(
4931                    item,
4932                    SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _)
4933                )
4934            }) {
4935                return Err(Error::InvalidArgumentError(
4936                    "Binder Error: derived table projections cannot mix wildcards with explicit columns"
4937                        .into(),
4938                ));
4939            }
4940            DerivedProjection::Columns(build_projection_columns(&select.projection)?)
4941        };
4942
4943        let mut batches = inner_exec.collect()?;
4944        let output_table_name = alias_name.clone().unwrap_or(inner_table_name.clone());
4945
4946        let mut name_to_index = FxHashMap::default();
4947        for (idx, field) in inner_schema.fields().iter().enumerate() {
4948            name_to_index.insert(field.name().to_ascii_lowercase(), idx);
4949        }
4950        if let Some(ref columns) = alias_columns {
4951            for (idx, alias_col) in columns.iter().enumerate() {
4952                name_to_index.insert(alias_col.to_ascii_lowercase(), idx);
4953            }
4954        }
4955
4956        let mut build_projected_result = |column_mappings: Vec<(String, String)>| -> SqlResult<_> {
4957            let mut column_indices = Vec::with_capacity(column_mappings.len());
4958            let mut projected_fields = Vec::with_capacity(column_mappings.len());
4959
4960            for (source, output) in column_mappings {
4961                let key = source.to_ascii_lowercase();
4962                let Some(&idx) = name_to_index.get(&key) else {
4963                    return Err(Error::InvalidArgumentError(format!(
4964                        "Binder Error: derived table does not provide a column named '{}'",
4965                        source
4966                    )));
4967                };
4968                column_indices.push(idx);
4969                let origin_field = inner_schema.field(idx).clone();
4970                let projected_field = Field::new(
4971                    &output,
4972                    origin_field.data_type().clone(),
4973                    origin_field.is_nullable(),
4974                )
4975                .with_metadata(origin_field.metadata().clone());
4976                projected_fields.push(projected_field);
4977            }
4978
4979            let projected_schema = Arc::new(Schema::new(projected_fields));
4980            let mut projected_batches = Vec::with_capacity(batches.len());
4981            for batch in batches.drain(..) {
4982                let mut arrays: Vec<ArrayRef> = Vec::with_capacity(column_indices.len());
4983                for idx in &column_indices {
4984                    arrays.push(Arc::clone(batch.column(*idx)));
4985                }
4986                let projected = RecordBatch::try_new(Arc::clone(&projected_schema), arrays)
4987                    .map_err(|err| {
4988                        Error::Internal(format!(
4989                            "failed to construct derived table projection batch: {err}"
4990                        ))
4991                    })?;
4992                projected_batches.push(projected);
4993            }
4994
4995            let combined_batch = if projected_batches.is_empty() {
4996                RecordBatch::new_empty(Arc::clone(&projected_schema))
4997            } else if projected_batches.len() == 1 {
4998                projected_batches.remove(0)
4999            } else {
5000                concat_batches(&projected_schema, projected_batches.iter()).map_err(|err| {
5001                    Error::Internal(format!(
5002                        "failed to concatenate derived table batches: {err}"
5003                    ))
5004                })?
5005            };
5006
5007            Ok((projected_schema, combined_batch))
5008        };
5009
5010        let (final_schema, combined_batch) = match projection {
5011            DerivedProjection::All => {
5012                if let Some(columns) = alias_columns {
5013                    let mappings = columns
5014                        .iter()
5015                        .map(|name| (name.clone(), name.clone()))
5016                        .collect::<Vec<_>>();
5017                    build_projected_result(mappings)?
5018                } else {
5019                    let schema = Arc::clone(&inner_schema);
5020                    let combined = if batches.is_empty() {
5021                        RecordBatch::new_empty(Arc::clone(&schema))
5022                    } else if batches.len() == 1 {
5023                        batches.remove(0)
5024                    } else {
5025                        concat_batches(&schema, batches.iter()).map_err(|err| {
5026                            Error::Internal(format!(
5027                                "failed to concatenate derived table batches: {err}"
5028                            ))
5029                        })?
5030                    };
5031                    (schema, combined)
5032                }
5033            }
5034            DerivedProjection::Columns(mappings) => build_projected_result(mappings)?,
5035        };
5036
5037        let execution = SelectExecution::from_batch(
5038            output_table_name.clone(),
5039            Arc::clone(&final_schema),
5040            combined_batch,
5041        );
5042
5043        Ok(Some(RuntimeStatementResult::Select {
5044            execution: Box::new(execution),
5045            table_name: output_table_name,
5046            schema: final_schema,
5047        }))
5048    }
5049
5050    fn try_execute_view_set_operation(
5051        &self,
5052        query: &Query,
5053        visited_views: &mut FxHashSet<String>,
5054    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
5055        if !matches!(query.body.as_ref(), SetExpr::SetOperation { .. }) {
5056            return Ok(None);
5057        }
5058
5059        if query.with.is_some()
5060            || query.order_by.is_some()
5061            || query.limit_clause.is_some()
5062            || query.fetch.is_some()
5063        {
5064            return Ok(None);
5065        }
5066
5067        if !self.set_expr_contains_view(query.body.as_ref())? {
5068            return Ok(None);
5069        }
5070
5071        let result = self.evaluate_set_expr(query.body.as_ref(), visited_views)?;
5072        Ok(Some(result))
5073    }
5074
5075    fn evaluate_set_expr(
5076        &self,
5077        expr: &SetExpr,
5078        visited_views: &mut FxHashSet<String>,
5079    ) -> SqlResult<RuntimeStatementResult<P>> {
5080        match expr {
5081            SetExpr::SetOperation {
5082                left,
5083                right,
5084                op,
5085                set_quantifier,
5086            } => {
5087                let left_result = self.evaluate_set_expr(left.as_ref(), visited_views)?;
5088                let right_result = self.evaluate_set_expr(right.as_ref(), visited_views)?;
5089                self.combine_set_results(left_result, right_result, *op, *set_quantifier)
5090            }
5091            SetExpr::Query(subquery) => {
5092                self.execute_query_with_view_support(*subquery.clone(), visited_views)
5093            }
5094            _ => self.execute_setexpr_query(expr, visited_views),
5095        }
5096    }
5097
5098    fn execute_setexpr_query(
5099        &self,
5100        expr: &SetExpr,
5101        visited_views: &mut FxHashSet<String>,
5102    ) -> SqlResult<RuntimeStatementResult<P>> {
5103        let sql = expr.to_string();
5104        let dialect = GenericDialect {};
5105        let statements = parse_sql_with_recursion_limit(&dialect, &sql).map_err(|err| {
5106            Error::InvalidArgumentError(format!(
5107                "failed to parse expanded view query '{sql}': {err}"
5108            ))
5109        })?;
5110
5111        let mut iter = statements.into_iter();
5112        let statement = iter.next().ok_or_else(|| {
5113            Error::InvalidArgumentError("expanded view query did not produce a statement".into())
5114        })?;
5115        if iter.next().is_some() {
5116            return Err(Error::InvalidArgumentError(
5117                "expanded view query produced multiple statements".into(),
5118            ));
5119        }
5120
5121        let query = match statement {
5122            Statement::Query(q) => *q,
5123            other => {
5124                return Err(Error::InvalidArgumentError(format!(
5125                    "expanded view query did not produce a SELECT statement: {other:?}"
5126                )));
5127            }
5128        };
5129
5130        self.execute_query_with_view_support(query, visited_views)
5131    }
5132
5133    fn combine_set_results(
5134        &self,
5135        left: RuntimeStatementResult<P>,
5136        right: RuntimeStatementResult<P>,
5137        op: SetOperator,
5138        quantifier: SetQuantifier,
5139    ) -> SqlResult<RuntimeStatementResult<P>> {
5140        match op {
5141            SetOperator::Union => self.union_select_results(left, right, quantifier),
5142            other => Err(Error::InvalidArgumentError(format!(
5143                "Binder Error: unsupported set operator {other:?} in view query"
5144            ))),
5145        }
5146    }
5147
5148    fn union_select_results(
5149        &self,
5150        left: RuntimeStatementResult<P>,
5151        right: RuntimeStatementResult<P>,
5152        quantifier: SetQuantifier,
5153    ) -> SqlResult<RuntimeStatementResult<P>> {
5154        let (left_exec, left_schema, left_name) = self.extract_select_result(left)?;
5155        let (right_exec, right_schema, _) = self.extract_select_result(right)?;
5156
5157        self.ensure_schemas_compatible(&left_schema, &right_schema)?;
5158
5159        let mut batches = Vec::new();
5160        batches.extend(left_exec.collect()?);
5161        batches.extend(right_exec.collect()?);
5162
5163        let mut combined_batch = if batches.is_empty() {
5164            RecordBatch::new_empty(Arc::clone(&left_schema))
5165        } else if batches.len() == 1 {
5166            batches.pop().expect("length checked above")
5167        } else {
5168            concat_batches(&left_schema, batches.iter()).map_err(|err| {
5169                Error::Internal(format!("failed to concatenate UNION batches: {err}"))
5170            })?
5171        };
5172
5173        if matches!(quantifier, SetQuantifier::Distinct) {
5174            combined_batch = self.distinct_batch(&left_schema, combined_batch)?;
5175        }
5176
5177        let execution = SelectExecution::from_batch(
5178            left_name.clone(),
5179            Arc::clone(&left_schema),
5180            combined_batch,
5181        );
5182
5183        Ok(RuntimeStatementResult::Select {
5184            execution: Box::new(execution),
5185            table_name: left_name,
5186            schema: left_schema,
5187        })
5188    }
5189
5190    fn extract_select_result(
5191        &self,
5192        result: RuntimeStatementResult<P>,
5193    ) -> SqlResult<(SelectExecution<P>, Arc<Schema>, String)> {
5194        match result {
5195            RuntimeStatementResult::Select {
5196                execution,
5197                schema,
5198                table_name,
5199            } => Ok((*execution, schema, table_name)),
5200            _ => Err(Error::InvalidArgumentError(
5201                "expected SELECT result while evaluating set operation".into(),
5202            )),
5203        }
5204    }
5205
5206    fn ensure_schemas_compatible(&self, left: &Arc<Schema>, right: &Arc<Schema>) -> SqlResult<()> {
5207        if left.fields().len() != right.fields().len() {
5208            return Err(Error::InvalidArgumentError(
5209                "Binder Error: UNION inputs project different column counts".into(),
5210            ));
5211        }
5212
5213        for (idx, (l_field, r_field)) in left.fields().iter().zip(right.fields().iter()).enumerate()
5214        {
5215            if l_field.data_type() != r_field.data_type() {
5216                return Err(Error::InvalidArgumentError(format!(
5217                    "Binder Error: UNION column {} type mismatch ({:?} vs {:?})",
5218                    idx + 1,
5219                    l_field.data_type(),
5220                    r_field.data_type()
5221                )));
5222            }
5223        }
5224
5225        Ok(())
5226    }
5227
5228    fn distinct_batch(&self, schema: &Arc<Schema>, batch: RecordBatch) -> SqlResult<RecordBatch> {
5229        if batch.num_rows() <= 1 {
5230            return Ok(batch);
5231        }
5232
5233        let sort_fields: Vec<SortField> = schema
5234            .fields()
5235            .iter()
5236            .map(|field| SortField::new(field.data_type().clone()))
5237            .collect();
5238
5239        let converter = RowConverter::new(sort_fields)
5240            .map_err(|err| Error::Internal(format!("failed to initialize row converter: {err}")))?;
5241        let rows = converter
5242            .convert_columns(batch.columns())
5243            .map_err(|err| Error::Internal(format!("failed to row-encode union result: {err}")))?;
5244
5245        let mut seen = FxHashSet::default();
5246        let mut indices = Vec::new();
5247        let mut has_duplicates = false;
5248        for (idx, row) in rows.iter().enumerate() {
5249            if seen.insert(row) {
5250                indices.push(idx as u32);
5251            } else {
5252                has_duplicates = true;
5253            }
5254        }
5255
5256        if !has_duplicates {
5257            return Ok(batch);
5258        }
5259
5260        let index_array = UInt32Array::from(indices);
5261        let mut columns = Vec::with_capacity(batch.num_columns());
5262        for column in batch.columns() {
5263            let taken = take(column.as_ref(), &index_array, None).map_err(|err| {
5264                Error::Internal(format!("failed to materialize DISTINCT rows: {err}"))
5265            })?;
5266            columns.push(taken);
5267        }
5268
5269        RecordBatch::try_new(Arc::clone(schema), columns)
5270            .map_err(|err| Error::Internal(format!("failed to build DISTINCT RecordBatch: {err}")))
5271    }
5272
5273    fn set_expr_contains_view(&self, expr: &SetExpr) -> SqlResult<bool> {
5274        match expr {
5275            SetExpr::Select(select) => self.select_contains_view(select.as_ref()),
5276            SetExpr::Query(query) => self.set_expr_contains_view(&query.body),
5277            SetExpr::SetOperation { left, right, .. } => Ok(self
5278                .set_expr_contains_view(left.as_ref())?
5279                || self.set_expr_contains_view(right.as_ref())?),
5280            _ => Ok(false),
5281        }
5282    }
5283
5284    fn select_contains_view(&self, select: &Select) -> SqlResult<bool> {
5285        for from_item in &select.from {
5286            if self.table_with_joins_contains_view(from_item)? {
5287                return Ok(true);
5288            }
5289        }
5290        Ok(false)
5291    }
5292
5293    fn table_with_joins_contains_view(&self, table: &TableWithJoins) -> SqlResult<bool> {
5294        if self.table_factor_contains_view(&table.relation)? {
5295            return Ok(true);
5296        }
5297
5298        for join in &table.joins {
5299            if self.table_factor_contains_view(&join.relation)? {
5300                return Ok(true);
5301            }
5302        }
5303
5304        Ok(false)
5305    }
5306
5307    fn table_factor_contains_view(&self, factor: &TableFactor) -> SqlResult<bool> {
5308        match factor {
5309            TableFactor::Table { name, .. } => {
5310                let (_, canonical) = canonical_object_name(name)?;
5311                let catalog = self.engine.context().table_catalog();
5312                let Some(table_id) = catalog.table_id(&canonical) else {
5313                    return Ok(false);
5314                };
5315                self.engine.context().is_view(table_id)
5316            }
5317            TableFactor::Derived { subquery, .. } => self.set_expr_contains_view(&subquery.body),
5318            TableFactor::NestedJoin {
5319                table_with_joins, ..
5320            } => self.table_with_joins_contains_view(table_with_joins),
5321            _ => Ok(false),
5322        }
5323    }
5324
5325    fn build_select_plan(&self, query: Query) -> SqlResult<SelectPlan> {
5326        if self.engine.session().has_active_transaction() && self.engine.session().is_aborted() {
5327            return Err(Error::TransactionContextError(
5328                "TransactionContext Error: transaction is aborted".into(),
5329            ));
5330        }
5331
5332        validate_simple_query(&query)?;
5333        let catalog = self.engine.context().table_catalog();
5334        let resolver = catalog.identifier_resolver();
5335
5336        let (mut select_plan, select_context) =
5337            self.translate_query_body(query.body.as_ref(), &resolver)?;
5338        if let Some(order_by) = &query.order_by {
5339            if !select_plan.aggregates.is_empty() {
5340                return Err(Error::InvalidArgumentError(
5341                    "ORDER BY is not supported for aggregate queries".into(),
5342                ));
5343            }
5344            let order_plan = self.translate_order_by(&resolver, select_context, order_by)?;
5345            select_plan = select_plan.with_order_by(order_plan);
5346        }
5347        Ok(select_plan)
5348    }
5349
5350    /// Internal version of build_select_plan that supports correlated subqueries.
5351    ///
5352    /// # Parameters
5353    /// - `query`: The SQL query to translate
5354    /// - `resolver`: Identifier resolver for column lookups
5355    /// - `outer_scopes`: Stack of outer query contexts for correlated column resolution
5356    /// - `subqueries`: Accumulator for EXISTS subqueries encountered during translation
5357    /// - `correlated_tracker`: Optional tracker for recording correlated column references
5358    fn build_select_plan_internal(
5359        &self,
5360        query: Query,
5361        resolver: &IdentifierResolver<'_>,
5362        outer_scopes: &[IdentifierContext],
5363        subqueries: &mut Vec<llkv_plan::FilterSubquery>,
5364        correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
5365    ) -> SqlResult<SelectPlan> {
5366        if self.engine.session().has_active_transaction() && self.engine.session().is_aborted() {
5367            return Err(Error::TransactionContextError(
5368                "TransactionContext Error: transaction is aborted".into(),
5369            ));
5370        }
5371
5372        validate_simple_query(&query)?;
5373
5374        let (mut select_plan, select_context) = self.translate_query_body_internal(
5375            query.body.as_ref(),
5376            resolver,
5377            outer_scopes,
5378            subqueries,
5379            correlated_tracker,
5380        )?;
5381        if let Some(order_by) = &query.order_by {
5382            if !select_plan.aggregates.is_empty() {
5383                return Err(Error::InvalidArgumentError(
5384                    "ORDER BY is not supported for aggregate queries".into(),
5385                ));
5386            }
5387            let order_plan = self.translate_order_by(resolver, select_context, order_by)?;
5388            select_plan = select_plan.with_order_by(order_plan);
5389        }
5390        Ok(select_plan)
5391    }
5392
5393    fn translate_select(
5394        &self,
5395        select: &Select,
5396        resolver: &IdentifierResolver<'_>,
5397    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
5398        let mut subqueries = Vec::new();
5399        let result =
5400            self.translate_select_internal(select, resolver, &[], &mut subqueries, None)?;
5401        if !subqueries.is_empty() {
5402            return Err(Error::Internal(
5403                "translate_select: unexpected subqueries from non-correlated translation".into(),
5404            ));
5405        }
5406        Ok(result)
5407    }
5408
5409    fn translate_query_body(
5410        &self,
5411        body: &SetExpr,
5412        resolver: &IdentifierResolver<'_>,
5413    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
5414        let mut subqueries = Vec::new();
5415        let result =
5416            self.translate_query_body_internal(body, resolver, &[], &mut subqueries, None)?;
5417        if !subqueries.is_empty() {
5418            return Err(Error::Internal(
5419                "translate_query_body: unexpected subqueries from non-correlated translation"
5420                    .into(),
5421            ));
5422        }
5423        Ok(result)
5424    }
5425
5426    fn translate_query_body_internal(
5427        &self,
5428        body: &SetExpr,
5429        resolver: &IdentifierResolver<'_>,
5430        outer_scopes: &[IdentifierContext],
5431        subqueries: &mut Vec<llkv_plan::FilterSubquery>,
5432        mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
5433    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
5434        match body {
5435            SetExpr::Select(select) => self.translate_select_internal(
5436                select.as_ref(),
5437                resolver,
5438                outer_scopes,
5439                subqueries,
5440                correlated_tracker,
5441            ),
5442            SetExpr::Query(query) => self.translate_query_body_internal(
5443                &query.body,
5444                resolver,
5445                outer_scopes,
5446                subqueries,
5447                correlated_tracker,
5448            ),
5449            SetExpr::SetOperation {
5450                left,
5451                right,
5452                op,
5453                set_quantifier,
5454            } => {
5455                let left_tracker = correlated_tracker.reborrow();
5456                let (left_plan, left_context) = self.translate_query_body_internal(
5457                    left.as_ref(),
5458                    resolver,
5459                    outer_scopes,
5460                    subqueries,
5461                    left_tracker,
5462                )?;
5463
5464                let right_tracker = correlated_tracker.reborrow();
5465                let (right_plan, _) = self.translate_query_body_internal(
5466                    right.as_ref(),
5467                    resolver,
5468                    outer_scopes,
5469                    subqueries,
5470                    right_tracker,
5471                )?;
5472
5473                let operator = match op {
5474                    sqlparser::ast::SetOperator::Union => llkv_plan::CompoundOperator::Union,
5475                    sqlparser::ast::SetOperator::Intersect => {
5476                        llkv_plan::CompoundOperator::Intersect
5477                    }
5478                    sqlparser::ast::SetOperator::Except | sqlparser::ast::SetOperator::Minus => {
5479                        llkv_plan::CompoundOperator::Except
5480                    }
5481                };
5482
5483                let quantifier = match set_quantifier {
5484                    SetQuantifier::All => llkv_plan::CompoundQuantifier::All,
5485                    _ => llkv_plan::CompoundQuantifier::Distinct,
5486                };
5487
5488                let mut compound = if let Some(existing) = left_plan.compound {
5489                    existing
5490                } else {
5491                    llkv_plan::CompoundSelectPlan::new(left_plan)
5492                };
5493                compound.push_operation(operator, quantifier, right_plan);
5494
5495                let result_plan = SelectPlan::new("").with_compound(compound);
5496
5497                Ok((result_plan, left_context))
5498            }
5499            other => Err(Error::InvalidArgumentError(format!(
5500                "unsupported query expression: {other:?}"
5501            ))),
5502        }
5503    }
5504
5505    fn translate_select_internal(
5506        &self,
5507        select: &Select,
5508        resolver: &IdentifierResolver<'_>,
5509        outer_scopes: &[IdentifierContext],
5510        subqueries: &mut Vec<llkv_plan::FilterSubquery>,
5511        mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
5512    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
5513        let mut distinct = match &select.distinct {
5514            None => false,
5515            Some(Distinct::Distinct) => true,
5516            Some(Distinct::On(_)) => {
5517                return Err(Error::InvalidArgumentError(
5518                    "SELECT DISTINCT ON is not supported".into(),
5519                ));
5520            }
5521        };
5522        if matches!(
5523            select.value_table_mode,
5524            Some(
5525                sqlparser::ast::ValueTableMode::DistinctAsStruct
5526                    | sqlparser::ast::ValueTableMode::DistinctAsValue
5527            )
5528        ) {
5529            distinct = true;
5530        }
5531        if select.top.is_some() {
5532            return Err(Error::InvalidArgumentError(
5533                "SELECT TOP is not supported".into(),
5534            ));
5535        }
5536        if select.exclude.is_some() {
5537            return Err(Error::InvalidArgumentError(
5538                "SELECT EXCLUDE is not supported".into(),
5539            ));
5540        }
5541        if select.into.is_some() {
5542            return Err(Error::InvalidArgumentError(
5543                "SELECT INTO is not supported".into(),
5544            ));
5545        }
5546        if !select.lateral_views.is_empty() {
5547            return Err(Error::InvalidArgumentError(
5548                "LATERAL VIEW is not supported".into(),
5549            ));
5550        }
5551        if select.prewhere.is_some() {
5552            return Err(Error::InvalidArgumentError(
5553                "PREWHERE is not supported".into(),
5554            ));
5555        }
5556        if !select.cluster_by.is_empty()
5557            || !select.distribute_by.is_empty()
5558            || !select.sort_by.is_empty()
5559        {
5560            return Err(Error::InvalidArgumentError(
5561                "CLUSTER/DISTRIBUTE/SORT BY clauses are not supported".into(),
5562            ));
5563        }
5564        if !select.named_window.is_empty()
5565            || select.qualify.is_some()
5566            || select.connect_by.is_some()
5567        {
5568            return Err(Error::InvalidArgumentError(
5569                "advanced SELECT clauses are not supported".into(),
5570            ));
5571        }
5572
5573        let table_alias = select
5574            .from
5575            .first()
5576            .and_then(|table_with_joins| match &table_with_joins.relation {
5577                TableFactor::Table { alias, .. } => alias.as_ref().map(|a| a.name.value.clone()),
5578                _ => None,
5579            });
5580
5581        let has_joins = select.from.iter().any(table_with_joins_has_join);
5582        let mut join_conditions: Vec<Option<SqlExpr>> = Vec::new();
5583        let mut scalar_subqueries: Vec<llkv_plan::ScalarSubquery> = Vec::new();
5584        // Handle different FROM clause scenarios
5585        let catalog = self.engine.context().table_catalog();
5586        let has_group_by = !group_by_is_empty(&select.group_by);
5587        let (mut plan, id_context) = if select.from.is_empty() {
5588            if has_group_by {
5589                return Err(Error::InvalidArgumentError(
5590                    "GROUP BY requires a FROM clause".into(),
5591                ));
5592            }
5593            // No FROM clause - use empty string for table context (e.g., SELECT 42, SELECT {'a': 1} AS x)
5594            let mut p = SelectPlan::new("");
5595            let projections = self.build_projection_list(
5596                resolver,
5597                IdentifierContext::new(None),
5598                &select.projection,
5599                outer_scopes,
5600                &mut scalar_subqueries,
5601                correlated_tracker.reborrow(),
5602            )?;
5603            p = p.with_projections(projections);
5604            (p, IdentifierContext::new(None))
5605        } else if select.from.len() == 1 && !has_joins {
5606            // Single table query
5607            let (display_name, canonical_name) = extract_single_table(&select.from)?;
5608            let table_id = catalog.table_id(&canonical_name);
5609            let mut p = SelectPlan::new(display_name.clone());
5610            let single_table_context =
5611                IdentifierContext::new(table_id).with_table_alias(table_alias.clone());
5612            if let Some(alias) = table_alias.as_ref() {
5613                validate_projection_alias_qualifiers(&select.projection, alias)?;
5614            }
5615            if !has_group_by
5616                && let Some(aggregates) = self.detect_simple_aggregates(&select.projection)?
5617            {
5618                p = p.with_aggregates(aggregates);
5619            } else {
5620                let projections = self.build_projection_list(
5621                    resolver,
5622                    single_table_context.clone(),
5623                    &select.projection,
5624                    outer_scopes,
5625                    &mut scalar_subqueries,
5626                    correlated_tracker.reborrow(),
5627                )?;
5628                p = p.with_projections(projections);
5629            }
5630            (p, single_table_context)
5631        } else {
5632            // Multiple tables or explicit joins - treat as cross product for now
5633            let (tables, join_metadata, extracted_filters) = extract_tables(&select.from)?;
5634            join_conditions = extracted_filters;
5635            let mut p = SelectPlan::with_tables(tables).with_joins(join_metadata);
5636            // For multi-table queries, we'll build projections differently
5637            // For now, just handle simple column references
5638            let projections = self.build_projection_list(
5639                resolver,
5640                IdentifierContext::new(None),
5641                &select.projection,
5642                outer_scopes,
5643                &mut scalar_subqueries,
5644                correlated_tracker.reborrow(),
5645            )?;
5646            p = p.with_projections(projections);
5647            (p, IdentifierContext::new(None))
5648        };
5649
5650        let mut filter_components: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
5651        let mut all_subqueries = Vec::new();
5652
5653        if let Some(expr) = &select.selection {
5654            let materialized_expr = self.materialize_in_subquery(expr.clone())?;
5655            filter_components.push(translate_condition_with_context(
5656                self,
5657                resolver,
5658                id_context.clone(),
5659                &materialized_expr,
5660                outer_scopes,
5661                &mut all_subqueries,
5662                correlated_tracker.reborrow(),
5663            )?);
5664        }
5665
5666        // Translate JOIN ON predicates and attach them to the join metadata. INNER joins can
5667        // safely push these predicates into the WHERE clause, while LEFT joins must retain
5668        // them on the join so that unmatched left rows are preserved.
5669        for (idx, join_expr_opt) in join_conditions.iter().enumerate() {
5670            let Some(join_expr) = join_expr_opt else {
5671                continue;
5672            };
5673
5674            let materialized_expr = self.materialize_in_subquery(join_expr.clone())?;
5675            let translated = translate_condition_with_context(
5676                self,
5677                resolver,
5678                id_context.clone(),
5679                &materialized_expr,
5680                outer_scopes,
5681                &mut all_subqueries,
5682                correlated_tracker.reborrow(),
5683            )?;
5684
5685            let is_left_join = plan
5686                .joins
5687                .get(idx)
5688                .map(|j| j.join_type == llkv_plan::JoinPlan::Left)
5689                .unwrap_or(false);
5690
5691            if let Some(join_meta) = plan.joins.get_mut(idx) {
5692                join_meta.on_condition = Some(translated.clone());
5693            }
5694
5695            if !is_left_join {
5696                filter_components.push(translated);
5697            }
5698        }
5699
5700        let having_expr = if let Some(having) = &select.having {
5701            let materialized_expr = self.materialize_in_subquery(having.clone())?;
5702            let translated = translate_condition_with_context(
5703                self,
5704                resolver,
5705                id_context.clone(),
5706                &materialized_expr,
5707                outer_scopes,
5708                &mut all_subqueries,
5709                correlated_tracker.reborrow(),
5710            )?;
5711            Some(translated)
5712        } else {
5713            None
5714        };
5715
5716        subqueries.append(&mut all_subqueries);
5717
5718        let filter = match filter_components.len() {
5719            0 => None,
5720            1 if subqueries.is_empty() => Some(llkv_plan::SelectFilter {
5721                predicate: filter_components.into_iter().next().unwrap(),
5722                subqueries: Vec::new(),
5723            }),
5724            1 => Some(llkv_plan::SelectFilter {
5725                predicate: filter_components.into_iter().next().unwrap(),
5726                subqueries: std::mem::take(subqueries),
5727            }),
5728            _ => Some(llkv_plan::SelectFilter {
5729                predicate: llkv_expr::expr::Expr::And(filter_components),
5730                subqueries: std::mem::take(subqueries),
5731            }),
5732        };
5733        plan = plan.with_filter(filter);
5734        plan = plan.with_having(having_expr);
5735        plan = plan.with_scalar_subqueries(std::mem::take(&mut scalar_subqueries));
5736        plan = plan.with_distinct(distinct);
5737
5738        let group_by_columns = if has_group_by {
5739            self.translate_group_by_columns(resolver, id_context.clone(), &select.group_by)?
5740        } else {
5741            Vec::new()
5742        };
5743        plan = plan.with_group_by(group_by_columns);
5744
5745        let value_mode = select.value_table_mode.map(convert_value_table_mode);
5746        plan = plan.with_value_table_mode(value_mode);
5747        Ok((plan, id_context))
5748    }
5749
5750    fn translate_order_by(
5751        &self,
5752        resolver: &IdentifierResolver<'_>,
5753        id_context: IdentifierContext,
5754        order_by: &OrderBy,
5755    ) -> SqlResult<Vec<OrderByPlan>> {
5756        let exprs = match &order_by.kind {
5757            OrderByKind::Expressions(exprs) => exprs,
5758            _ => {
5759                return Err(Error::InvalidArgumentError(
5760                    "unsupported ORDER BY clause".into(),
5761                ));
5762            }
5763        };
5764
5765        let base_nulls_first = self.default_nulls_first.load(AtomicOrdering::Relaxed);
5766
5767        let mut plans = Vec::with_capacity(exprs.len());
5768        for order_expr in exprs {
5769            let ascending = order_expr.options.asc.unwrap_or(true);
5770            let default_nulls_first_for_direction = if ascending {
5771                base_nulls_first
5772            } else {
5773                !base_nulls_first
5774            };
5775            let nulls_first = order_expr
5776                .options
5777                .nulls_first
5778                .unwrap_or(default_nulls_first_for_direction);
5779
5780            if let SqlExpr::Identifier(ident) = &order_expr.expr
5781                && ident.value.eq_ignore_ascii_case("ALL")
5782                && ident.quote_style.is_none()
5783            {
5784                plans.push(OrderByPlan {
5785                    target: OrderTarget::All,
5786                    sort_type: OrderSortType::Native,
5787                    ascending,
5788                    nulls_first,
5789                });
5790                continue;
5791            }
5792
5793            let (target, sort_type) = match &order_expr.expr {
5794                SqlExpr::Identifier(_) | SqlExpr::CompoundIdentifier(_) => (
5795                    OrderTarget::Column(self.resolve_simple_column_expr(
5796                        resolver,
5797                        id_context.clone(),
5798                        &order_expr.expr,
5799                    )?),
5800                    OrderSortType::Native,
5801                ),
5802                SqlExpr::Cast {
5803                    expr,
5804                    data_type:
5805                        SqlDataType::Int(_)
5806                        | SqlDataType::Integer(_)
5807                        | SqlDataType::BigInt(_)
5808                        | SqlDataType::SmallInt(_)
5809                        | SqlDataType::TinyInt(_),
5810                    ..
5811                } => (
5812                    OrderTarget::Column(self.resolve_simple_column_expr(
5813                        resolver,
5814                        id_context.clone(),
5815                        expr,
5816                    )?),
5817                    OrderSortType::CastTextToInteger,
5818                ),
5819                SqlExpr::Cast { data_type, .. } => {
5820                    return Err(Error::InvalidArgumentError(format!(
5821                        "ORDER BY CAST target type {:?} is not supported",
5822                        data_type
5823                    )));
5824                }
5825                SqlExpr::Value(value_with_span) => match &value_with_span.value {
5826                    Value::Number(raw, _) => {
5827                        let position: usize = raw.parse().map_err(|_| {
5828                            Error::InvalidArgumentError(format!(
5829                                "ORDER BY position '{}' is not a valid positive integer",
5830                                raw
5831                            ))
5832                        })?;
5833                        if position == 0 {
5834                            return Err(Error::InvalidArgumentError(
5835                                "ORDER BY position must be at least 1".into(),
5836                            ));
5837                        }
5838                        (OrderTarget::Index(position - 1), OrderSortType::Native)
5839                    }
5840                    other => {
5841                        return Err(Error::InvalidArgumentError(format!(
5842                            "unsupported ORDER BY literal expression: {other:?}"
5843                        )));
5844                    }
5845                },
5846                other => {
5847                    return Err(Error::InvalidArgumentError(format!(
5848                        "unsupported ORDER BY expression: {other:?}"
5849                    )));
5850                }
5851            };
5852
5853            plans.push(OrderByPlan {
5854                target,
5855                sort_type,
5856                ascending,
5857                nulls_first,
5858            });
5859        }
5860
5861        Ok(plans)
5862    }
5863
5864    fn translate_group_by_columns(
5865        &self,
5866        resolver: &IdentifierResolver<'_>,
5867        id_context: IdentifierContext,
5868        group_by: &GroupByExpr,
5869    ) -> SqlResult<Vec<String>> {
5870        use sqlparser::ast::Expr as SqlExpr;
5871
5872        match group_by {
5873            GroupByExpr::All(_) => Err(Error::InvalidArgumentError(
5874                "GROUP BY ALL is not supported".into(),
5875            )),
5876            GroupByExpr::Expressions(exprs, modifiers) => {
5877                if !modifiers.is_empty() {
5878                    return Err(Error::InvalidArgumentError(
5879                        "GROUP BY modifiers are not supported".into(),
5880                    ));
5881                }
5882                let mut columns = Vec::with_capacity(exprs.len());
5883                for expr in exprs {
5884                    let parts: Vec<String> = match expr {
5885                        SqlExpr::Identifier(ident) => vec![ident.value.clone()],
5886                        SqlExpr::CompoundIdentifier(idents) => {
5887                            idents.iter().map(|id| id.value.clone()).collect()
5888                        }
5889                        _ => {
5890                            return Err(Error::InvalidArgumentError(
5891                                "GROUP BY expressions must be simple column references".into(),
5892                            ));
5893                        }
5894                    };
5895                    let resolution = resolver.resolve(&parts, id_context.clone())?;
5896                    if !resolution.is_simple() {
5897                        return Err(Error::InvalidArgumentError(
5898                            "GROUP BY nested field references are not supported".into(),
5899                        ));
5900                    }
5901                    columns.push(resolution.column().to_string());
5902                }
5903                Ok(columns)
5904            }
5905        }
5906    }
5907
5908    fn resolve_simple_column_expr(
5909        &self,
5910        resolver: &IdentifierResolver<'_>,
5911        context: IdentifierContext,
5912        expr: &SqlExpr,
5913    ) -> SqlResult<String> {
5914        let normalized_expr = self.materialize_in_subquery(expr.clone())?;
5915        let scalar = translate_scalar_with_context(resolver, context, &normalized_expr)?;
5916        match scalar {
5917            llkv_expr::expr::ScalarExpr::Column(column) => Ok(column),
5918            other => Err(Error::InvalidArgumentError(format!(
5919                "ORDER BY expression must reference a simple column, found {other:?}"
5920            ))),
5921        }
5922    }
5923
5924    fn detect_simple_aggregates(
5925        &self,
5926        projection_items: &[SelectItem],
5927    ) -> SqlResult<Option<Vec<AggregateExpr>>> {
5928        if projection_items.is_empty() {
5929            return Ok(None);
5930        }
5931
5932        let mut specs: Vec<AggregateExpr> = Vec::with_capacity(projection_items.len());
5933        for (idx, item) in projection_items.iter().enumerate() {
5934            let (expr, alias_opt) = match item {
5935                SelectItem::UnnamedExpr(expr) => (expr, None),
5936                SelectItem::ExprWithAlias { expr, alias } => (expr, Some(alias.value.clone())),
5937                _ => return Ok(None),
5938            };
5939
5940            let alias = alias_opt.unwrap_or_else(|| format!("col{}", idx + 1));
5941            let SqlExpr::Function(func) = expr else {
5942                return Ok(None);
5943            };
5944
5945            if func.uses_odbc_syntax {
5946                return Err(Error::InvalidArgumentError(
5947                    "ODBC function syntax is not supported in aggregate queries".into(),
5948                ));
5949            }
5950            if !matches!(func.parameters, FunctionArguments::None) {
5951                return Err(Error::InvalidArgumentError(
5952                    "parameterized aggregate functions are not supported".into(),
5953                ));
5954            }
5955            if func.filter.is_some()
5956                || func.null_treatment.is_some()
5957                || func.over.is_some()
5958                || !func.within_group.is_empty()
5959            {
5960                return Err(Error::InvalidArgumentError(
5961                    "advanced aggregate clauses are not supported".into(),
5962                ));
5963            }
5964
5965            let mut is_distinct = false;
5966            let args_slice: &[FunctionArg] = match &func.args {
5967                FunctionArguments::List(list) => {
5968                    if let Some(dup) = &list.duplicate_treatment {
5969                        use sqlparser::ast::DuplicateTreatment;
5970                        match dup {
5971                            DuplicateTreatment::All => {}
5972                            DuplicateTreatment::Distinct => is_distinct = true,
5973                        }
5974                    }
5975                    if !list.clauses.is_empty() {
5976                        return Err(Error::InvalidArgumentError(
5977                            "aggregate argument clauses are not supported".into(),
5978                        ));
5979                    }
5980                    &list.args
5981                }
5982                FunctionArguments::None => &[],
5983                FunctionArguments::Subquery(_) => {
5984                    return Err(Error::InvalidArgumentError(
5985                        "aggregate subquery arguments are not supported".into(),
5986                    ));
5987                }
5988            };
5989
5990            let func_name = if func.name.0.len() == 1 {
5991                match &func.name.0[0] {
5992                    ObjectNamePart::Identifier(ident) => ident.value.to_ascii_lowercase(),
5993                    _ => {
5994                        return Err(Error::InvalidArgumentError(
5995                            "unsupported aggregate function name".into(),
5996                        ));
5997                    }
5998                }
5999            } else {
6000                return Err(Error::InvalidArgumentError(
6001                    "qualified aggregate function names are not supported".into(),
6002                ));
6003            };
6004
6005            let aggregate = match func_name.as_str() {
6006                "count" => {
6007                    if args_slice.len() != 1 {
6008                        return Err(Error::InvalidArgumentError(
6009                            "COUNT accepts exactly one argument".into(),
6010                        ));
6011                    }
6012                    match &args_slice[0] {
6013                        FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => {
6014                            if is_distinct {
6015                                return Err(Error::InvalidArgumentError(
6016                                    "DISTINCT aggregates must be applied to columns not *, e.g. table columns like: 1,0,2,2".into(),
6017                                ));
6018                            }
6019                            AggregateExpr::count_star(alias, false)
6020                        }
6021                        FunctionArg::Unnamed(FunctionArgExpr::Expr(arg_expr)) => {
6022                            if !is_simple_aggregate_column(arg_expr) {
6023                                return Ok(None);
6024                            }
6025                            let column = resolve_column_name(arg_expr)?;
6026                            AggregateExpr::count_column(column, alias, is_distinct)
6027                        }
6028                        FunctionArg::Named { .. } | FunctionArg::ExprNamed { .. } => {
6029                            return Err(Error::InvalidArgumentError(
6030                                "named COUNT arguments are not supported".into(),
6031                            ));
6032                        }
6033                        FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(_)) => {
6034                            return Err(Error::InvalidArgumentError(
6035                                "COUNT does not support qualified wildcards".into(),
6036                            ));
6037                        }
6038                    }
6039                }
6040                "sum" | "min" | "max" => {
6041                    if args_slice.len() != 1 {
6042                        return Err(Error::InvalidArgumentError(format!(
6043                            "{} accepts exactly one argument",
6044                            func_name.to_uppercase()
6045                        )));
6046                    }
6047                    let arg_expr = match &args_slice[0] {
6048                        FunctionArg::Unnamed(FunctionArgExpr::Expr(arg_expr)) => arg_expr,
6049                        FunctionArg::Unnamed(FunctionArgExpr::Wildcard)
6050                        | FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(_)) => {
6051                            return Err(Error::InvalidArgumentError(format!(
6052                                "{} does not support wildcard arguments",
6053                                func_name.to_uppercase()
6054                            )));
6055                        }
6056                        FunctionArg::Named { .. } | FunctionArg::ExprNamed { .. } => {
6057                            return Err(Error::InvalidArgumentError(format!(
6058                                "{} arguments must be column references",
6059                                func_name.to_uppercase()
6060                            )));
6061                        }
6062                    };
6063
6064                    if is_distinct {
6065                        return Ok(None);
6066                    }
6067
6068                    if func_name == "sum" {
6069                        if let Some(column) = parse_count_nulls_case(arg_expr)? {
6070                            AggregateExpr::count_nulls(column, alias)
6071                        } else {
6072                            if !is_simple_aggregate_column(arg_expr) {
6073                                return Ok(None);
6074                            }
6075                            let column = resolve_column_name(arg_expr)?;
6076                            AggregateExpr::sum_int64(column, alias)
6077                        }
6078                    } else {
6079                        if !is_simple_aggregate_column(arg_expr) {
6080                            return Ok(None);
6081                        }
6082                        let column = resolve_column_name(arg_expr)?;
6083                        if func_name == "min" {
6084                            AggregateExpr::min_int64(column, alias)
6085                        } else {
6086                            AggregateExpr::max_int64(column, alias)
6087                        }
6088                    }
6089                }
6090                _ => return Ok(None),
6091            };
6092
6093            specs.push(aggregate);
6094        }
6095
6096        if specs.is_empty() {
6097            return Ok(None);
6098        }
6099        Ok(Some(specs))
6100    }
6101
6102    fn build_projection_list(
6103        &self,
6104        resolver: &IdentifierResolver<'_>,
6105        id_context: IdentifierContext,
6106        projection_items: &[SelectItem],
6107        outer_scopes: &[IdentifierContext],
6108        scalar_subqueries: &mut Vec<llkv_plan::ScalarSubquery>,
6109        mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
6110    ) -> SqlResult<Vec<SelectProjection>> {
6111        if projection_items.is_empty() {
6112            return Err(Error::InvalidArgumentError(
6113                "SELECT projection must include at least one column".into(),
6114            ));
6115        }
6116
6117        let mut projections = Vec::with_capacity(projection_items.len());
6118        for (idx, item) in projection_items.iter().enumerate() {
6119            match item {
6120                SelectItem::Wildcard(options) => {
6121                    if let Some(exclude) = &options.opt_exclude {
6122                        use sqlparser::ast::ExcludeSelectItem;
6123                        let exclude_cols = match exclude {
6124                            ExcludeSelectItem::Single(ident) => vec![ident.value.clone()],
6125                            ExcludeSelectItem::Multiple(idents) => {
6126                                idents.iter().map(|id| id.value.clone()).collect()
6127                            }
6128                        };
6129                        projections.push(SelectProjection::AllColumnsExcept {
6130                            exclude: exclude_cols,
6131                        });
6132                    } else {
6133                        projections.push(SelectProjection::AllColumns);
6134                    }
6135                }
6136                SelectItem::QualifiedWildcard(kind, _) => match kind {
6137                    SelectItemQualifiedWildcardKind::ObjectName(name) => {
6138                        projections.push(SelectProjection::Column {
6139                            name: name.to_string(),
6140                            alias: None,
6141                        });
6142                    }
6143                    SelectItemQualifiedWildcardKind::Expr(_) => {
6144                        return Err(Error::InvalidArgumentError(
6145                            "expression-qualified wildcards are not supported".into(),
6146                        ));
6147                    }
6148                },
6149                SelectItem::UnnamedExpr(expr) => match expr {
6150                    SqlExpr::Identifier(ident) => {
6151                        let parts = vec![ident.value.clone()];
6152                        let resolution = resolver.resolve(&parts, id_context.clone())?;
6153                        if resolution.is_simple() {
6154                            projections.push(SelectProjection::Column {
6155                                name: resolution.column().to_string(),
6156                                alias: None,
6157                            });
6158                        } else {
6159                            let alias = format!("col{}", idx + 1);
6160                            projections.push(SelectProjection::Computed {
6161                                expr: resolution.into_scalar_expr(),
6162                                alias,
6163                            });
6164                        }
6165                    }
6166                    SqlExpr::CompoundIdentifier(parts) => {
6167                        let name_parts: Vec<String> =
6168                            parts.iter().map(|part| part.value.clone()).collect();
6169                        let resolution = resolver.resolve(&name_parts, id_context.clone())?;
6170                        if resolution.is_simple() {
6171                            projections.push(SelectProjection::Column {
6172                                name: resolution.column().to_string(),
6173                                alias: None,
6174                            });
6175                        } else {
6176                            let alias = format!("col{}", idx + 1);
6177                            projections.push(SelectProjection::Computed {
6178                                expr: resolution.into_scalar_expr(),
6179                                alias,
6180                            });
6181                        }
6182                    }
6183                    _ => {
6184                        // Use the original SQL expression string as the alias for complex expressions.
6185                        // This preserves operators like unary plus (e.g., "+ col2" rather than "col2").
6186                        let alias = expr.to_string();
6187                        let normalized_expr = if matches!(expr, SqlExpr::Subquery(_)) {
6188                            expr.clone()
6189                        } else {
6190                            self.materialize_in_subquery(expr.clone())?
6191                        };
6192                        let scalar = {
6193                            let tracker_view = correlated_tracker.reborrow();
6194                            let mut builder = ScalarSubqueryPlanner {
6195                                engine: self,
6196                                scalar_subqueries,
6197                            };
6198                            let mut tracker_wrapper =
6199                                SubqueryCorrelatedTracker::from_option(tracker_view);
6200                            translate_scalar_internal(
6201                                &normalized_expr,
6202                                Some(resolver),
6203                                Some(&id_context),
6204                                outer_scopes,
6205                                &mut tracker_wrapper,
6206                                Some(&mut builder),
6207                            )?
6208                        };
6209                        projections.push(SelectProjection::Computed {
6210                            expr: scalar,
6211                            alias,
6212                        });
6213                    }
6214                },
6215                SelectItem::ExprWithAlias { expr, alias } => match expr {
6216                    SqlExpr::Identifier(ident) => {
6217                        let parts = vec![ident.value.clone()];
6218                        let resolution = resolver.resolve(&parts, id_context.clone())?;
6219                        if resolution.is_simple() {
6220                            projections.push(SelectProjection::Column {
6221                                name: resolution.column().to_string(),
6222                                alias: Some(alias.value.clone()),
6223                            });
6224                        } else {
6225                            projections.push(SelectProjection::Computed {
6226                                expr: resolution.into_scalar_expr(),
6227                                alias: alias.value.clone(),
6228                            });
6229                        }
6230                    }
6231                    SqlExpr::CompoundIdentifier(parts) => {
6232                        let name_parts: Vec<String> =
6233                            parts.iter().map(|part| part.value.clone()).collect();
6234                        let resolution = resolver.resolve(&name_parts, id_context.clone())?;
6235                        if resolution.is_simple() {
6236                            projections.push(SelectProjection::Column {
6237                                name: resolution.column().to_string(),
6238                                alias: Some(alias.value.clone()),
6239                            });
6240                        } else {
6241                            projections.push(SelectProjection::Computed {
6242                                expr: resolution.into_scalar_expr(),
6243                                alias: alias.value.clone(),
6244                            });
6245                        }
6246                    }
6247                    _ => {
6248                        let normalized_expr = if matches!(expr, SqlExpr::Subquery(_)) {
6249                            expr.clone()
6250                        } else {
6251                            self.materialize_in_subquery(expr.clone())?
6252                        };
6253                        let scalar = {
6254                            let tracker_view = correlated_tracker.reborrow();
6255                            let mut builder = ScalarSubqueryPlanner {
6256                                engine: self,
6257                                scalar_subqueries,
6258                            };
6259                            let mut tracker_wrapper =
6260                                SubqueryCorrelatedTracker::from_option(tracker_view);
6261                            translate_scalar_internal(
6262                                &normalized_expr,
6263                                Some(resolver),
6264                                Some(&id_context),
6265                                outer_scopes,
6266                                &mut tracker_wrapper,
6267                                Some(&mut builder),
6268                            )?
6269                        };
6270                        projections.push(SelectProjection::Computed {
6271                            expr: scalar,
6272                            alias: alias.value.clone(),
6273                        });
6274                    }
6275                },
6276            }
6277        }
6278        Ok(projections)
6279    }
6280
6281    #[allow(clippy::too_many_arguments)] // NOTE: Keeps parity with SQL START TRANSACTION grammar; revisit when options expand.
6282    fn handle_start_transaction(
6283        &self,
6284        modes: Vec<TransactionMode>,
6285        begin: bool,
6286        transaction: Option<BeginTransactionKind>,
6287        modifier: Option<TransactionModifier>,
6288        statements: Vec<Statement>,
6289        exception: Option<Vec<ExceptionWhen>>,
6290        has_end_keyword: bool,
6291    ) -> SqlResult<RuntimeStatementResult<P>> {
6292        if !modes.is_empty() {
6293            return Err(Error::InvalidArgumentError(
6294                "transaction modes are not supported".into(),
6295            ));
6296        }
6297        if modifier.is_some() {
6298            return Err(Error::InvalidArgumentError(
6299                "transaction modifiers are not supported".into(),
6300            ));
6301        }
6302        if !statements.is_empty() || exception.is_some() || has_end_keyword {
6303            return Err(Error::InvalidArgumentError(
6304                "BEGIN blocks with inline statements or exceptions are not supported".into(),
6305            ));
6306        }
6307        if let Some(kind) = transaction {
6308            match kind {
6309                BeginTransactionKind::Transaction | BeginTransactionKind::Work => {}
6310            }
6311        }
6312        if !begin {
6313            // Currently treat START TRANSACTION same as BEGIN
6314            tracing::warn!("Currently treat `START TRANSACTION` same as `BEGIN`")
6315        }
6316
6317        self.execute_plan_statement(PlanStatement::BeginTransaction)
6318    }
6319
6320    fn handle_commit(
6321        &self,
6322        chain: bool,
6323        end: bool,
6324        modifier: Option<TransactionModifier>,
6325    ) -> SqlResult<RuntimeStatementResult<P>> {
6326        if chain {
6327            return Err(Error::InvalidArgumentError(
6328                "COMMIT AND [NO] CHAIN is not supported".into(),
6329            ));
6330        }
6331        if end {
6332            return Err(Error::InvalidArgumentError(
6333                "END blocks are not supported".into(),
6334            ));
6335        }
6336        if modifier.is_some() {
6337            return Err(Error::InvalidArgumentError(
6338                "transaction modifiers are not supported".into(),
6339            ));
6340        }
6341
6342        self.execute_plan_statement(PlanStatement::CommitTransaction)
6343    }
6344
6345    fn handle_rollback(
6346        &self,
6347        chain: bool,
6348        savepoint: Option<Ident>,
6349    ) -> SqlResult<RuntimeStatementResult<P>> {
6350        if chain {
6351            return Err(Error::InvalidArgumentError(
6352                "ROLLBACK AND [NO] CHAIN is not supported".into(),
6353            ));
6354        }
6355        if savepoint.is_some() {
6356            return Err(Error::InvalidArgumentError(
6357                "ROLLBACK TO SAVEPOINT is not supported".into(),
6358            ));
6359        }
6360
6361        self.execute_plan_statement(PlanStatement::RollbackTransaction)
6362    }
6363
6364    fn handle_set(&self, set_stmt: Set) -> SqlResult<RuntimeStatementResult<P>> {
6365        match set_stmt {
6366            Set::SingleAssignment {
6367                scope,
6368                hivevar,
6369                variable,
6370                values,
6371            } => {
6372                if scope.is_some() || hivevar {
6373                    return Err(Error::InvalidArgumentError(
6374                        "SET modifiers are not supported".into(),
6375                    ));
6376                }
6377
6378                let variable_name_raw = variable.to_string();
6379                let variable_name = variable_name_raw.to_ascii_lowercase();
6380
6381                match variable_name.as_str() {
6382                    "default_null_order" => {
6383                        if values.len() != 1 {
6384                            return Err(Error::InvalidArgumentError(
6385                                "SET default_null_order expects exactly one value".into(),
6386                            ));
6387                        }
6388
6389                        let value_expr = &values[0];
6390                        let normalized = match value_expr {
6391                            SqlExpr::Value(value_with_span) => value_with_span
6392                                .value
6393                                .clone()
6394                                .into_string()
6395                                .map(|s| s.to_ascii_lowercase()),
6396                            SqlExpr::Identifier(ident) => Some(ident.value.to_ascii_lowercase()),
6397                            _ => None,
6398                        };
6399
6400                        if !matches!(normalized.as_deref(), Some("nulls_first" | "nulls_last")) {
6401                            return Err(Error::InvalidArgumentError(format!(
6402                                "unsupported value for SET default_null_order: {value_expr:?}"
6403                            )));
6404                        }
6405
6406                        let use_nulls_first = matches!(normalized.as_deref(), Some("nulls_first"));
6407                        self.default_nulls_first
6408                            .store(use_nulls_first, AtomicOrdering::Relaxed);
6409
6410                        Ok(RuntimeStatementResult::NoOp)
6411                    }
6412                    "immediate_transaction_mode" => {
6413                        if values.len() != 1 {
6414                            return Err(Error::InvalidArgumentError(
6415                                "SET immediate_transaction_mode expects exactly one value".into(),
6416                            ));
6417                        }
6418                        let normalized = values[0].to_string().to_ascii_lowercase();
6419                        let enabled = match normalized.as_str() {
6420                            "true" | "on" | "1" => true,
6421                            "false" | "off" | "0" => false,
6422                            _ => {
6423                                return Err(Error::InvalidArgumentError(format!(
6424                                    "unsupported value for SET immediate_transaction_mode: {}",
6425                                    values[0]
6426                                )));
6427                            }
6428                        };
6429                        if !enabled {
6430                            tracing::warn!(
6431                                "SET immediate_transaction_mode=false has no effect; continuing with auto mode"
6432                            );
6433                        }
6434                        Ok(RuntimeStatementResult::NoOp)
6435                    }
6436                    _ => Err(Error::InvalidArgumentError(format!(
6437                        "unsupported SET variable: {variable_name_raw}"
6438                    ))),
6439                }
6440            }
6441            other => Err(Error::InvalidArgumentError(format!(
6442                "unsupported SQL SET statement: {other:?}",
6443            ))),
6444        }
6445    }
6446
6447    fn handle_pragma(
6448        &self,
6449        name: ObjectName,
6450        value: Option<Value>,
6451        is_eq: bool,
6452    ) -> SqlResult<RuntimeStatementResult<P>> {
6453        let (display, canonical) = canonical_object_name(&name)?;
6454        if value.is_some() || is_eq {
6455            return Err(Error::InvalidArgumentError(format!(
6456                "PRAGMA '{display}' does not accept a value"
6457            )));
6458        }
6459
6460        match canonical.as_str() {
6461            "enable_verification" | "disable_verification" => Ok(RuntimeStatementResult::NoOp),
6462            _ => Err(Error::InvalidArgumentError(format!(
6463                "unsupported PRAGMA '{}'",
6464                display
6465            ))),
6466        }
6467    }
6468
6469    fn handle_vacuum(&self, vacuum: VacuumStatement) -> SqlResult<RuntimeStatementResult<P>> {
6470        // Only support REINDEX with a table name (which is treated as index name in LLKV)
6471        if vacuum.reindex {
6472            let index_name = vacuum.table_name.ok_or_else(|| {
6473                Error::InvalidArgumentError("REINDEX requires an index name".to_string())
6474            })?;
6475
6476            let (display_name, canonical_name) = canonical_object_name(&index_name)?;
6477
6478            let plan = ReindexPlan::new(display_name.clone()).with_canonical(canonical_name);
6479
6480            let statement = PlanStatement::Reindex(plan);
6481            self.engine.execute_statement(statement).map_err(|err| {
6482                tracing::error!("REINDEX failed for '{}': {}", display_name, err);
6483                err
6484            })
6485        } else {
6486            // Other VACUUM variants are not supported
6487            Err(Error::InvalidArgumentError(
6488                "Only REINDEX is supported; general VACUUM is not implemented".to_string(),
6489            ))
6490        }
6491    }
6492}
6493
6494fn canonical_object_name(name: &ObjectName) -> SqlResult<(String, String)> {
6495    if name.0.is_empty() {
6496        return Err(Error::InvalidArgumentError(
6497            "object name must not be empty".into(),
6498        ));
6499    }
6500    let mut parts: Vec<String> = Vec::with_capacity(name.0.len());
6501    for part in &name.0 {
6502        let ident = match part {
6503            ObjectNamePart::Identifier(ident) => ident,
6504            _ => {
6505                return Err(Error::InvalidArgumentError(
6506                    "object names using functions are not supported".into(),
6507                ));
6508            }
6509        };
6510        parts.push(ident.value.clone());
6511    }
6512    let display = parts.join(".");
6513    let canonical = display.to_ascii_lowercase();
6514    Ok((display, canonical))
6515}
6516
6517/// Parse an object name into optional schema and table name components.
6518///
6519/// Returns (schema_name, table_name) where schema_name is None if not qualified.
6520///
6521/// Examples:
6522/// - "users" -> (None, "users")
6523/// - "test.users" -> (Some("test"), "users")
6524/// - "catalog.test.users" -> Error (too many parts)
6525fn parse_schema_qualified_name(name: &ObjectName) -> SqlResult<(Option<String>, String)> {
6526    if name.0.is_empty() {
6527        return Err(Error::InvalidArgumentError(
6528            "object name must not be empty".into(),
6529        ));
6530    }
6531
6532    let mut parts: Vec<String> = Vec::with_capacity(name.0.len());
6533    for part in &name.0 {
6534        let ident = match part {
6535            ObjectNamePart::Identifier(ident) => ident,
6536            _ => {
6537                return Err(Error::InvalidArgumentError(
6538                    "object names using functions are not supported".into(),
6539                ));
6540            }
6541        };
6542        parts.push(ident.value.clone());
6543    }
6544
6545    match parts.len() {
6546        1 => Ok((None, parts[0].clone())),
6547        2 => Ok((Some(parts[0].clone()), parts[1].clone())),
6548        _ => Err(Error::InvalidArgumentError(format!(
6549            "table name has too many parts: {}",
6550            name
6551        ))),
6552    }
6553}
6554
6555/// Extract column name from an index column specification (OrderBy expression).
6556///
6557/// This handles the common pattern of validating and extracting column names from
6558/// SQL index column definitions (PRIMARY KEY, UNIQUE, CREATE INDEX).
6559///
6560/// # Parameters
6561/// - `index_col`: The index column from sqlparser AST
6562/// - `context`: Description of where this column appears (e.g., "PRIMARY KEY", "UNIQUE constraint")
6563/// - `allow_sort_options`: If false, errors if any sort options are present; if true, validates them
6564/// - `allow_compound`: If true, allows compound identifiers and takes the last part; if false, only allows simple identifiers
6565///
6566/// # Returns
6567/// Column name as a String
6568fn extract_index_column_name(
6569    index_col: &sqlparser::ast::IndexColumn,
6570    context: &str,
6571    allow_sort_options: bool,
6572    allow_compound: bool,
6573) -> SqlResult<String> {
6574    use sqlparser::ast::Expr as SqlExpr;
6575
6576    // Check operator class
6577    if index_col.operator_class.is_some() {
6578        return Err(Error::InvalidArgumentError(format!(
6579            "{} operator classes are not supported",
6580            context
6581        )));
6582    }
6583
6584    let order_expr = &index_col.column;
6585
6586    // Validate sort options
6587    if allow_sort_options {
6588        // For CREATE INDEX: extract and validate sort options
6589        let _ascending = order_expr.options.asc.unwrap_or(true);
6590        let _nulls_first = order_expr.options.nulls_first.unwrap_or(false);
6591        // DESC and NULLS FIRST are now supported
6592    } else {
6593        // For constraints: no sort options allowed
6594        if order_expr.options.asc.is_some()
6595            || order_expr.options.nulls_first.is_some()
6596            || order_expr.with_fill.is_some()
6597        {
6598            return Err(Error::InvalidArgumentError(format!(
6599                "{} columns must be simple identifiers",
6600                context
6601            )));
6602        }
6603    }
6604
6605    // Extract column name from expression
6606    let column_name = match &order_expr.expr {
6607        SqlExpr::Identifier(ident) => ident.value.clone(),
6608        SqlExpr::CompoundIdentifier(parts) => {
6609            if allow_compound {
6610                // For CREATE INDEX: allow qualified names, take last part
6611                parts
6612                    .last()
6613                    .map(|ident| ident.value.clone())
6614                    .ok_or_else(|| {
6615                        Error::InvalidArgumentError(format!(
6616                            "invalid column reference in {}",
6617                            context
6618                        ))
6619                    })?
6620            } else if parts.len() == 1 {
6621                // For constraints: only allow single-part compound identifiers
6622                parts[0].value.clone()
6623            } else {
6624                return Err(Error::InvalidArgumentError(format!(
6625                    "{} columns must be column identifiers",
6626                    context
6627                )));
6628            }
6629        }
6630        other => {
6631            return Err(Error::InvalidArgumentError(format!(
6632                "{} only supports column references, found {:?}",
6633                context, other
6634            )));
6635        }
6636    };
6637
6638    Ok(column_name)
6639}
6640
6641fn validate_create_table_common(stmt: &sqlparser::ast::CreateTable) -> SqlResult<()> {
6642    if stmt.clone.is_some() || stmt.like.is_some() {
6643        return Err(Error::InvalidArgumentError(
6644            "CREATE TABLE LIKE/CLONE is not supported".into(),
6645        ));
6646    }
6647    if stmt.or_replace && stmt.if_not_exists {
6648        return Err(Error::InvalidArgumentError(
6649            "CREATE TABLE cannot combine OR REPLACE with IF NOT EXISTS".into(),
6650        ));
6651    }
6652    use sqlparser::ast::TableConstraint;
6653
6654    let mut seen_primary_key = false;
6655    for constraint in &stmt.constraints {
6656        match constraint {
6657            TableConstraint::PrimaryKey { .. } => {
6658                if seen_primary_key {
6659                    return Err(Error::InvalidArgumentError(
6660                        "multiple PRIMARY KEY constraints are not supported".into(),
6661                    ));
6662                }
6663                seen_primary_key = true;
6664            }
6665            TableConstraint::Unique { .. } => {
6666                // Detailed validation is performed later during plan construction.
6667            }
6668            TableConstraint::ForeignKey { .. } => {
6669                // Detailed validation is performed later during plan construction.
6670            }
6671            other => {
6672                return Err(Error::InvalidArgumentError(format!(
6673                    "table-level constraint {:?} is not supported",
6674                    other
6675                )));
6676            }
6677        }
6678    }
6679    Ok(())
6680}
6681
6682fn validate_check_constraint(
6683    check_expr: &sqlparser::ast::Expr,
6684    table_name: &str,
6685    column_names: &[&str],
6686) -> SqlResult<()> {
6687    use sqlparser::ast::Expr as SqlExpr;
6688
6689    let column_names_lower: FxHashSet<String> = column_names
6690        .iter()
6691        .map(|name| name.to_ascii_lowercase())
6692        .collect();
6693
6694    let mut stack: Vec<&SqlExpr> = vec![check_expr];
6695
6696    while let Some(expr) = stack.pop() {
6697        match expr {
6698            SqlExpr::Subquery(_) => {
6699                return Err(Error::InvalidArgumentError(
6700                    "Subqueries are not allowed in CHECK constraints".into(),
6701                ));
6702            }
6703            SqlExpr::Function(func) => {
6704                let func_name = func.name.to_string().to_uppercase();
6705                if matches!(func_name.as_str(), "SUM" | "AVG" | "COUNT" | "MIN" | "MAX") {
6706                    return Err(Error::InvalidArgumentError(
6707                        "Aggregate functions are not allowed in CHECK constraints".into(),
6708                    ));
6709                }
6710
6711                if let sqlparser::ast::FunctionArguments::List(list) = &func.args {
6712                    for arg in &list.args {
6713                        if let sqlparser::ast::FunctionArg::Unnamed(
6714                            sqlparser::ast::FunctionArgExpr::Expr(expr),
6715                        ) = arg
6716                        {
6717                            stack.push(expr);
6718                        }
6719                    }
6720                }
6721            }
6722            SqlExpr::Identifier(ident) => {
6723                if !column_names_lower.contains(&ident.value.to_ascii_lowercase()) {
6724                    return Err(Error::InvalidArgumentError(format!(
6725                        "Column '{}' referenced in CHECK constraint does not exist",
6726                        ident.value
6727                    )));
6728                }
6729            }
6730            SqlExpr::CompoundIdentifier(idents) => {
6731                if idents.len() == 2 {
6732                    let first = idents[0].value.as_str();
6733                    let second = &idents[1].value;
6734
6735                    if column_names_lower.contains(&first.to_ascii_lowercase()) {
6736                        continue;
6737                    }
6738
6739                    if !first.eq_ignore_ascii_case(table_name) {
6740                        return Err(Error::InvalidArgumentError(format!(
6741                            "CHECK constraint references column from different table '{}'",
6742                            first
6743                        )));
6744                    }
6745
6746                    if !column_names_lower.contains(&second.to_ascii_lowercase()) {
6747                        return Err(Error::InvalidArgumentError(format!(
6748                            "Column '{}' referenced in CHECK constraint does not exist",
6749                            second
6750                        )));
6751                    }
6752                } else if idents.len() == 3 {
6753                    let first = &idents[0].value;
6754                    let second = &idents[1].value;
6755                    let third = &idents[2].value;
6756
6757                    if first.eq_ignore_ascii_case(table_name) {
6758                        if !column_names_lower.contains(&second.to_ascii_lowercase()) {
6759                            return Err(Error::InvalidArgumentError(format!(
6760                                "Column '{}' referenced in CHECK constraint does not exist",
6761                                second
6762                            )));
6763                        }
6764                    } else if second.eq_ignore_ascii_case(table_name) {
6765                        if !column_names_lower.contains(&third.to_ascii_lowercase()) {
6766                            return Err(Error::InvalidArgumentError(format!(
6767                                "Column '{}' referenced in CHECK constraint does not exist",
6768                                third
6769                            )));
6770                        }
6771                    } else {
6772                        return Err(Error::InvalidArgumentError(format!(
6773                            "CHECK constraint references column from different table '{}'",
6774                            second
6775                        )));
6776                    }
6777                }
6778            }
6779            SqlExpr::BinaryOp { left, right, .. } => {
6780                stack.push(left);
6781                stack.push(right);
6782            }
6783            SqlExpr::UnaryOp { expr, .. } | SqlExpr::Nested(expr) => {
6784                stack.push(expr);
6785            }
6786            SqlExpr::Value(_) | SqlExpr::TypedString { .. } => {}
6787            _ => {}
6788        }
6789    }
6790
6791    Ok(())
6792}
6793
6794fn validate_create_table_definition(stmt: &sqlparser::ast::CreateTable) -> SqlResult<()> {
6795    for column in &stmt.columns {
6796        for ColumnOptionDef { option, .. } in &column.options {
6797            match option {
6798                ColumnOption::Null
6799                | ColumnOption::NotNull
6800                | ColumnOption::Unique { .. }
6801                | ColumnOption::Check(_)
6802                | ColumnOption::ForeignKey { .. } => {}
6803                ColumnOption::Default(_) => {
6804                    return Err(Error::InvalidArgumentError(format!(
6805                        "DEFAULT values are not supported for column '{}'",
6806                        column.name
6807                    )));
6808                }
6809                other => {
6810                    return Err(Error::InvalidArgumentError(format!(
6811                        "unsupported column option {:?} on '{}'",
6812                        other, column.name
6813                    )));
6814                }
6815            }
6816        }
6817    }
6818    Ok(())
6819}
6820
6821fn validate_create_table_as(stmt: &sqlparser::ast::CreateTable) -> SqlResult<()> {
6822    if !stmt.columns.is_empty() {
6823        return Err(Error::InvalidArgumentError(
6824            "CREATE TABLE AS SELECT does not support column definitions yet".into(),
6825        ));
6826    }
6827    Ok(())
6828}
6829
6830fn validate_simple_query(query: &Query) -> SqlResult<()> {
6831    if query.with.is_some() {
6832        return Err(Error::InvalidArgumentError(
6833            "WITH clauses are not supported".into(),
6834        ));
6835    }
6836    if let Some(limit_clause) = &query.limit_clause {
6837        match limit_clause {
6838            LimitClause::LimitOffset {
6839                offset: Some(_), ..
6840            }
6841            | LimitClause::OffsetCommaLimit { .. } => {
6842                return Err(Error::InvalidArgumentError(
6843                    "OFFSET clauses are not supported".into(),
6844                ));
6845            }
6846            LimitClause::LimitOffset { limit_by, .. } if !limit_by.is_empty() => {
6847                return Err(Error::InvalidArgumentError(
6848                    "LIMIT BY clauses are not supported".into(),
6849                ));
6850            }
6851            _ => {}
6852        }
6853    }
6854    if query.fetch.is_some() {
6855        return Err(Error::InvalidArgumentError(
6856            "FETCH clauses are not supported".into(),
6857        ));
6858    }
6859    Ok(())
6860}
6861
6862fn resolve_column_name(expr: &SqlExpr) -> SqlResult<String> {
6863    match expr {
6864        SqlExpr::Identifier(ident) => Ok(ident.value.clone()),
6865        SqlExpr::CompoundIdentifier(parts) => {
6866            if let Some(last) = parts.last() {
6867                Ok(last.value.clone())
6868            } else {
6869                Err(Error::InvalidArgumentError(
6870                    "empty column identifier".into(),
6871                ))
6872            }
6873        }
6874        SqlExpr::Nested(inner) => resolve_column_name(inner),
6875        // Handle unary +/- by recursively resolving the inner expression
6876        SqlExpr::UnaryOp {
6877            op: UnaryOperator::Plus | UnaryOperator::Minus,
6878            expr,
6879        } => resolve_column_name(expr),
6880        _ => Err(Error::InvalidArgumentError(
6881            "aggregate arguments must be plain column identifiers".into(),
6882        )),
6883    }
6884}
6885
6886fn is_simple_aggregate_column(expr: &SqlExpr) -> bool {
6887    match expr {
6888        SqlExpr::Identifier(_) | SqlExpr::CompoundIdentifier(_) => true,
6889        SqlExpr::Nested(inner) => is_simple_aggregate_column(inner),
6890        SqlExpr::UnaryOp {
6891            op: UnaryOperator::Plus,
6892            expr,
6893        } => is_simple_aggregate_column(expr),
6894        _ => false,
6895    }
6896}
6897
6898fn validate_projection_alias_qualifiers(
6899    projection_items: &[SelectItem],
6900    alias: &str,
6901) -> SqlResult<()> {
6902    let alias_lower = alias.to_ascii_lowercase();
6903    for item in projection_items {
6904        match item {
6905            SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } => {
6906                if let SqlExpr::CompoundIdentifier(parts) = expr
6907                    && parts.len() >= 2
6908                    && let Some(first) = parts.first()
6909                    && !first.value.eq_ignore_ascii_case(&alias_lower)
6910                {
6911                    return Err(Error::InvalidArgumentError(format!(
6912                        "Binder Error: table '{}' not found",
6913                        first.value
6914                    )));
6915                }
6916            }
6917            _ => {}
6918        }
6919    }
6920    Ok(())
6921}
6922
6923/// Try to parse a function as an aggregate call for use in scalar expressions
6924/// Check if a scalar expression contains any aggregate functions
6925#[allow(dead_code)] // Utility function for future use
6926fn expr_contains_aggregate(expr: &llkv_expr::expr::ScalarExpr<String>) -> bool {
6927    match expr {
6928        llkv_expr::expr::ScalarExpr::Aggregate(_) => true,
6929        llkv_expr::expr::ScalarExpr::Binary { left, right, .. } => {
6930            expr_contains_aggregate(left) || expr_contains_aggregate(right)
6931        }
6932        llkv_expr::expr::ScalarExpr::Compare { left, right, .. } => {
6933            expr_contains_aggregate(left) || expr_contains_aggregate(right)
6934        }
6935        llkv_expr::expr::ScalarExpr::Not(inner) => expr_contains_aggregate(inner),
6936        llkv_expr::expr::ScalarExpr::IsNull { expr, .. } => expr_contains_aggregate(expr),
6937        llkv_expr::expr::ScalarExpr::GetField { base, .. } => expr_contains_aggregate(base),
6938        llkv_expr::expr::ScalarExpr::Cast { expr, .. } => expr_contains_aggregate(expr),
6939        llkv_expr::expr::ScalarExpr::Case {
6940            operand,
6941            branches,
6942            else_expr,
6943        } => {
6944            operand
6945                .as_deref()
6946                .map(expr_contains_aggregate)
6947                .unwrap_or(false)
6948                || branches.iter().any(|(when_expr, then_expr)| {
6949                    expr_contains_aggregate(when_expr) || expr_contains_aggregate(then_expr)
6950                })
6951                || else_expr
6952                    .as_deref()
6953                    .map(expr_contains_aggregate)
6954                    .unwrap_or(false)
6955        }
6956        llkv_expr::expr::ScalarExpr::Coalesce(items) => items.iter().any(expr_contains_aggregate),
6957        llkv_expr::expr::ScalarExpr::Column(_) | llkv_expr::expr::ScalarExpr::Literal(_) => false,
6958        llkv_expr::expr::ScalarExpr::ScalarSubquery(_) => false,
6959    }
6960}
6961
6962fn try_parse_aggregate_function(
6963    func: &sqlparser::ast::Function,
6964    resolver: Option<&IdentifierResolver<'_>>,
6965    context: Option<&IdentifierContext>,
6966    outer_scopes: &[IdentifierContext],
6967    tracker: &mut SubqueryCorrelatedTracker<'_>,
6968) -> SqlResult<Option<llkv_expr::expr::AggregateCall<String>>> {
6969    use sqlparser::ast::{
6970        DuplicateTreatment, FunctionArg, FunctionArgExpr, FunctionArguments, ObjectNamePart,
6971    };
6972
6973    if func.uses_odbc_syntax {
6974        return Ok(None);
6975    }
6976    if !matches!(func.parameters, FunctionArguments::None) {
6977        return Ok(None);
6978    }
6979    if func.filter.is_some()
6980        || func.null_treatment.is_some()
6981        || func.over.is_some()
6982        || !func.within_group.is_empty()
6983    {
6984        return Ok(None);
6985    }
6986
6987    let func_name = if func.name.0.len() == 1 {
6988        match &func.name.0[0] {
6989            ObjectNamePart::Identifier(ident) => ident.value.to_ascii_lowercase(),
6990            _ => return Ok(None),
6991        }
6992    } else {
6993        return Ok(None);
6994    };
6995
6996    // Check for DISTINCT modifier
6997    let distinct = match &func.args {
6998        FunctionArguments::List(list) => {
6999            if !list.clauses.is_empty() {
7000                return Ok(None);
7001            }
7002            matches!(list.duplicate_treatment, Some(DuplicateTreatment::Distinct))
7003        }
7004        _ => false,
7005    };
7006
7007    let args_slice: &[FunctionArg] = match &func.args {
7008        FunctionArguments::List(list) => &list.args,
7009        FunctionArguments::None => &[],
7010        FunctionArguments::Subquery(_) => return Ok(None),
7011    };
7012
7013    let agg_call = match func_name.as_str() {
7014        "count" => {
7015            if args_slice.len() != 1 {
7016                return Err(Error::InvalidArgumentError(
7017                    "COUNT accepts exactly one argument".into(),
7018                ));
7019            }
7020            match &args_slice[0] {
7021                FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => {
7022                    if distinct {
7023                        return Err(Error::InvalidArgumentError(
7024                            "COUNT(DISTINCT *) is not supported".into(),
7025                        ));
7026                    }
7027                    llkv_expr::expr::AggregateCall::CountStar
7028                }
7029                FunctionArg::Unnamed(FunctionArgExpr::Expr(arg_expr)) => {
7030                    let expr = translate_scalar_internal(
7031                        arg_expr,
7032                        resolver,
7033                        context,
7034                        outer_scopes,
7035                        tracker,
7036                        None,
7037                    )?;
7038                    llkv_expr::expr::AggregateCall::Count {
7039                        expr: Box::new(expr),
7040                        distinct,
7041                    }
7042                }
7043                _ => {
7044                    return Err(Error::InvalidArgumentError(
7045                        "unsupported COUNT argument".into(),
7046                    ));
7047                }
7048            }
7049        }
7050        "sum" => {
7051            if args_slice.len() != 1 {
7052                return Err(Error::InvalidArgumentError(
7053                    "SUM accepts exactly one argument".into(),
7054                ));
7055            }
7056            let arg_expr = match &args_slice[0] {
7057                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
7058                _ => {
7059                    return Err(Error::InvalidArgumentError(
7060                        "SUM requires a column argument".into(),
7061                    ));
7062                }
7063            };
7064
7065            // Check for COUNT(CASE ...) pattern
7066            if let Some(column) = parse_count_nulls_case(arg_expr)? {
7067                if distinct {
7068                    return Err(Error::InvalidArgumentError(
7069                        "DISTINCT not supported for COUNT(CASE ...) pattern".into(),
7070                    ));
7071                }
7072                llkv_expr::expr::AggregateCall::CountNulls(Box::new(
7073                    llkv_expr::expr::ScalarExpr::column(column),
7074                ))
7075            } else {
7076                let expr = translate_scalar_internal(
7077                    arg_expr,
7078                    resolver,
7079                    context,
7080                    outer_scopes,
7081                    tracker,
7082                    None,
7083                )?;
7084                llkv_expr::expr::AggregateCall::Sum {
7085                    expr: Box::new(expr),
7086                    distinct,
7087                }
7088            }
7089        }
7090        "total" => {
7091            if args_slice.len() != 1 {
7092                return Err(Error::InvalidArgumentError(
7093                    "TOTAL accepts exactly one argument".into(),
7094                ));
7095            }
7096            let arg_expr = match &args_slice[0] {
7097                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
7098                _ => {
7099                    return Err(Error::InvalidArgumentError(
7100                        "TOTAL requires a column argument".into(),
7101                    ));
7102                }
7103            };
7104
7105            let expr = translate_scalar_internal(
7106                arg_expr,
7107                resolver,
7108                context,
7109                outer_scopes,
7110                tracker,
7111                None,
7112            )?;
7113            llkv_expr::expr::AggregateCall::Total {
7114                expr: Box::new(expr),
7115                distinct,
7116            }
7117        }
7118        "min" => {
7119            if args_slice.len() != 1 {
7120                return Err(Error::InvalidArgumentError(
7121                    "MIN accepts exactly one argument".into(),
7122                ));
7123            }
7124            let arg_expr = match &args_slice[0] {
7125                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
7126                _ => {
7127                    return Err(Error::InvalidArgumentError(
7128                        "MIN requires a column argument".into(),
7129                    ));
7130                }
7131            };
7132            let expr = translate_scalar_internal(
7133                arg_expr,
7134                resolver,
7135                context,
7136                outer_scopes,
7137                tracker,
7138                None,
7139            )?;
7140            llkv_expr::expr::AggregateCall::Min(Box::new(expr))
7141        }
7142        "max" => {
7143            if args_slice.len() != 1 {
7144                return Err(Error::InvalidArgumentError(
7145                    "MAX accepts exactly one argument".into(),
7146                ));
7147            }
7148            let arg_expr = match &args_slice[0] {
7149                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
7150                _ => {
7151                    return Err(Error::InvalidArgumentError(
7152                        "MAX requires a column argument".into(),
7153                    ));
7154                }
7155            };
7156            let expr = translate_scalar_internal(
7157                arg_expr,
7158                resolver,
7159                context,
7160                outer_scopes,
7161                tracker,
7162                None,
7163            )?;
7164            llkv_expr::expr::AggregateCall::Max(Box::new(expr))
7165        }
7166        "avg" => {
7167            if args_slice.len() != 1 {
7168                return Err(Error::InvalidArgumentError(
7169                    "AVG accepts exactly one argument".into(),
7170                ));
7171            }
7172            let arg_expr = match &args_slice[0] {
7173                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
7174                _ => {
7175                    return Err(Error::InvalidArgumentError(
7176                        "AVG requires a column argument".into(),
7177                    ));
7178                }
7179            };
7180            let expr = translate_scalar_internal(
7181                arg_expr,
7182                resolver,
7183                context,
7184                outer_scopes,
7185                tracker,
7186                None,
7187            )?;
7188            llkv_expr::expr::AggregateCall::Avg {
7189                expr: Box::new(expr),
7190                distinct,
7191            }
7192        }
7193        "group_concat" => {
7194            if args_slice.is_empty() || args_slice.len() > 2 {
7195                return Err(Error::InvalidArgumentError(
7196                    "GROUP_CONCAT accepts one or two arguments".into(),
7197                ));
7198            }
7199
7200            // First argument is the column/expression
7201            let arg_expr = match &args_slice[0] {
7202                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
7203                _ => {
7204                    return Err(Error::InvalidArgumentError(
7205                        "GROUP_CONCAT requires a column argument".into(),
7206                    ));
7207                }
7208            };
7209
7210            let expr = translate_scalar_internal(
7211                arg_expr,
7212                resolver,
7213                context,
7214                outer_scopes,
7215                tracker,
7216                None,
7217            )?;
7218
7219            // Second argument (optional) is the separator
7220            let separator = if args_slice.len() == 2 {
7221                match &args_slice[1] {
7222                    FunctionArg::Unnamed(FunctionArgExpr::Expr(SqlExpr::Value(
7223                        ValueWithSpan {
7224                            value: sqlparser::ast::Value::SingleQuotedString(s),
7225                            ..
7226                        },
7227                    ))) => Some(s.clone()),
7228                    _ => {
7229                        return Err(Error::InvalidArgumentError(
7230                            "GROUP_CONCAT separator must be a string literal".into(),
7231                        ));
7232                    }
7233                }
7234            } else {
7235                None
7236            };
7237
7238            // SQLite doesn't support DISTINCT with a custom separator
7239            if distinct && separator.is_some() {
7240                return Err(Error::InvalidArgumentError(
7241                    "GROUP_CONCAT does not support DISTINCT with a custom separator".into(),
7242                ));
7243            }
7244
7245            llkv_expr::expr::AggregateCall::GroupConcat {
7246                expr: Box::new(expr),
7247                distinct,
7248                separator,
7249            }
7250        }
7251        _ => return Ok(None),
7252    };
7253
7254    Ok(Some(agg_call))
7255}
7256
7257fn parse_count_nulls_case(expr: &SqlExpr) -> SqlResult<Option<String>> {
7258    let SqlExpr::Case {
7259        operand,
7260        conditions,
7261        else_result,
7262        ..
7263    } = expr
7264    else {
7265        return Ok(None);
7266    };
7267
7268    if operand.is_some() || conditions.len() != 1 {
7269        return Ok(None);
7270    }
7271
7272    let case_when = &conditions[0];
7273    if !is_integer_literal(&case_when.result, 1) {
7274        return Ok(None);
7275    }
7276
7277    let else_expr = match else_result {
7278        Some(expr) => expr.as_ref(),
7279        None => return Ok(None),
7280    };
7281    if !is_integer_literal(else_expr, 0) {
7282        return Ok(None);
7283    }
7284
7285    let inner = match &case_when.condition {
7286        SqlExpr::IsNull(inner) => inner.as_ref(),
7287        _ => return Ok(None),
7288    };
7289
7290    resolve_column_name(inner).map(Some)
7291}
7292
7293fn is_integer_literal(expr: &SqlExpr, expected: i64) -> bool {
7294    match expr {
7295        SqlExpr::Value(ValueWithSpan {
7296            value: Value::Number(text, _),
7297            ..
7298        }) => text.parse::<i64>() == Ok(expected),
7299        _ => false,
7300    }
7301}
7302
7303fn strip_sql_expr_nesting(expr: &SqlExpr) -> &SqlExpr {
7304    match expr {
7305        SqlExpr::Nested(inner) => strip_sql_expr_nesting(inner),
7306        other => other,
7307    }
7308}
7309
7310struct BetweenBounds<'a> {
7311    lower: &'a SqlExpr,
7312    upper: &'a SqlExpr,
7313}
7314
7315fn translate_between_expr(
7316    resolver: &IdentifierResolver<'_>,
7317    context: IdentifierContext,
7318    between_expr: &SqlExpr,
7319    bounds: BetweenBounds<'_>,
7320    negated: bool,
7321    outer_scopes: &[IdentifierContext],
7322    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
7323) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
7324    let lower_op = if negated {
7325        BinaryOperator::Lt
7326    } else {
7327        BinaryOperator::GtEq
7328    };
7329    let upper_op = if negated {
7330        BinaryOperator::Gt
7331    } else {
7332        BinaryOperator::LtEq
7333    };
7334
7335    let lower_bound = translate_comparison_with_context(
7336        resolver,
7337        context.clone(),
7338        between_expr,
7339        lower_op,
7340        bounds.lower,
7341        outer_scopes,
7342        correlated_tracker.reborrow(),
7343    )?;
7344    let upper_bound = translate_comparison_with_context(
7345        resolver,
7346        context,
7347        between_expr,
7348        upper_op,
7349        bounds.upper,
7350        outer_scopes,
7351        correlated_tracker,
7352    )?;
7353
7354    if negated {
7355        Ok(llkv_expr::expr::Expr::Or(vec![lower_bound, upper_bound]))
7356    } else {
7357        Ok(llkv_expr::expr::Expr::And(vec![lower_bound, upper_bound]))
7358    }
7359}
7360
7361fn correlated_scalar_from_resolution(
7362    placeholder: String,
7363    resolution: &ColumnResolution,
7364) -> llkv_expr::expr::ScalarExpr<String> {
7365    let mut expr = llkv_expr::expr::ScalarExpr::column(placeholder);
7366    for field in resolution.field_path() {
7367        expr = llkv_expr::expr::ScalarExpr::get_field(expr, field.clone());
7368    }
7369    expr
7370}
7371
7372fn resolve_correlated_identifier(
7373    resolver: &IdentifierResolver<'_>,
7374    parts: &[String],
7375    outer_scopes: &[IdentifierContext],
7376    mut tracker: SubqueryCorrelatedTracker<'_>,
7377) -> SqlResult<Option<llkv_expr::expr::ScalarExpr<String>>> {
7378    if !tracker.is_active() {
7379        return Ok(None);
7380    }
7381
7382    for scope in outer_scopes.iter().rev() {
7383        match resolver.resolve(parts, scope.clone()) {
7384            Ok(resolution) => {
7385                if let Some(placeholder) = tracker.placeholder_for_resolution(&resolution) {
7386                    let expr = correlated_scalar_from_resolution(placeholder, &resolution);
7387                    return Ok(Some(expr));
7388                }
7389            }
7390            Err(_) => continue,
7391        }
7392    }
7393
7394    Ok(None)
7395}
7396
7397fn resolve_identifier_expr(
7398    resolver: &IdentifierResolver<'_>,
7399    context: &IdentifierContext,
7400    parts: Vec<String>,
7401    outer_scopes: &[IdentifierContext],
7402    tracker: SubqueryCorrelatedTracker<'_>,
7403) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
7404    match resolver.resolve(&parts, context.clone()) {
7405        Ok(resolution) => Ok(resolution.into_scalar_expr()),
7406        Err(err) => {
7407            if let Some(expr) =
7408                resolve_correlated_identifier(resolver, &parts, outer_scopes, tracker)?
7409            {
7410                Ok(expr)
7411            } else {
7412                Err(err)
7413            }
7414        }
7415    }
7416}
7417
7418fn translate_condition_with_context(
7419    engine: &SqlEngine,
7420    resolver: &IdentifierResolver<'_>,
7421    context: IdentifierContext,
7422    expr: &SqlExpr,
7423    outer_scopes: &[IdentifierContext],
7424    subqueries: &mut Vec<llkv_plan::FilterSubquery>,
7425    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
7426) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
7427    // Iterative postorder traversal using the TransformFrame pattern.
7428    // See llkv-plan::TransformFrame documentation for pattern details.
7429    //
7430    // This avoids stack overflow on deeply nested expressions (50k+ nodes) by using
7431    // explicit work_stack and result_stack instead of recursion.
7432
7433    enum ConditionExitContext {
7434        And,
7435        Or,
7436        Not,
7437        Nested,
7438    }
7439
7440    type ConditionFrame<'a> = llkv_plan::TransformFrame<
7441        'a,
7442        SqlExpr,
7443        llkv_expr::expr::Expr<'static, String>,
7444        ConditionExitContext,
7445    >;
7446
7447    let mut work_stack: Vec<ConditionFrame> = vec![ConditionFrame::Enter(expr)];
7448    let mut result_stack: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
7449
7450    while let Some(frame) = work_stack.pop() {
7451        match frame {
7452            ConditionFrame::Enter(node) => match node {
7453                SqlExpr::BinaryOp { left, op, right } => match op {
7454                    BinaryOperator::And => {
7455                        work_stack.push(ConditionFrame::Exit(ConditionExitContext::And));
7456                        work_stack.push(ConditionFrame::Enter(right));
7457                        work_stack.push(ConditionFrame::Enter(left));
7458                    }
7459                    BinaryOperator::Or => {
7460                        work_stack.push(ConditionFrame::Exit(ConditionExitContext::Or));
7461                        work_stack.push(ConditionFrame::Enter(right));
7462                        work_stack.push(ConditionFrame::Enter(left));
7463                    }
7464                    BinaryOperator::Eq
7465                    | BinaryOperator::NotEq
7466                    | BinaryOperator::Lt
7467                    | BinaryOperator::LtEq
7468                    | BinaryOperator::Gt
7469                    | BinaryOperator::GtEq => {
7470                        let result = translate_comparison_with_context(
7471                            resolver,
7472                            context.clone(),
7473                            left,
7474                            op.clone(),
7475                            right,
7476                            outer_scopes,
7477                            correlated_tracker.reborrow(),
7478                        )?;
7479                        work_stack.push(ConditionFrame::Leaf(result));
7480                    }
7481                    other => {
7482                        return Err(Error::InvalidArgumentError(format!(
7483                            "unsupported binary operator in WHERE clause: {other:?}"
7484                        )));
7485                    }
7486                },
7487                SqlExpr::UnaryOp {
7488                    op: UnaryOperator::Not,
7489                    expr: inner,
7490                } => {
7491                    let inner_stripped = strip_sql_expr_nesting(inner);
7492                    if let SqlExpr::Between {
7493                        expr: between_expr,
7494                        negated: inner_negated,
7495                        low,
7496                        high,
7497                    } = inner_stripped
7498                    {
7499                        let negated_mode = !*inner_negated;
7500                        let between_expr_result = translate_between_expr(
7501                            resolver,
7502                            context.clone(),
7503                            between_expr,
7504                            BetweenBounds {
7505                                lower: low,
7506                                upper: high,
7507                            },
7508                            negated_mode,
7509                            outer_scopes,
7510                            correlated_tracker.reborrow(),
7511                        )?;
7512                        work_stack.push(ConditionFrame::Leaf(between_expr_result));
7513                        continue;
7514                    }
7515                    // Note: Do not short-circuit NOT on NULL comparisons.
7516                    // NULL comparisons evaluate to NULL, and NOT NULL should also be NULL,
7517                    // not FALSE. Let the normal evaluation handle NULL propagation.
7518                    work_stack.push(ConditionFrame::Exit(ConditionExitContext::Not));
7519                    work_stack.push(ConditionFrame::Enter(inner));
7520                }
7521                SqlExpr::Nested(inner) => {
7522                    work_stack.push(ConditionFrame::Exit(ConditionExitContext::Nested));
7523                    work_stack.push(ConditionFrame::Enter(inner));
7524                }
7525                SqlExpr::IsNull(inner) => {
7526                    let scalar = translate_scalar_with_context_scoped(
7527                        resolver,
7528                        context.clone(),
7529                        inner,
7530                        outer_scopes,
7531                        correlated_tracker.reborrow(),
7532                    )?;
7533                    match scalar {
7534                        llkv_expr::expr::ScalarExpr::Column(column) => {
7535                            // Optimize simple column checks to use Filter
7536                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::Pred(
7537                                llkv_expr::expr::Filter {
7538                                    field_id: column,
7539                                    op: llkv_expr::expr::Operator::IsNull,
7540                                },
7541                            )));
7542                        }
7543                        // NOTE: Do NOT constant-fold IsNull(Literal(Null)) to Literal(true).
7544                        // While technically correct (NULL IS NULL = TRUE), it breaks NULL
7545                        // propagation in boolean expressions like NOT (NOT NULL = NULL).
7546                        // The executor's evaluate_having_expr handles these correctly.
7547                        other => {
7548                            // For all expressions including literals, use the IsNull variant
7549                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::IsNull {
7550                                expr: other,
7551                                negated: false,
7552                            }));
7553                        }
7554                    }
7555                }
7556                SqlExpr::IsNotNull(inner) => {
7557                    let scalar = translate_scalar_with_context_scoped(
7558                        resolver,
7559                        context.clone(),
7560                        inner,
7561                        outer_scopes,
7562                        correlated_tracker.reborrow(),
7563                    )?;
7564                    match scalar {
7565                        llkv_expr::expr::ScalarExpr::Column(column) => {
7566                            // Optimize simple column checks to use Filter
7567                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::Pred(
7568                                llkv_expr::expr::Filter {
7569                                    field_id: column,
7570                                    op: llkv_expr::expr::Operator::IsNotNull,
7571                                },
7572                            )));
7573                        }
7574                        // NOTE: Do NOT constant-fold IsNotNull(Literal(Null)) to Literal(false).
7575                        // While technically correct (NULL IS NOT NULL = FALSE), it breaks NULL
7576                        // propagation in boolean expressions like NOT (NOT NULL = NULL).
7577                        // The executor's evaluate_having_expr handles these correctly.
7578                        other => {
7579                            // For all expressions including literals, use the IsNull variant with negation
7580                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::IsNull {
7581                                expr: other,
7582                                negated: true,
7583                            }));
7584                        }
7585                    }
7586                }
7587                SqlExpr::InList {
7588                    expr: in_expr,
7589                    list,
7590                    negated,
7591                } => {
7592                    if list.is_empty() {
7593                        let result = if *negated {
7594                            llkv_expr::expr::Expr::Literal(true)
7595                        } else {
7596                            llkv_expr::expr::Expr::Literal(false)
7597                        };
7598                        work_stack.push(ConditionFrame::Leaf(result));
7599                    } else {
7600                        let target = translate_scalar_with_context_scoped(
7601                            resolver,
7602                            context.clone(),
7603                            in_expr,
7604                            outer_scopes,
7605                            correlated_tracker.reborrow(),
7606                        )?;
7607                        let mut values = Vec::with_capacity(list.len());
7608                        for value_expr in list {
7609                            let scalar = translate_scalar_with_context_scoped(
7610                                resolver,
7611                                context.clone(),
7612                                value_expr,
7613                                outer_scopes,
7614                                correlated_tracker.reborrow(),
7615                            )?;
7616                            values.push(scalar);
7617                        }
7618
7619                        work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::InList {
7620                            expr: target,
7621                            list: values,
7622                            negated: *negated,
7623                        }));
7624                    }
7625                }
7626                SqlExpr::InSubquery { .. } => {
7627                    return Err(Error::InvalidArgumentError(
7628                        "IN (SELECT ...) subqueries must be materialized before translation".into(),
7629                    ));
7630                }
7631                SqlExpr::Between {
7632                    expr: between_expr,
7633                    negated,
7634                    low,
7635                    high,
7636                } => {
7637                    let between_expr_result = translate_between_expr(
7638                        resolver,
7639                        context.clone(),
7640                        between_expr,
7641                        BetweenBounds {
7642                            lower: low,
7643                            upper: high,
7644                        },
7645                        *negated,
7646                        outer_scopes,
7647                        correlated_tracker.reborrow(),
7648                    )?;
7649                    work_stack.push(ConditionFrame::Leaf(between_expr_result));
7650                }
7651                SqlExpr::Exists { subquery, negated } => {
7652                    // Build nested select plan for the subquery
7653                    let mut nested_scopes = outer_scopes.to_vec();
7654                    nested_scopes.push(context.clone());
7655
7656                    let mut tracker = SubqueryCorrelatedColumnTracker::new();
7657                    let mut nested_subqueries = Vec::new();
7658
7659                    // Translate the subquery in an extended scope
7660                    let subquery_plan = engine.build_select_plan_internal(
7661                        (**subquery).clone(),
7662                        resolver,
7663                        &nested_scopes,
7664                        &mut nested_subqueries,
7665                        Some(&mut tracker),
7666                    )?;
7667
7668                    let subquery_id = llkv_expr::SubqueryId(subqueries.len() as u32);
7669                    let filter_subquery = llkv_plan::FilterSubquery {
7670                        id: subquery_id,
7671                        plan: Box::new(subquery_plan),
7672                        correlated_columns: tracker.into_columns(),
7673                    };
7674                    subqueries.push(filter_subquery);
7675
7676                    work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::Exists(
7677                        llkv_expr::SubqueryExpr {
7678                            id: subquery_id,
7679                            negated: *negated,
7680                        },
7681                    )));
7682                }
7683                other => {
7684                    return Err(Error::InvalidArgumentError(format!(
7685                        "unsupported WHERE clause: {other:?}"
7686                    )));
7687                }
7688            },
7689            ConditionFrame::Leaf(translated) => {
7690                result_stack.push(translated);
7691            }
7692            ConditionFrame::Exit(exit_context) => match exit_context {
7693                ConditionExitContext::And => {
7694                    let right = result_stack.pop().ok_or_else(|| {
7695                        Error::Internal(
7696                            "translate_condition: result stack underflow for And right".into(),
7697                        )
7698                    })?;
7699                    let left = result_stack.pop().ok_or_else(|| {
7700                        Error::Internal(
7701                            "translate_condition: result stack underflow for And left".into(),
7702                        )
7703                    })?;
7704                    result_stack.push(flatten_and(left, right));
7705                }
7706                ConditionExitContext::Or => {
7707                    let right = result_stack.pop().ok_or_else(|| {
7708                        Error::Internal(
7709                            "translate_condition: result stack underflow for Or right".into(),
7710                        )
7711                    })?;
7712                    let left = result_stack.pop().ok_or_else(|| {
7713                        Error::Internal(
7714                            "translate_condition: result stack underflow for Or left".into(),
7715                        )
7716                    })?;
7717                    result_stack.push(flatten_or(left, right));
7718                }
7719                ConditionExitContext::Not => {
7720                    let inner = result_stack.pop().ok_or_else(|| {
7721                        Error::Internal(
7722                            "translate_condition: result stack underflow for Not".into(),
7723                        )
7724                    })?;
7725                    // Optimize: NOT (expr IS NULL) -> expr IS NOT NULL by flipping negation
7726                    match inner {
7727                        llkv_expr::expr::Expr::IsNull { expr, negated } => {
7728                            result_stack.push(llkv_expr::expr::Expr::IsNull {
7729                                expr,
7730                                negated: !negated,
7731                            });
7732                        }
7733                        other => {
7734                            result_stack.push(llkv_expr::expr::Expr::not(other));
7735                        }
7736                    }
7737                }
7738                ConditionExitContext::Nested => {
7739                    // Nested is a no-op - just pass through the inner expression
7740                }
7741            },
7742        }
7743    }
7744
7745    result_stack.pop().ok_or_else(|| {
7746        Error::Internal("translate_condition_with_context: empty result stack".into())
7747    })
7748}
7749
7750fn flatten_and(
7751    left: llkv_expr::expr::Expr<'static, String>,
7752    right: llkv_expr::expr::Expr<'static, String>,
7753) -> llkv_expr::expr::Expr<'static, String> {
7754    let mut children: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
7755    match left {
7756        llkv_expr::expr::Expr::And(mut left_children) => children.append(&mut left_children),
7757        other => children.push(other),
7758    }
7759    match right {
7760        llkv_expr::expr::Expr::And(mut right_children) => children.append(&mut right_children),
7761        other => children.push(other),
7762    }
7763    if children.len() == 1 {
7764        children.into_iter().next().unwrap()
7765    } else {
7766        llkv_expr::expr::Expr::And(children)
7767    }
7768}
7769
7770fn flatten_or(
7771    left: llkv_expr::expr::Expr<'static, String>,
7772    right: llkv_expr::expr::Expr<'static, String>,
7773) -> llkv_expr::expr::Expr<'static, String> {
7774    let mut children: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
7775    match left {
7776        llkv_expr::expr::Expr::Or(mut left_children) => children.append(&mut left_children),
7777        other => children.push(other),
7778    }
7779    match right {
7780        llkv_expr::expr::Expr::Or(mut right_children) => children.append(&mut right_children),
7781        other => children.push(other),
7782    }
7783    if children.len() == 1 {
7784        children.into_iter().next().unwrap()
7785    } else {
7786        llkv_expr::expr::Expr::Or(children)
7787    }
7788}
7789
7790fn peel_unparenthesized_not_chain(expr: &SqlExpr) -> (usize, &SqlExpr) {
7791    let mut count: usize = 0;
7792    let mut current = expr;
7793    while let SqlExpr::UnaryOp {
7794        op: UnaryOperator::Not,
7795        expr: inner,
7796    } = current
7797    {
7798        if matches!(inner.as_ref(), SqlExpr::Nested(_)) {
7799            break;
7800        }
7801        count += 1;
7802        current = inner.as_ref();
7803    }
7804    (count, current)
7805}
7806
7807fn translate_comparison_with_context(
7808    resolver: &IdentifierResolver<'_>,
7809    context: IdentifierContext,
7810    left: &SqlExpr,
7811    op: BinaryOperator,
7812    right: &SqlExpr,
7813    outer_scopes: &[IdentifierContext],
7814    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
7815) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
7816    let (not_count, comparison_left) = peel_unparenthesized_not_chain(left);
7817
7818    let left_scalar = {
7819        let tracker = correlated_tracker.reborrow();
7820        translate_scalar_with_context_scoped(
7821            resolver,
7822            context.clone(),
7823            comparison_left,
7824            outer_scopes,
7825            tracker,
7826        )?
7827    };
7828    let right_scalar = {
7829        let tracker = correlated_tracker.reborrow();
7830        translate_scalar_with_context_scoped(resolver, context, right, outer_scopes, tracker)?
7831    };
7832    let compare_op = match op {
7833        BinaryOperator::Eq => llkv_expr::expr::CompareOp::Eq,
7834        BinaryOperator::NotEq => llkv_expr::expr::CompareOp::NotEq,
7835        BinaryOperator::Lt => llkv_expr::expr::CompareOp::Lt,
7836        BinaryOperator::LtEq => llkv_expr::expr::CompareOp::LtEq,
7837        BinaryOperator::Gt => llkv_expr::expr::CompareOp::Gt,
7838        BinaryOperator::GtEq => llkv_expr::expr::CompareOp::GtEq,
7839        other => {
7840            return Err(Error::InvalidArgumentError(format!(
7841                "unsupported comparison operator: {other:?}"
7842            )));
7843        }
7844    };
7845
7846    let mut expr = llkv_expr::expr::Expr::Compare {
7847        left: left_scalar.clone(),
7848        op: compare_op,
7849        right: right_scalar.clone(),
7850    };
7851
7852    if let (
7853        llkv_expr::expr::ScalarExpr::Column(column),
7854        llkv_expr::expr::ScalarExpr::Literal(literal),
7855    ) = (&left_scalar, &right_scalar)
7856        && let Some(op) = compare_op_to_filter_operator(compare_op, literal)
7857    {
7858        tracing::debug!(
7859            column = ?column,
7860            literal = ?literal,
7861            ?compare_op,
7862            "translate_comparison direct"
7863        );
7864        expr = llkv_expr::expr::Expr::Pred(llkv_expr::expr::Filter {
7865            field_id: column.clone(),
7866            op,
7867        });
7868    } else if let (
7869        llkv_expr::expr::ScalarExpr::Literal(literal),
7870        llkv_expr::expr::ScalarExpr::Column(column),
7871    ) = (&left_scalar, &right_scalar)
7872        && let Some(flipped) = flip_compare_op(compare_op)
7873        && let Some(op) = compare_op_to_filter_operator(flipped, literal)
7874    {
7875        tracing::debug!(
7876            column = ?column,
7877            literal = ?literal,
7878            original_op = ?compare_op,
7879            flipped_op = ?flipped,
7880            "translate_comparison flipped"
7881        );
7882        expr = llkv_expr::expr::Expr::Pred(llkv_expr::expr::Filter {
7883            field_id: column.clone(),
7884            op,
7885        });
7886    }
7887
7888    let mut wrapped = expr;
7889    for _ in 0..not_count {
7890        wrapped = llkv_expr::expr::Expr::Not(Box::new(wrapped));
7891    }
7892
7893    Ok(wrapped)
7894}
7895
7896fn compare_op_to_filter_operator(
7897    op: llkv_expr::expr::CompareOp,
7898    literal: &Literal,
7899) -> Option<llkv_expr::expr::Operator<'static>> {
7900    if matches!(literal, Literal::Null) {
7901        return None;
7902    }
7903    let lit = literal.clone();
7904    tracing::debug!(?op, literal = ?literal, "compare_op_to_filter_operator input");
7905    match op {
7906        llkv_expr::expr::CompareOp::Eq => Some(llkv_expr::expr::Operator::Equals(lit)),
7907        llkv_expr::expr::CompareOp::Lt => Some(llkv_expr::expr::Operator::LessThan(lit)),
7908        llkv_expr::expr::CompareOp::LtEq => Some(llkv_expr::expr::Operator::LessThanOrEquals(lit)),
7909        llkv_expr::expr::CompareOp::Gt => Some(llkv_expr::expr::Operator::GreaterThan(lit)),
7910        llkv_expr::expr::CompareOp::GtEq => {
7911            Some(llkv_expr::expr::Operator::GreaterThanOrEquals(lit))
7912        }
7913        llkv_expr::expr::CompareOp::NotEq => None,
7914    }
7915}
7916
7917fn flip_compare_op(op: llkv_expr::expr::CompareOp) -> Option<llkv_expr::expr::CompareOp> {
7918    match op {
7919        llkv_expr::expr::CompareOp::Eq => Some(llkv_expr::expr::CompareOp::Eq),
7920        llkv_expr::expr::CompareOp::Lt => Some(llkv_expr::expr::CompareOp::Gt),
7921        llkv_expr::expr::CompareOp::LtEq => Some(llkv_expr::expr::CompareOp::GtEq),
7922        llkv_expr::expr::CompareOp::Gt => Some(llkv_expr::expr::CompareOp::Lt),
7923        llkv_expr::expr::CompareOp::GtEq => Some(llkv_expr::expr::CompareOp::LtEq),
7924        llkv_expr::expr::CompareOp::NotEq => None,
7925    }
7926}
7927/// Translate scalar expression with knowledge of the FROM table context.
7928/// This allows us to properly distinguish schema.table.column from column.field.field.
7929fn translate_scalar_with_context(
7930    resolver: &IdentifierResolver<'_>,
7931    context: IdentifierContext,
7932    expr: &SqlExpr,
7933) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
7934    let mut tracker = SubqueryCorrelatedTracker::from_option(None);
7935    translate_scalar_internal(
7936        expr,
7937        Some(resolver),
7938        Some(&context),
7939        &[],
7940        &mut tracker,
7941        None,
7942    )
7943}
7944
7945fn translate_scalar_with_context_scoped(
7946    resolver: &IdentifierResolver<'_>,
7947    context: IdentifierContext,
7948    expr: &SqlExpr,
7949    outer_scopes: &[IdentifierContext],
7950    correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
7951) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
7952    let mut tracker = SubqueryCorrelatedTracker::from_option(correlated_tracker);
7953    translate_scalar_internal(
7954        expr,
7955        Some(resolver),
7956        Some(&context),
7957        outer_scopes,
7958        &mut tracker,
7959        None,
7960    )
7961}
7962
7963#[allow(dead_code)]
7964fn translate_scalar(expr: &SqlExpr) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
7965    let mut tracker = SubqueryCorrelatedTracker::from_option(None);
7966    translate_scalar_internal(expr, None, None, &[], &mut tracker, None)
7967}
7968
7969fn translate_scalar_internal(
7970    expr: &SqlExpr,
7971    resolver: Option<&IdentifierResolver<'_>>,
7972    context: Option<&IdentifierContext>,
7973    outer_scopes: &[IdentifierContext],
7974    tracker: &mut SubqueryCorrelatedTracker<'_>,
7975    mut subquery_resolver: Option<&mut dyn ScalarSubqueryResolver>,
7976) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
7977    // Iterative postorder traversal using the TransformFrame pattern.
7978    // See llkv-plan::traversal module documentation for pattern details.
7979    //
7980    // This avoids stack overflow on deeply nested expressions (50k+ nodes) by using
7981    // explicit work_stack and result_stack instead of recursion.
7982
7983    /// Context passed through Exit frames during scalar expression translation
7984    enum ScalarExitContext {
7985        BinaryOp {
7986            op: BinaryOperator,
7987        },
7988        Compare {
7989            op: llkv_expr::expr::CompareOp,
7990        },
7991        UnaryNot,
7992        UnaryMinus,
7993        UnaryPlus,
7994        Nested,
7995        Cast(DataType),
7996        IsNull {
7997            negated: bool,
7998        },
7999        Between {
8000            negated: bool,
8001        },
8002        InList {
8003            list_len: usize,
8004            negated: bool,
8005        },
8006        Case {
8007            branch_count: usize,
8008            has_operand: bool,
8009            has_else: bool,
8010        },
8011        BuiltinFunction {
8012            func: BuiltinScalarFunction,
8013            arg_count: usize,
8014        },
8015    }
8016
8017    #[derive(Clone, Copy)]
8018    enum BuiltinScalarFunction {
8019        Abs,
8020        Coalesce,
8021        NullIf,
8022    }
8023
8024    type ScalarFrame<'a> =
8025        TransformFrame<'a, SqlExpr, llkv_expr::expr::ScalarExpr<String>, ScalarExitContext>;
8026
8027    let mut work_stack: Vec<ScalarFrame> = vec![ScalarFrame::Enter(expr)];
8028    let mut result_stack: Vec<llkv_expr::expr::ScalarExpr<String>> = Vec::new();
8029
8030    while let Some(frame) = work_stack.pop() {
8031        match frame {
8032            ScalarFrame::Enter(node) => match node {
8033                SqlExpr::Identifier(ident) => {
8034                    if let (Some(resolver), Some(ctx)) = (resolver, context) {
8035                        let parts = vec![ident.value.clone()];
8036                        let tracker_view = tracker.reborrow();
8037                        let expr = resolve_identifier_expr(
8038                            resolver,
8039                            ctx,
8040                            parts,
8041                            outer_scopes,
8042                            tracker_view,
8043                        )?;
8044                        work_stack.push(ScalarFrame::Leaf(expr));
8045                    } else {
8046                        work_stack.push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::column(
8047                            ident.value.clone(),
8048                        )));
8049                    }
8050                }
8051                SqlExpr::CompoundIdentifier(idents) => {
8052                    if idents.is_empty() {
8053                        return Err(Error::InvalidArgumentError(
8054                            "invalid compound identifier".into(),
8055                        ));
8056                    }
8057
8058                    if let (Some(resolver), Some(ctx)) = (resolver, context) {
8059                        let parts: Vec<String> =
8060                            idents.iter().map(|ident| ident.value.clone()).collect();
8061                        let tracker_view = tracker.reborrow();
8062                        let expr = resolve_identifier_expr(
8063                            resolver,
8064                            ctx,
8065                            parts,
8066                            outer_scopes,
8067                            tracker_view,
8068                        )?;
8069                        work_stack.push(ScalarFrame::Leaf(expr));
8070                    } else {
8071                        let column_name = idents[0].value.clone();
8072                        let mut result = llkv_expr::expr::ScalarExpr::column(column_name);
8073
8074                        for part in &idents[1..] {
8075                            let field_name = part.value.clone();
8076                            result = llkv_expr::expr::ScalarExpr::get_field(result, field_name);
8077                        }
8078
8079                        work_stack.push(ScalarFrame::Leaf(result));
8080                    }
8081                }
8082                SqlExpr::Value(value) => {
8083                    let result = literal_from_value(value)?;
8084                    work_stack.push(ScalarFrame::Leaf(result));
8085                }
8086                SqlExpr::BinaryOp { left, op, right } => match op {
8087                    BinaryOperator::Plus
8088                    | BinaryOperator::Minus
8089                    | BinaryOperator::Multiply
8090                    | BinaryOperator::Divide
8091                    | BinaryOperator::Modulo
8092                    | BinaryOperator::And
8093                    | BinaryOperator::Or
8094                    | BinaryOperator::PGBitwiseShiftLeft
8095                    | BinaryOperator::PGBitwiseShiftRight => {
8096                        work_stack.push(ScalarFrame::Exit(ScalarExitContext::BinaryOp {
8097                            op: op.clone(),
8098                        }));
8099                        work_stack.push(ScalarFrame::Enter(right));
8100                        work_stack.push(ScalarFrame::Enter(left));
8101                    }
8102                    BinaryOperator::Eq
8103                    | BinaryOperator::NotEq
8104                    | BinaryOperator::Lt
8105                    | BinaryOperator::LtEq
8106                    | BinaryOperator::Gt
8107                    | BinaryOperator::GtEq => {
8108                        let compare_op = match op {
8109                            BinaryOperator::Eq => llkv_expr::expr::CompareOp::Eq,
8110                            BinaryOperator::NotEq => llkv_expr::expr::CompareOp::NotEq,
8111                            BinaryOperator::Lt => llkv_expr::expr::CompareOp::Lt,
8112                            BinaryOperator::LtEq => llkv_expr::expr::CompareOp::LtEq,
8113                            BinaryOperator::Gt => llkv_expr::expr::CompareOp::Gt,
8114                            BinaryOperator::GtEq => llkv_expr::expr::CompareOp::GtEq,
8115                            _ => unreachable!(),
8116                        };
8117                        work_stack.push(ScalarFrame::Exit(ScalarExitContext::Compare {
8118                            op: compare_op,
8119                        }));
8120                        work_stack.push(ScalarFrame::Enter(right));
8121                        work_stack.push(ScalarFrame::Enter(left));
8122                    }
8123                    other => {
8124                        return Err(Error::InvalidArgumentError(format!(
8125                            "unsupported scalar binary operator: {other:?}"
8126                        )));
8127                    }
8128                },
8129                SqlExpr::UnaryOp {
8130                    op: UnaryOperator::Not,
8131                    expr: inner,
8132                } => {
8133                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::UnaryNot));
8134                    work_stack.push(ScalarFrame::Enter(inner));
8135                }
8136                SqlExpr::UnaryOp {
8137                    op: UnaryOperator::Minus,
8138                    expr: inner,
8139                } => {
8140                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::UnaryMinus));
8141                    work_stack.push(ScalarFrame::Enter(inner));
8142                }
8143                SqlExpr::UnaryOp {
8144                    op: UnaryOperator::Plus,
8145                    expr: inner,
8146                } => {
8147                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::UnaryPlus));
8148                    work_stack.push(ScalarFrame::Enter(inner));
8149                }
8150                SqlExpr::Nested(inner) => {
8151                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Nested));
8152                    work_stack.push(ScalarFrame::Enter(inner));
8153                }
8154                SqlExpr::Cast {
8155                    expr: inner,
8156                    data_type,
8157                    ..
8158                } => {
8159                    let target_type = arrow_type_from_sql(data_type)?;
8160                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Cast(target_type)));
8161                    work_stack.push(ScalarFrame::Enter(inner));
8162                }
8163                SqlExpr::Case {
8164                    operand,
8165                    conditions,
8166                    else_result,
8167                    ..
8168                } => {
8169                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Case {
8170                        branch_count: conditions.len(),
8171                        has_operand: operand.is_some(),
8172                        has_else: else_result.is_some(),
8173                    }));
8174                    if let Some(else_expr) = else_result.as_deref() {
8175                        work_stack.push(ScalarFrame::Enter(else_expr));
8176                    }
8177                    for case_when in conditions.iter().rev() {
8178                        work_stack.push(ScalarFrame::Enter(&case_when.result));
8179                        work_stack.push(ScalarFrame::Enter(&case_when.condition));
8180                    }
8181                    if let Some(opnd) = operand.as_deref() {
8182                        work_stack.push(ScalarFrame::Enter(opnd));
8183                    }
8184                }
8185                SqlExpr::InList {
8186                    expr: in_expr,
8187                    list,
8188                    negated,
8189                } => {
8190                    if list.is_empty() {
8191                        let literal_value = if *negated {
8192                            llkv_expr::expr::ScalarExpr::literal(Literal::Integer(1))
8193                        } else {
8194                            llkv_expr::expr::ScalarExpr::literal(Literal::Integer(0))
8195                        };
8196                        work_stack.push(ScalarFrame::Leaf(literal_value));
8197                    } else {
8198                        work_stack.push(ScalarFrame::Exit(ScalarExitContext::InList {
8199                            list_len: list.len(),
8200                            negated: *negated,
8201                        }));
8202                        for value_expr in list.iter().rev() {
8203                            work_stack.push(ScalarFrame::Enter(value_expr));
8204                        }
8205                        work_stack.push(ScalarFrame::Enter(in_expr));
8206                    }
8207                }
8208                SqlExpr::IsNull(inner) => {
8209                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::IsNull {
8210                        negated: false,
8211                    }));
8212                    work_stack.push(ScalarFrame::Enter(inner));
8213                }
8214                SqlExpr::IsNotNull(inner) => {
8215                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::IsNull {
8216                        negated: true,
8217                    }));
8218                    work_stack.push(ScalarFrame::Enter(inner));
8219                }
8220                SqlExpr::Between {
8221                    expr: between_expr,
8222                    negated,
8223                    low,
8224                    high,
8225                } => {
8226                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Between {
8227                        negated: *negated,
8228                    }));
8229                    work_stack.push(ScalarFrame::Enter(high));
8230                    work_stack.push(ScalarFrame::Enter(low));
8231                    work_stack.push(ScalarFrame::Enter(between_expr));
8232                }
8233                SqlExpr::Function(func) => {
8234                    if let Some(agg_call) = try_parse_aggregate_function(
8235                        func,
8236                        resolver,
8237                        context,
8238                        outer_scopes,
8239                        tracker,
8240                    )? {
8241                        work_stack.push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::aggregate(
8242                            agg_call,
8243                        )));
8244                    } else {
8245                        use sqlparser::ast::{
8246                            FunctionArg, FunctionArgExpr, FunctionArguments, ObjectNamePart,
8247                        };
8248
8249                        if func.uses_odbc_syntax
8250                            || !matches!(func.parameters, FunctionArguments::None)
8251                            || func.filter.is_some()
8252                            || func.null_treatment.is_some()
8253                            || func.over.is_some()
8254                            || !func.within_group.is_empty()
8255                        {
8256                            return Err(Error::InvalidArgumentError(format!(
8257                                "unsupported function in scalar expression: {:?}",
8258                                func.name
8259                            )));
8260                        }
8261
8262                        let func_name = if func.name.0.len() == 1 {
8263                            match &func.name.0[0] {
8264                                ObjectNamePart::Identifier(ident) => {
8265                                    ident.value.to_ascii_lowercase()
8266                                }
8267                                _ => {
8268                                    return Err(Error::InvalidArgumentError(format!(
8269                                        "unsupported function in scalar expression: {:?}",
8270                                        func.name
8271                                    )));
8272                                }
8273                            }
8274                        } else {
8275                            return Err(Error::InvalidArgumentError(format!(
8276                                "unsupported function in scalar expression: {:?}",
8277                                func.name
8278                            )));
8279                        };
8280
8281                        match func_name.as_str() {
8282                            "abs" => {
8283                                let args_slice: &[FunctionArg] = match &func.args {
8284                                    FunctionArguments::List(list) => {
8285                                        if list.duplicate_treatment.is_some()
8286                                            || !list.clauses.is_empty()
8287                                        {
8288                                            return Err(Error::InvalidArgumentError(
8289                                                "ABS does not support qualifiers".into(),
8290                                            ));
8291                                        }
8292                                        &list.args
8293                                    }
8294                                    _ => {
8295                                        return Err(Error::InvalidArgumentError(
8296                                            "ABS requires exactly one argument".into(),
8297                                        ));
8298                                    }
8299                                };
8300
8301                                if args_slice.len() != 1 {
8302                                    return Err(Error::InvalidArgumentError(
8303                                        "ABS requires exactly one argument".into(),
8304                                    ));
8305                                }
8306
8307                                let arg_expr = match &args_slice[0] {
8308                                    FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8309                                    _ => {
8310                                        return Err(Error::InvalidArgumentError(
8311                                            "ABS argument must be an expression".into(),
8312                                        ));
8313                                    }
8314                                };
8315
8316                                work_stack.push(ScalarFrame::Exit(
8317                                    ScalarExitContext::BuiltinFunction {
8318                                        func: BuiltinScalarFunction::Abs,
8319                                        arg_count: 1,
8320                                    },
8321                                ));
8322                                work_stack.push(ScalarFrame::Enter(arg_expr));
8323                                continue;
8324                            }
8325                            "coalesce" => {
8326                                let args_slice: &[FunctionArg] = match &func.args {
8327                                    FunctionArguments::List(list) => {
8328                                        if list.duplicate_treatment.is_some()
8329                                            || !list.clauses.is_empty()
8330                                        {
8331                                            return Err(Error::InvalidArgumentError(
8332                                                "COALESCE does not support qualifiers".into(),
8333                                            ));
8334                                        }
8335                                        &list.args
8336                                    }
8337                                    _ => {
8338                                        return Err(Error::InvalidArgumentError(
8339                                            "COALESCE requires at least one argument".into(),
8340                                        ));
8341                                    }
8342                                };
8343
8344                                if args_slice.is_empty() {
8345                                    return Err(Error::InvalidArgumentError(
8346                                        "COALESCE requires at least one argument".into(),
8347                                    ));
8348                                }
8349
8350                                work_stack.push(ScalarFrame::Exit(
8351                                    ScalarExitContext::BuiltinFunction {
8352                                        func: BuiltinScalarFunction::Coalesce,
8353                                        arg_count: args_slice.len(),
8354                                    },
8355                                ));
8356
8357                                for arg in args_slice.iter().rev() {
8358                                    let arg_expr = match arg {
8359                                        FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8360                                        _ => {
8361                                            return Err(Error::InvalidArgumentError(
8362                                                "COALESCE arguments must be expressions".into(),
8363                                            ));
8364                                        }
8365                                    };
8366                                    work_stack.push(ScalarFrame::Enter(arg_expr));
8367                                }
8368                                continue;
8369                            }
8370                            "nullif" => {
8371                                let args_slice: &[FunctionArg] = match &func.args {
8372                                    FunctionArguments::List(list) => {
8373                                        if list.duplicate_treatment.is_some()
8374                                            || !list.clauses.is_empty()
8375                                        {
8376                                            return Err(Error::InvalidArgumentError(
8377                                                "NULLIF does not support qualifiers".into(),
8378                                            ));
8379                                        }
8380                                        &list.args
8381                                    }
8382                                    _ => {
8383                                        return Err(Error::InvalidArgumentError(
8384                                            "NULLIF requires exactly two arguments".into(),
8385                                        ));
8386                                    }
8387                                };
8388
8389                                if args_slice.len() != 2 {
8390                                    return Err(Error::InvalidArgumentError(
8391                                        "NULLIF requires exactly two arguments".into(),
8392                                    ));
8393                                }
8394
8395                                work_stack.push(ScalarFrame::Exit(
8396                                    ScalarExitContext::BuiltinFunction {
8397                                        func: BuiltinScalarFunction::NullIf,
8398                                        arg_count: 2,
8399                                    },
8400                                ));
8401
8402                                for arg in args_slice.iter().rev() {
8403                                    let arg_expr = match arg {
8404                                        FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8405                                        _ => {
8406                                            return Err(Error::InvalidArgumentError(
8407                                                "NULLIF arguments must be expressions".into(),
8408                                            ));
8409                                        }
8410                                    };
8411                                    work_stack.push(ScalarFrame::Enter(arg_expr));
8412                                }
8413                                continue;
8414                            }
8415                            _ => {
8416                                return Err(Error::InvalidArgumentError(format!(
8417                                    "unsupported function in scalar expression: {:?}",
8418                                    func.name
8419                                )));
8420                            }
8421                        }
8422                    }
8423                }
8424                SqlExpr::Dictionary(fields) => {
8425                    // Process dictionary fields iteratively to avoid recursion
8426                    let mut struct_fields = Vec::new();
8427                    for entry in fields {
8428                        let key = entry.key.value.clone();
8429                        // Reuse scalar translation for nested values while honoring identifier context.
8430                        // Dictionaries rarely nest deeply, so recursion here is acceptable.
8431                        let mut tracker_view = tracker.reborrow();
8432                        let value_expr = translate_scalar_internal(
8433                            &entry.value,
8434                            resolver,
8435                            context,
8436                            outer_scopes,
8437                            &mut tracker_view,
8438                            None,
8439                        )?;
8440                        match value_expr {
8441                            llkv_expr::expr::ScalarExpr::Literal(lit) => {
8442                                struct_fields.push((key, Box::new(lit)));
8443                            }
8444                            _ => {
8445                                return Err(Error::InvalidArgumentError(
8446                                    "Dictionary values must be literals".to_string(),
8447                                ));
8448                            }
8449                        }
8450                    }
8451                    work_stack.push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::literal(
8452                        Literal::Struct(struct_fields),
8453                    )));
8454                }
8455                SqlExpr::Subquery(subquery) => {
8456                    let handler = subquery_resolver.as_mut().ok_or_else(|| {
8457                        Error::InvalidArgumentError(
8458                            "Correlated scalar subqueries not yet fully implemented - requires plan-level support".
8459                                to_string(),
8460                        )
8461                    })?;
8462                    let resolver_ref = resolver.ok_or_else(|| {
8463                        Error::InvalidArgumentError(
8464                            "scalar subquery translation requires identifier resolver".into(),
8465                        )
8466                    })?;
8467                    let context_ref = context.ok_or_else(|| {
8468                        Error::InvalidArgumentError(
8469                            "scalar subquery translation requires identifier context".into(),
8470                        )
8471                    })?;
8472                    let translated = handler.handle_scalar_subquery(
8473                        subquery.as_ref(),
8474                        resolver_ref,
8475                        context_ref,
8476                        outer_scopes,
8477                    )?;
8478                    work_stack.push(ScalarFrame::Leaf(translated));
8479                }
8480                other => {
8481                    return Err(Error::InvalidArgumentError(format!(
8482                        "unsupported scalar expression: {other:?}"
8483                    )));
8484                }
8485            },
8486            ScalarFrame::Leaf(translated) => {
8487                result_stack.push(translated);
8488            }
8489            ScalarFrame::Exit(exit_context) => match exit_context {
8490                ScalarExitContext::BinaryOp { op } => {
8491                    let right_expr = result_stack.pop().ok_or_else(|| {
8492                        Error::Internal(
8493                            "translate_scalar: result stack underflow for BinaryOp right".into(),
8494                        )
8495                    })?;
8496                    let left_expr = result_stack.pop().ok_or_else(|| {
8497                        Error::Internal(
8498                            "translate_scalar: result stack underflow for BinaryOp left".into(),
8499                        )
8500                    })?;
8501                    match op {
8502                        BinaryOperator::Plus => {
8503                            let expr = llkv_expr::expr::ScalarExpr::binary(
8504                                left_expr,
8505                                llkv_expr::expr::BinaryOp::Add,
8506                                right_expr,
8507                            );
8508                            result_stack.push(expr);
8509                        }
8510                        BinaryOperator::Minus => {
8511                            let expr = llkv_expr::expr::ScalarExpr::binary(
8512                                left_expr,
8513                                llkv_expr::expr::BinaryOp::Subtract,
8514                                right_expr,
8515                            );
8516                            result_stack.push(expr);
8517                        }
8518                        BinaryOperator::Multiply => {
8519                            let expr = llkv_expr::expr::ScalarExpr::binary(
8520                                left_expr,
8521                                llkv_expr::expr::BinaryOp::Multiply,
8522                                right_expr,
8523                            );
8524                            result_stack.push(expr);
8525                        }
8526                        BinaryOperator::Divide => {
8527                            let expr = llkv_expr::expr::ScalarExpr::binary(
8528                                left_expr,
8529                                llkv_expr::expr::BinaryOp::Divide,
8530                                right_expr,
8531                            );
8532                            result_stack.push(expr);
8533                        }
8534                        BinaryOperator::Modulo => {
8535                            let expr = llkv_expr::expr::ScalarExpr::binary(
8536                                left_expr,
8537                                llkv_expr::expr::BinaryOp::Modulo,
8538                                right_expr,
8539                            );
8540                            result_stack.push(expr);
8541                        }
8542                        BinaryOperator::And => {
8543                            let expr = llkv_expr::expr::ScalarExpr::binary(
8544                                left_expr,
8545                                llkv_expr::expr::BinaryOp::And,
8546                                right_expr,
8547                            );
8548                            result_stack.push(expr);
8549                        }
8550                        BinaryOperator::Or => {
8551                            let expr = llkv_expr::expr::ScalarExpr::binary(
8552                                left_expr,
8553                                llkv_expr::expr::BinaryOp::Or,
8554                                right_expr,
8555                            );
8556                            result_stack.push(expr);
8557                        }
8558                        BinaryOperator::PGBitwiseShiftLeft => {
8559                            let expr = llkv_expr::expr::ScalarExpr::binary(
8560                                left_expr,
8561                                llkv_expr::expr::BinaryOp::BitwiseShiftLeft,
8562                                right_expr,
8563                            );
8564                            result_stack.push(expr);
8565                        }
8566                        BinaryOperator::PGBitwiseShiftRight => {
8567                            let expr = llkv_expr::expr::ScalarExpr::binary(
8568                                left_expr,
8569                                llkv_expr::expr::BinaryOp::BitwiseShiftRight,
8570                                right_expr,
8571                            );
8572                            result_stack.push(expr);
8573                        }
8574                        other => {
8575                            return Err(Error::InvalidArgumentError(format!(
8576                                "unsupported scalar binary operator: {other:?}"
8577                            )));
8578                        }
8579                    }
8580                }
8581                ScalarExitContext::Compare { op } => {
8582                    let right_expr = result_stack.pop().ok_or_else(|| {
8583                        Error::Internal(
8584                            "translate_scalar: result stack underflow for Compare right".into(),
8585                        )
8586                    })?;
8587                    let left_expr = result_stack.pop().ok_or_else(|| {
8588                        Error::Internal(
8589                            "translate_scalar: result stack underflow for Compare left".into(),
8590                        )
8591                    })?;
8592                    result_stack.push(llkv_expr::expr::ScalarExpr::compare(
8593                        left_expr, op, right_expr,
8594                    ));
8595                }
8596                ScalarExitContext::BuiltinFunction { func, arg_count } => {
8597                    if result_stack.len() < arg_count {
8598                        return Err(Error::Internal(
8599                            "translate_scalar: result stack underflow for builtin function".into(),
8600                        ));
8601                    }
8602
8603                    let mut args: Vec<llkv_expr::expr::ScalarExpr<String>> =
8604                        Vec::with_capacity(arg_count);
8605                    for _ in 0..arg_count {
8606                        if let Some(expr) = result_stack.pop() {
8607                            args.push(expr);
8608                        }
8609                    }
8610                    args.reverse();
8611
8612                    let result_expr = match func {
8613                        BuiltinScalarFunction::Abs => {
8614                            debug_assert_eq!(args.len(), 1);
8615                            build_abs_case_expr(args.pop().expect("ABS expects one argument"))
8616                        }
8617                        BuiltinScalarFunction::Coalesce => {
8618                            llkv_expr::expr::ScalarExpr::coalesce(args)
8619                        }
8620                        BuiltinScalarFunction::NullIf => {
8621                            debug_assert_eq!(args.len(), 2);
8622                            let left = args.remove(0);
8623                            let right = args.remove(0);
8624                            let condition = llkv_expr::expr::ScalarExpr::compare(
8625                                left.clone(),
8626                                llkv_expr::expr::CompareOp::Eq,
8627                                right,
8628                            );
8629                            llkv_expr::expr::ScalarExpr::Case {
8630                                operand: None,
8631                                branches: vec![(
8632                                    condition,
8633                                    llkv_expr::expr::ScalarExpr::literal(Literal::Null),
8634                                )],
8635                                else_expr: Some(Box::new(left)),
8636                            }
8637                        }
8638                    };
8639
8640                    result_stack.push(result_expr);
8641                }
8642                ScalarExitContext::UnaryMinus => {
8643                    let inner = result_stack.pop().ok_or_else(|| {
8644                        Error::Internal(
8645                            "translate_scalar: result stack underflow for UnaryMinus".into(),
8646                        )
8647                    })?;
8648                    match inner {
8649                        llkv_expr::expr::ScalarExpr::Literal(lit) => match lit {
8650                            Literal::Integer(v) => {
8651                                result_stack.push(llkv_expr::expr::ScalarExpr::literal(
8652                                    Literal::Integer(-v),
8653                                ));
8654                            }
8655                            Literal::Float(v) => {
8656                                result_stack
8657                                    .push(llkv_expr::expr::ScalarExpr::literal(Literal::Float(-v)));
8658                            }
8659                            Literal::Boolean(_) => {
8660                                return Err(Error::InvalidArgumentError(
8661                                    "cannot negate boolean literal".into(),
8662                                ));
8663                            }
8664                            Literal::String(_) => {
8665                                return Err(Error::InvalidArgumentError(
8666                                    "cannot negate string literal".into(),
8667                                ));
8668                            }
8669                            Literal::Struct(_) => {
8670                                return Err(Error::InvalidArgumentError(
8671                                    "cannot negate struct literal".into(),
8672                                ));
8673                            }
8674                            Literal::Null => {
8675                                result_stack
8676                                    .push(llkv_expr::expr::ScalarExpr::literal(Literal::Null));
8677                            }
8678                        },
8679                        other => {
8680                            let zero = llkv_expr::expr::ScalarExpr::literal(Literal::Integer(0));
8681                            result_stack.push(llkv_expr::expr::ScalarExpr::binary(
8682                                zero,
8683                                llkv_expr::expr::BinaryOp::Subtract,
8684                                other,
8685                            ));
8686                        }
8687                    }
8688                }
8689                ScalarExitContext::UnaryNot => {
8690                    let inner = result_stack.pop().ok_or_else(|| {
8691                        Error::Internal(
8692                            "translate_scalar: result stack underflow for UnaryNot".into(),
8693                        )
8694                    })?;
8695                    result_stack.push(llkv_expr::expr::ScalarExpr::logical_not(inner));
8696                }
8697                ScalarExitContext::UnaryPlus => {
8698                    // Unary plus is an identity operation in SQL - it returns the value unchanged.
8699                    // Unlike unary minus, it does NOT force numeric conversion; it's purely syntactic.
8700                    // SQLite treats `+col` identically to `col` in all contexts.
8701                    let inner = result_stack.pop().ok_or_else(|| {
8702                        Error::Internal(
8703                            "translate_scalar: result stack underflow for UnaryPlus".into(),
8704                        )
8705                    })?;
8706                    result_stack.push(inner);
8707                }
8708                ScalarExitContext::Nested => {
8709                    // Nested is a no-op - just pass through
8710                }
8711                ScalarExitContext::Cast(target_type) => {
8712                    let inner = result_stack.pop().ok_or_else(|| {
8713                        Error::Internal("translate_scalar: result stack underflow for CAST".into())
8714                    })?;
8715                    result_stack.push(llkv_expr::expr::ScalarExpr::cast(inner, target_type));
8716                }
8717                ScalarExitContext::InList { list_len, negated } => {
8718                    let mut list_exprs = Vec::with_capacity(list_len);
8719                    for _ in 0..list_len {
8720                        let value_expr = result_stack.pop().ok_or_else(|| {
8721                            Error::Internal(
8722                                "translate_scalar: result stack underflow for IN list value".into(),
8723                            )
8724                        })?;
8725                        list_exprs.push(value_expr);
8726                    }
8727                    list_exprs.reverse();
8728
8729                    let target_expr = result_stack.pop().ok_or_else(|| {
8730                        Error::Internal(
8731                            "translate_scalar: result stack underflow for IN list target".into(),
8732                        )
8733                    })?;
8734
8735                    let mut comparisons: Vec<llkv_expr::expr::ScalarExpr<String>> =
8736                        Vec::with_capacity(list_len);
8737                    for value in &list_exprs {
8738                        comparisons.push(llkv_expr::expr::ScalarExpr::compare(
8739                            target_expr.clone(),
8740                            llkv_expr::expr::CompareOp::Eq,
8741                            value.clone(),
8742                        ));
8743                    }
8744
8745                    let mut branches: Vec<(
8746                        llkv_expr::expr::ScalarExpr<String>,
8747                        llkv_expr::expr::ScalarExpr<String>,
8748                    )> = Vec::with_capacity(list_len.saturating_mul(2));
8749
8750                    for comparison in &comparisons {
8751                        branches.push((
8752                            comparison.clone(),
8753                            llkv_expr::expr::ScalarExpr::literal(Literal::Integer(1)),
8754                        ));
8755                    }
8756
8757                    for comparison in comparisons {
8758                        let comparison_is_null =
8759                            llkv_expr::expr::ScalarExpr::is_null(comparison, false);
8760                        branches.push((
8761                            comparison_is_null,
8762                            llkv_expr::expr::ScalarExpr::literal(Literal::Null),
8763                        ));
8764                    }
8765
8766                    let else_expr = Some(llkv_expr::expr::ScalarExpr::literal(Literal::Integer(0)));
8767                    let in_result = llkv_expr::expr::ScalarExpr::case(None, branches, else_expr);
8768                    let final_expr = if negated {
8769                        llkv_expr::expr::ScalarExpr::logical_not(in_result)
8770                    } else {
8771                        in_result
8772                    };
8773
8774                    result_stack.push(final_expr);
8775                }
8776                ScalarExitContext::Case {
8777                    branch_count,
8778                    has_operand,
8779                    has_else,
8780                } => {
8781                    let else_expr = if has_else {
8782                        Some(result_stack.pop().ok_or_else(|| {
8783                            Error::Internal(
8784                                "translate_scalar: result stack underflow for CASE ELSE".into(),
8785                            )
8786                        })?)
8787                    } else {
8788                        None
8789                    };
8790
8791                    let mut branches_rev = Vec::with_capacity(branch_count);
8792                    for _ in 0..branch_count {
8793                        let then_expr = result_stack.pop().ok_or_else(|| {
8794                            Error::Internal(
8795                                "translate_scalar: result stack underflow for CASE THEN".into(),
8796                            )
8797                        })?;
8798                        let when_expr = result_stack.pop().ok_or_else(|| {
8799                            Error::Internal(
8800                                "translate_scalar: result stack underflow for CASE WHEN".into(),
8801                            )
8802                        })?;
8803                        branches_rev.push((when_expr, then_expr));
8804                    }
8805                    branches_rev.reverse();
8806
8807                    let operand_expr = if has_operand {
8808                        Some(result_stack.pop().ok_or_else(|| {
8809                            Error::Internal(
8810                                "translate_scalar: result stack underflow for CASE operand".into(),
8811                            )
8812                        })?)
8813                    } else {
8814                        None
8815                    };
8816
8817                    let case_expr =
8818                        llkv_expr::expr::ScalarExpr::case(operand_expr, branches_rev, else_expr);
8819                    result_stack.push(case_expr);
8820                }
8821                ScalarExitContext::IsNull { negated } => {
8822                    let inner = result_stack.pop().ok_or_else(|| {
8823                        Error::Internal(
8824                            "translate_scalar: result stack underflow for IS NULL operand".into(),
8825                        )
8826                    })?;
8827                    result_stack.push(llkv_expr::expr::ScalarExpr::is_null(inner, negated));
8828                }
8829                ScalarExitContext::Between { negated } => {
8830                    let high = result_stack.pop().ok_or_else(|| {
8831                        Error::Internal(
8832                            "translate_scalar: result stack underflow for BETWEEN upper".into(),
8833                        )
8834                    })?;
8835                    let low = result_stack.pop().ok_or_else(|| {
8836                        Error::Internal(
8837                            "translate_scalar: result stack underflow for BETWEEN lower".into(),
8838                        )
8839                    })?;
8840                    let expr_value = result_stack.pop().ok_or_else(|| {
8841                        Error::Internal(
8842                            "translate_scalar: result stack underflow for BETWEEN operand".into(),
8843                        )
8844                    })?;
8845
8846                    let between_expr = if negated {
8847                        let less_than = llkv_expr::expr::ScalarExpr::compare(
8848                            expr_value.clone(),
8849                            llkv_expr::expr::CompareOp::Lt,
8850                            low.clone(),
8851                        );
8852                        let greater_than = llkv_expr::expr::ScalarExpr::compare(
8853                            expr_value,
8854                            llkv_expr::expr::CompareOp::Gt,
8855                            high,
8856                        );
8857                        llkv_expr::expr::ScalarExpr::binary(
8858                            less_than,
8859                            llkv_expr::expr::BinaryOp::Or,
8860                            greater_than,
8861                        )
8862                    } else {
8863                        let greater_or_equal = llkv_expr::expr::ScalarExpr::compare(
8864                            expr_value.clone(),
8865                            llkv_expr::expr::CompareOp::GtEq,
8866                            low,
8867                        );
8868                        let less_or_equal = llkv_expr::expr::ScalarExpr::compare(
8869                            expr_value,
8870                            llkv_expr::expr::CompareOp::LtEq,
8871                            high,
8872                        );
8873                        llkv_expr::expr::ScalarExpr::binary(
8874                            greater_or_equal,
8875                            llkv_expr::expr::BinaryOp::And,
8876                            less_or_equal,
8877                        )
8878                    };
8879                    result_stack.push(between_expr);
8880                }
8881            },
8882        }
8883    }
8884
8885    result_stack
8886        .pop()
8887        .ok_or_else(|| Error::Internal("translate_scalar: empty result stack".into()))
8888}
8889
8890struct ScalarSubqueryPlanner<'engine, 'vec> {
8891    engine: &'engine SqlEngine,
8892    scalar_subqueries: &'vec mut Vec<llkv_plan::ScalarSubquery>,
8893}
8894
8895impl<'engine, 'vec> ScalarSubqueryResolver for ScalarSubqueryPlanner<'engine, 'vec> {
8896    fn handle_scalar_subquery(
8897        &mut self,
8898        subquery: &Query,
8899        resolver: &IdentifierResolver<'_>,
8900        context: &IdentifierContext,
8901        outer_scopes: &[IdentifierContext],
8902    ) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
8903        let mut nested_scopes = outer_scopes.to_vec();
8904        nested_scopes.push(context.clone());
8905
8906        let mut tracker = SubqueryCorrelatedColumnTracker::new();
8907        let mut nested_filter_subqueries = Vec::new();
8908
8909        let plan = self.engine.build_select_plan_internal(
8910            subquery.clone(),
8911            resolver,
8912            &nested_scopes,
8913            &mut nested_filter_subqueries,
8914            Some(&mut tracker),
8915        )?;
8916
8917        debug_assert!(nested_filter_subqueries.is_empty());
8918
8919        let id = u32::try_from(self.scalar_subqueries.len()).map_err(|_| {
8920            Error::InvalidArgumentError(
8921                "scalar subquery limit exceeded for current query".to_string(),
8922            )
8923        })?;
8924        let subquery_id = llkv_expr::SubqueryId(id);
8925        self.scalar_subqueries.push(llkv_plan::ScalarSubquery {
8926            id: subquery_id,
8927            plan: Box::new(plan),
8928            correlated_columns: tracker.into_columns(),
8929        });
8930
8931        Ok(llkv_expr::expr::ScalarExpr::scalar_subquery(subquery_id))
8932    }
8933}
8934
8935fn build_abs_case_expr(
8936    arg: llkv_expr::expr::ScalarExpr<String>,
8937) -> llkv_expr::expr::ScalarExpr<String> {
8938    use llkv_expr::expr::{BinaryOp, CompareOp, ScalarExpr};
8939
8940    let zero = ScalarExpr::literal(Literal::Integer(0));
8941    let condition = ScalarExpr::compare(arg.clone(), CompareOp::Lt, zero.clone());
8942    let negated = ScalarExpr::binary(zero.clone(), BinaryOp::Subtract, arg.clone());
8943
8944    ScalarExpr::case(None, vec![(condition, negated)], Some(arg))
8945}
8946
8947fn literal_from_value(value: &ValueWithSpan) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
8948    match &value.value {
8949        Value::Number(text, _) => {
8950            if text.contains(['.', 'e', 'E']) {
8951                let parsed = text.parse::<f64>().map_err(|err| {
8952                    Error::InvalidArgumentError(format!("invalid float literal: {err}"))
8953                })?;
8954                Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Float(parsed)))
8955            } else {
8956                let parsed = text.parse::<i128>().map_err(|err| {
8957                    Error::InvalidArgumentError(format!("invalid integer literal: {err}"))
8958                })?;
8959                Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Integer(
8960                    parsed,
8961                )))
8962            }
8963        }
8964        Value::Boolean(value) => Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Boolean(
8965            *value,
8966        ))),
8967        Value::Null => Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Null)),
8968        other => {
8969            if let Some(text) = other.clone().into_string() {
8970                Ok(llkv_expr::expr::ScalarExpr::literal(Literal::String(text)))
8971            } else {
8972                Err(Error::InvalidArgumentError(format!(
8973                    "unsupported literal: {other:?}"
8974                )))
8975            }
8976        }
8977    }
8978}
8979
8980fn resolve_assignment_column_name(target: &AssignmentTarget) -> SqlResult<String> {
8981    match target {
8982        AssignmentTarget::ColumnName(name) => {
8983            if name.0.len() != 1 {
8984                return Err(Error::InvalidArgumentError(
8985                    "qualified column names in UPDATE assignments are not supported yet".into(),
8986                ));
8987            }
8988            match &name.0[0] {
8989                ObjectNamePart::Identifier(ident) => Ok(ident.value.clone()),
8990                other => Err(Error::InvalidArgumentError(format!(
8991                    "unsupported column reference in UPDATE assignment: {other:?}"
8992                ))),
8993            }
8994        }
8995        AssignmentTarget::Tuple(_) => Err(Error::InvalidArgumentError(
8996            "tuple assignments are not supported yet".into(),
8997        )),
8998    }
8999}
9000
9001fn arrow_type_from_sql(data_type: &SqlDataType) -> SqlResult<arrow::datatypes::DataType> {
9002    use arrow::datatypes::DataType;
9003    match data_type {
9004        SqlDataType::Int(_)
9005        | SqlDataType::Integer(_)
9006        | SqlDataType::BigInt(_)
9007        | SqlDataType::SmallInt(_)
9008        | SqlDataType::TinyInt(_) => Ok(DataType::Int64),
9009        SqlDataType::Float(_)
9010        | SqlDataType::Real
9011        | SqlDataType::Double(_)
9012        | SqlDataType::DoublePrecision => Ok(DataType::Float64),
9013        SqlDataType::Text
9014        | SqlDataType::String(_)
9015        | SqlDataType::Varchar(_)
9016        | SqlDataType::Char(_)
9017        | SqlDataType::Uuid => Ok(DataType::Utf8),
9018        SqlDataType::Date => Ok(DataType::Date32),
9019        SqlDataType::Decimal(_) | SqlDataType::Numeric(_) => Ok(DataType::Float64),
9020        SqlDataType::Boolean => Ok(DataType::Boolean),
9021        SqlDataType::Custom(name, args) => {
9022            if name.0.len() == 1
9023                && let ObjectNamePart::Identifier(ident) = &name.0[0]
9024                && ident.value.eq_ignore_ascii_case("row")
9025            {
9026                return row_type_to_arrow(data_type, args);
9027            }
9028            Err(Error::InvalidArgumentError(format!(
9029                "unsupported SQL data type: {data_type:?}"
9030            )))
9031        }
9032        other => Err(Error::InvalidArgumentError(format!(
9033            "unsupported SQL data type: {other:?}"
9034        ))),
9035    }
9036}
9037
9038fn row_type_to_arrow(
9039    data_type: &SqlDataType,
9040    tokens: &[String],
9041) -> SqlResult<arrow::datatypes::DataType> {
9042    use arrow::datatypes::{DataType, Field, FieldRef, Fields};
9043
9044    let row_str = data_type.to_string();
9045    if tokens.is_empty() {
9046        return Err(Error::InvalidArgumentError(
9047            "ROW type must define at least one field".into(),
9048        ));
9049    }
9050
9051    let dialect = GenericDialect {};
9052    let field_definitions = resolve_row_field_types(tokens, &dialect).map_err(|err| {
9053        Error::InvalidArgumentError(format!("unable to parse ROW type '{row_str}': {err}"))
9054    })?;
9055
9056    let mut fields: Vec<FieldRef> = Vec::with_capacity(field_definitions.len());
9057    for (field_name, field_type) in field_definitions {
9058        let arrow_field_type = arrow_type_from_sql(&field_type)?;
9059        fields.push(Arc::new(Field::new(field_name, arrow_field_type, true)));
9060    }
9061
9062    let struct_fields: Fields = fields.into();
9063    Ok(DataType::Struct(struct_fields))
9064}
9065
9066fn resolve_row_field_types(
9067    tokens: &[String],
9068    dialect: &GenericDialect,
9069) -> SqlResult<Vec<(String, SqlDataType)>> {
9070    if tokens.is_empty() {
9071        return Err(Error::InvalidArgumentError(
9072            "ROW type must define at least one field".into(),
9073        ));
9074    }
9075
9076    let mut start = 0;
9077    let mut end = tokens.len();
9078    if tokens[start] == "(" {
9079        if end == 0 || tokens[end - 1] != ")" {
9080            return Err(Error::InvalidArgumentError(
9081                "ROW type is missing closing ')'".into(),
9082            ));
9083        }
9084        start += 1;
9085        end -= 1;
9086    } else if tokens[end - 1] == ")" {
9087        return Err(Error::InvalidArgumentError(
9088            "ROW type contains unmatched ')'".into(),
9089        ));
9090    }
9091
9092    let slice = &tokens[start..end];
9093    if slice.is_empty() {
9094        return Err(Error::InvalidArgumentError(
9095            "ROW type did not provide any field definitions".into(),
9096        ));
9097    }
9098
9099    let mut fields = Vec::new();
9100    let mut index = 0;
9101
9102    while index < slice.len() {
9103        if slice[index] == "," {
9104            index += 1;
9105            continue;
9106        }
9107
9108        let field_name = normalize_row_field_name(&slice[index])?;
9109        index += 1;
9110
9111        if index >= slice.len() {
9112            return Err(Error::InvalidArgumentError(format!(
9113                "ROW field '{field_name}' is missing a type specification"
9114            )));
9115        }
9116
9117        let mut last_success: Option<(usize, SqlDataType)> = None;
9118        let mut type_end = index;
9119
9120        while type_end <= slice.len() {
9121            let candidate = slice[index..type_end].join(" ");
9122            if candidate.trim().is_empty() {
9123                type_end += 1;
9124                continue;
9125            }
9126
9127            if let Ok(parsed_type) = parse_sql_data_type(&candidate, dialect) {
9128                last_success = Some((type_end, parsed_type));
9129            }
9130
9131            if type_end == slice.len() {
9132                break;
9133            }
9134
9135            if slice[type_end] == "," && last_success.is_some() {
9136                break;
9137            }
9138
9139            type_end += 1;
9140        }
9141
9142        let Some((next_index, data_type)) = last_success else {
9143            return Err(Error::InvalidArgumentError(format!(
9144                "failed to parse ROW field type for '{field_name}'"
9145            )));
9146        };
9147
9148        fields.push((field_name, data_type));
9149        index = next_index;
9150
9151        if index < slice.len() && slice[index] == "," {
9152            index += 1;
9153        }
9154    }
9155
9156    if fields.is_empty() {
9157        return Err(Error::InvalidArgumentError(
9158            "ROW type did not provide any field definitions".into(),
9159        ));
9160    }
9161
9162    Ok(fields)
9163}
9164
9165/// Parse SQL string into statements with increased recursion limit.
9166///
9167/// This helper wraps sqlparser's `Parser` with a custom recursion limit to handle
9168/// deeply nested queries that exceed the default limit of 50.
9169///
9170/// # Arguments
9171///
9172/// * `dialect` - SQL dialect to use for parsing
9173/// * `sql` - SQL string to parse
9174///
9175/// # Returns
9176///
9177/// Parsed statements or parser error
9178fn parse_sql_with_recursion_limit(
9179    dialect: &GenericDialect,
9180    sql: &str,
9181) -> Result<Vec<Statement>, sqlparser::parser::ParserError> {
9182    Parser::new(dialect)
9183        .with_recursion_limit(PARSER_RECURSION_LIMIT)
9184        .try_with_sql(sql)?
9185        .parse_statements()
9186}
9187
9188fn normalize_row_field_name(raw: &str) -> SqlResult<String> {
9189    let trimmed = raw.trim();
9190    if trimmed.is_empty() {
9191        return Err(Error::InvalidArgumentError(
9192            "ROW field name must not be empty".into(),
9193        ));
9194    }
9195
9196    if let Some(stripped) = trimmed.strip_prefix('"') {
9197        let without_end = stripped.strip_suffix('"').ok_or_else(|| {
9198            Error::InvalidArgumentError(format!("unterminated quoted ROW field name: {trimmed}"))
9199        })?;
9200        let name = without_end.replace("\"\"", "\"");
9201        return Ok(name);
9202    }
9203
9204    Ok(trimmed.to_string())
9205}
9206
9207fn parse_sql_data_type(type_str: &str, dialect: &GenericDialect) -> SqlResult<SqlDataType> {
9208    let trimmed = type_str.trim();
9209    let sql = format!("CREATE TABLE __row(__field {trimmed});");
9210    let statements = parse_sql_with_recursion_limit(dialect, &sql).map_err(|err| {
9211        Error::InvalidArgumentError(format!("failed to parse ROW field type '{trimmed}': {err}"))
9212    })?;
9213
9214    let stmt = statements.into_iter().next().ok_or_else(|| {
9215        Error::InvalidArgumentError(format!(
9216            "ROW field type '{trimmed}' did not produce a statement"
9217        ))
9218    })?;
9219
9220    match stmt {
9221        Statement::CreateTable(table) => table
9222            .columns
9223            .first()
9224            .map(|col| col.data_type.clone())
9225            .ok_or_else(|| {
9226                Error::InvalidArgumentError(format!(
9227                    "ROW field type '{trimmed}' missing column definition"
9228                ))
9229            }),
9230        other => Err(Error::InvalidArgumentError(format!(
9231            "unexpected statement while parsing ROW field type: {other:?}"
9232        ))),
9233    }
9234}
9235
9236/// Extract VALUES data from a derived table in FROM clause.
9237/// Returns (rows, column_names) if the pattern matches: SELECT ... FROM (VALUES ...) alias(col1, col2, ...)
9238type ExtractValuesResult = Option<(Vec<Vec<PlanValue>>, Vec<String>)>;
9239
9240#[allow(clippy::type_complexity)]
9241fn extract_values_from_derived_table(from: &[TableWithJoins]) -> SqlResult<ExtractValuesResult> {
9242    if from.len() != 1 {
9243        return Ok(None);
9244    }
9245
9246    let table_with_joins = &from[0];
9247    if !table_with_joins.joins.is_empty() {
9248        return Ok(None);
9249    }
9250
9251    match &table_with_joins.relation {
9252        TableFactor::Derived {
9253            subquery, alias, ..
9254        } => {
9255            // Check if the subquery is a VALUES expression
9256            let values = match subquery.body.as_ref() {
9257                SetExpr::Values(v) => v,
9258                _ => return Ok(None),
9259            };
9260
9261            // Extract column names from alias
9262            let column_names = if let Some(alias) = alias {
9263                alias
9264                    .columns
9265                    .iter()
9266                    .map(|col_def| col_def.name.value.clone())
9267                    .collect::<Vec<_>>()
9268            } else {
9269                // Generate default column names if no alias provided
9270                if values.rows.is_empty() {
9271                    return Err(Error::InvalidArgumentError(
9272                        "VALUES expression must have at least one row".into(),
9273                    ));
9274                }
9275                let first_row = &values.rows[0];
9276                (0..first_row.len())
9277                    .map(|i| format!("column{}", i))
9278                    .collect()
9279            };
9280
9281            // Extract rows
9282            if values.rows.is_empty() {
9283                return Err(Error::InvalidArgumentError(
9284                    "VALUES expression must have at least one row".into(),
9285                ));
9286            }
9287
9288            let mut rows = Vec::with_capacity(values.rows.len());
9289            for row in &values.rows {
9290                if row.len() != column_names.len() {
9291                    return Err(Error::InvalidArgumentError(format!(
9292                        "VALUES row has {} columns but table alias specifies {} columns",
9293                        row.len(),
9294                        column_names.len()
9295                    )));
9296                }
9297
9298                let mut converted_row = Vec::with_capacity(row.len());
9299                for expr in row {
9300                    let value = SqlValue::try_from_expr(expr)?;
9301                    converted_row.push(PlanValue::from(value));
9302                }
9303                rows.push(converted_row);
9304            }
9305
9306            Ok(Some((rows, column_names)))
9307        }
9308        _ => Ok(None),
9309    }
9310}
9311
9312fn extract_constant_select_rows(select: &Select) -> SqlResult<Option<Vec<Vec<PlanValue>>>> {
9313    if !select.from.is_empty() {
9314        return Ok(None);
9315    }
9316
9317    if select.selection.is_some()
9318        || select.having.is_some()
9319        || !select.named_window.is_empty()
9320        || select.qualify.is_some()
9321        || select.distinct.is_some()
9322        || select.top.is_some()
9323        || select.into.is_some()
9324        || select.prewhere.is_some()
9325        || !select.lateral_views.is_empty()
9326        || select.value_table_mode.is_some()
9327        || !group_by_is_empty(&select.group_by)
9328    {
9329        return Err(Error::InvalidArgumentError(
9330            "constant SELECT statements do not support advanced clauses".into(),
9331        ));
9332    }
9333
9334    if select.projection.is_empty() {
9335        return Err(Error::InvalidArgumentError(
9336            "constant SELECT requires at least one projection".into(),
9337        ));
9338    }
9339
9340    let mut row: Vec<PlanValue> = Vec::with_capacity(select.projection.len());
9341    for item in &select.projection {
9342        let expr = match item {
9343            SelectItem::UnnamedExpr(expr) => expr,
9344            SelectItem::ExprWithAlias { expr, .. } => expr,
9345            other => {
9346                return Err(Error::InvalidArgumentError(format!(
9347                    "unsupported projection in constant SELECT: {other:?}"
9348                )));
9349            }
9350        };
9351
9352        let value = SqlValue::try_from_expr(expr)?;
9353        row.push(PlanValue::from(value));
9354    }
9355
9356    Ok(Some(vec![row]))
9357}
9358
9359fn extract_single_table(from: &[TableWithJoins]) -> SqlResult<(String, String)> {
9360    if from.len() != 1 {
9361        return Err(Error::InvalidArgumentError(
9362            "queries over multiple tables are not supported yet".into(),
9363        ));
9364    }
9365    let item = &from[0];
9366
9367    if table_with_joins_has_join(item) {
9368        return Err(Error::InvalidArgumentError(
9369            "JOIN clauses are not supported yet".into(),
9370        ));
9371    }
9372    match &item.relation {
9373        TableFactor::Table { name, .. } => canonical_object_name(name),
9374        TableFactor::Derived { alias, .. } => {
9375            // Derived table (subquery) - use the alias as the table name if provided
9376            // For CTAS, this allows: CREATE TABLE t AS SELECT * FROM (VALUES ...) v(id)
9377            let table_name = alias
9378                .as_ref()
9379                .map(|a| a.name.value.clone())
9380                .unwrap_or_else(|| "derived".to_string());
9381            let canonical = table_name.to_ascii_lowercase();
9382            Ok((table_name, canonical))
9383        }
9384        TableFactor::NestedJoin { .. } => Err(Error::InvalidArgumentError(
9385            "JOIN clauses are not supported yet".into(),
9386        )),
9387        _ => Err(Error::InvalidArgumentError(
9388            "queries require a plain table name or derived table".into(),
9389        )),
9390    }
9391}
9392
9393// TODO: Rename for clarity?  i.e. "...nested_joins" or something?
9394fn table_with_joins_has_join(item: &TableWithJoins) -> bool {
9395    if !item.joins.is_empty() {
9396        return true;
9397    }
9398    match &item.relation {
9399        TableFactor::NestedJoin {
9400            table_with_joins, ..
9401        } => table_with_joins_has_join(table_with_joins.as_ref()),
9402        _ => false,
9403    }
9404}
9405
9406/// Extract table references from a FROM clause, flattening supported JOINs and
9407/// collecting any join predicates that must be applied as filters.
9408///
9409type ExtractedJoinData = (
9410    Vec<llkv_plan::TableRef>,
9411    Vec<llkv_plan::JoinMetadata>,
9412    Vec<Option<SqlExpr>>,
9413);
9414
9415/// Returns [`ExtractedJoinData`] (tables, join metadata, join filters).
9416/// - `tables`: list of all table references in order
9417/// - `join_metadata`: [`llkv_plan::JoinMetadata`] entries pairing consecutive tables
9418/// - `join_filters`: ON conditions to be merged into WHERE clause
9419fn extract_tables(from: &[TableWithJoins]) -> SqlResult<ExtractedJoinData> {
9420    let mut tables = Vec::new();
9421    let mut join_metadata = Vec::new();
9422    let mut join_filters = Vec::new();
9423
9424    for item in from {
9425        flatten_table_with_joins(item, &mut tables, &mut join_metadata, &mut join_filters)?;
9426    }
9427
9428    Ok((tables, join_metadata, join_filters))
9429}
9430
9431fn push_table_factor(
9432    factor: &TableFactor,
9433    tables: &mut Vec<llkv_plan::TableRef>,
9434    join_metadata: &mut Vec<llkv_plan::JoinMetadata>,
9435    join_filters: &mut Vec<Option<SqlExpr>>,
9436) -> SqlResult<()> {
9437    match factor {
9438        TableFactor::Table { name, alias, .. } => {
9439            // Note: Index hints (INDEXED BY, NOT INDEXED) are SQLite-specific query hints
9440            // that are ignored by the `..` pattern. We accept them for compatibility.
9441            let (schema_opt, table) = parse_schema_qualified_name(name)?;
9442            let schema = schema_opt.unwrap_or_default();
9443            let alias_name = alias.as_ref().map(|a| a.name.value.clone());
9444            tables.push(llkv_plan::TableRef::with_alias(schema, table, alias_name));
9445            Ok(())
9446        }
9447        TableFactor::NestedJoin {
9448            table_with_joins,
9449            alias,
9450        } => {
9451            if alias.is_some() {
9452                return Err(Error::InvalidArgumentError(
9453                    "parenthesized JOINs with aliases are not supported yet".into(),
9454                ));
9455            }
9456            flatten_table_with_joins(
9457                table_with_joins.as_ref(),
9458                tables,
9459                join_metadata,
9460                join_filters,
9461            )
9462        }
9463        TableFactor::Derived { .. } => Err(Error::InvalidArgumentError(
9464            "JOIN clauses require base tables; derived tables are not supported".into(),
9465        )),
9466        _ => Err(Error::InvalidArgumentError(
9467            "queries require a plain table name".into(),
9468        )),
9469    }
9470}
9471
9472fn flatten_table_with_joins(
9473    item: &TableWithJoins,
9474    tables: &mut Vec<llkv_plan::TableRef>,
9475    join_metadata: &mut Vec<llkv_plan::JoinMetadata>,
9476    join_filters: &mut Vec<Option<SqlExpr>>,
9477) -> SqlResult<()> {
9478    push_table_factor(&item.relation, tables, join_metadata, join_filters)?;
9479
9480    for join in &item.joins {
9481        let left_table_index = tables.len() - 1;
9482
9483        match &join.join_operator {
9484            JoinOperator::CrossJoin(JoinConstraint::None)
9485            | JoinOperator::Join(JoinConstraint::None)
9486            | JoinOperator::Inner(JoinConstraint::None) => {
9487                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
9488                join_metadata.push(llkv_plan::JoinMetadata {
9489                    left_table_index,
9490                    join_type: llkv_plan::JoinPlan::Inner,
9491                    on_condition: None,
9492                });
9493                join_filters.push(None);
9494            }
9495            JoinOperator::Join(JoinConstraint::On(condition))
9496            | JoinOperator::Inner(JoinConstraint::On(condition)) => {
9497                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
9498                join_filters.push(Some(condition.clone()));
9499                join_metadata.push(llkv_plan::JoinMetadata {
9500                    left_table_index,
9501                    join_type: llkv_plan::JoinPlan::Inner,
9502                    on_condition: None,
9503                });
9504            }
9505            JoinOperator::Left(JoinConstraint::On(condition))
9506            | JoinOperator::LeftOuter(JoinConstraint::On(condition)) => {
9507                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
9508                join_filters.push(Some(condition.clone()));
9509                join_metadata.push(llkv_plan::JoinMetadata {
9510                    left_table_index,
9511                    join_type: llkv_plan::JoinPlan::Left,
9512                    on_condition: None,
9513                });
9514            }
9515            JoinOperator::Left(JoinConstraint::None)
9516            | JoinOperator::LeftOuter(JoinConstraint::None) => {
9517                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
9518                join_metadata.push(llkv_plan::JoinMetadata {
9519                    left_table_index,
9520                    join_type: llkv_plan::JoinPlan::Left,
9521                    on_condition: None,
9522                });
9523                join_filters.push(None);
9524            }
9525            JoinOperator::CrossJoin(_) => {
9526                return Err(Error::InvalidArgumentError(
9527                    "CROSS JOIN with constraints is not supported".into(),
9528                ));
9529            }
9530            JoinOperator::Join(JoinConstraint::Using(_))
9531            | JoinOperator::Inner(JoinConstraint::Using(_))
9532            | JoinOperator::Left(JoinConstraint::Using(_))
9533            | JoinOperator::LeftOuter(JoinConstraint::Using(_)) => {
9534                return Err(Error::InvalidArgumentError(
9535                    "JOIN ... USING (...) is not supported yet".into(),
9536                ));
9537            }
9538            JoinOperator::Join(JoinConstraint::Natural)
9539            | JoinOperator::Inner(JoinConstraint::Natural)
9540            | JoinOperator::Left(JoinConstraint::Natural)
9541            | JoinOperator::LeftOuter(JoinConstraint::Natural)
9542            | JoinOperator::Right(_)
9543            | JoinOperator::RightOuter(_)
9544            | JoinOperator::FullOuter(_)
9545            | JoinOperator::Semi(_)
9546            | JoinOperator::LeftSemi(_)
9547            | JoinOperator::LeftAnti(_)
9548            | JoinOperator::RightSemi(_)
9549            | JoinOperator::RightAnti(_)
9550            | JoinOperator::CrossApply
9551            | JoinOperator::OuterApply
9552            | JoinOperator::Anti(_)
9553            | JoinOperator::StraightJoin(_) => {
9554                return Err(Error::InvalidArgumentError(
9555                    "only INNER JOIN and LEFT JOIN with optional ON constraints are supported"
9556                        .into(),
9557                ));
9558            }
9559            other => {
9560                return Err(Error::InvalidArgumentError(format!(
9561                    "unsupported JOIN clause: {other:?}"
9562                )));
9563            }
9564        }
9565    }
9566
9567    Ok(())
9568}
9569
9570fn group_by_is_empty(expr: &GroupByExpr) -> bool {
9571    matches!(
9572        expr,
9573        GroupByExpr::Expressions(exprs, modifiers)
9574            if exprs.is_empty() && modifiers.is_empty()
9575    )
9576}
9577
9578fn convert_value_table_mode(mode: sqlparser::ast::ValueTableMode) -> llkv_plan::ValueTableMode {
9579    use llkv_plan::ValueTableMode as PlanMode;
9580    match mode {
9581        sqlparser::ast::ValueTableMode::AsStruct => PlanMode::AsStruct,
9582        sqlparser::ast::ValueTableMode::AsValue => PlanMode::AsValue,
9583        sqlparser::ast::ValueTableMode::DistinctAsStruct => PlanMode::DistinctAsStruct,
9584        sqlparser::ast::ValueTableMode::DistinctAsValue => PlanMode::DistinctAsValue,
9585    }
9586}
9587#[cfg(test)]
9588mod tests {
9589    use super::*;
9590    use arrow::array::{Array, Float64Array, Int32Array, Int64Array, StringArray};
9591    use arrow::record_batch::RecordBatch;
9592    use llkv_storage::pager::MemPager;
9593
9594    fn extract_string_options(batches: &[RecordBatch]) -> Vec<Option<String>> {
9595        let mut values: Vec<Option<String>> = Vec::new();
9596        for batch in batches {
9597            let column = batch
9598                .column(0)
9599                .as_any()
9600                .downcast_ref::<StringArray>()
9601                .expect("string column");
9602            for idx in 0..column.len() {
9603                if column.is_null(idx) {
9604                    values.push(None);
9605                } else {
9606                    values.push(Some(column.value(idx).to_string()));
9607                }
9608            }
9609        }
9610        values
9611    }
9612
9613    #[test]
9614    fn test_insert_batching_across_calls() {
9615        let engine = SqlEngine::new(Arc::new(MemPager::default()));
9616
9617        // Create table
9618        engine.execute("CREATE TABLE test (id INTEGER)").unwrap();
9619
9620        // Insert two rows in SEPARATE execute() calls (simulating SLT)
9621        engine.execute("INSERT INTO test VALUES (1)").unwrap();
9622        engine.execute("INSERT INTO test VALUES (2)").unwrap();
9623
9624        // SELECT will flush the buffer - result will have [INSERT, SELECT]
9625        let result = engine.execute("SELECT * FROM test ORDER BY id").unwrap();
9626        let select_result = result
9627            .into_iter()
9628            .find_map(|res| match res {
9629                RuntimeStatementResult::Select { execution, .. } => {
9630                    Some(execution.collect().unwrap())
9631                }
9632                _ => None,
9633            })
9634            .expect("expected SELECT result in response");
9635        let batches = select_result;
9636        assert_eq!(
9637            batches[0].num_rows(),
9638            2,
9639            "Should have 2 rows after cross-call batching"
9640        );
9641    }
9642
9643    #[test]
9644    fn create_insert_select_roundtrip() {
9645        let pager = Arc::new(MemPager::default());
9646        let engine = SqlEngine::new(pager);
9647
9648        let result = engine
9649            .execute("CREATE TABLE people (id INT NOT NULL, name TEXT NOT NULL)")
9650            .expect("create table");
9651        assert!(matches!(
9652            result[0],
9653            RuntimeStatementResult::CreateTable { .. }
9654        ));
9655
9656        let result = engine
9657            .execute("INSERT INTO people (id, name) VALUES (1, 'alice'), (2, 'bob')")
9658            .expect("insert rows");
9659        assert!(matches!(
9660            result[0],
9661            RuntimeStatementResult::Insert {
9662                rows_inserted: 2,
9663                ..
9664            }
9665        ));
9666
9667        let mut result = engine
9668            .execute("SELECT name FROM people WHERE id = 2")
9669            .expect("select rows");
9670        let select_result = result.remove(0);
9671        let batches = match select_result {
9672            RuntimeStatementResult::Select { execution, .. } => {
9673                execution.collect().expect("collect batches")
9674            }
9675            _ => panic!("expected select result"),
9676        };
9677        assert_eq!(batches.len(), 1);
9678        let column = batches[0]
9679            .column(0)
9680            .as_any()
9681            .downcast_ref::<StringArray>()
9682            .expect("string column");
9683        assert_eq!(column.len(), 1);
9684        assert_eq!(column.value(0), "bob");
9685    }
9686
9687    #[test]
9688    fn insert_select_constant_including_null() {
9689        let pager = Arc::new(MemPager::default());
9690        let engine = SqlEngine::new(pager);
9691
9692        engine
9693            .execute("CREATE TABLE integers(i INTEGER)")
9694            .expect("create table");
9695
9696        let result = engine
9697            .execute("INSERT INTO integers SELECT 42")
9698            .expect("insert literal");
9699        assert!(matches!(
9700            result[0],
9701            RuntimeStatementResult::Insert {
9702                rows_inserted: 1,
9703                ..
9704            }
9705        ));
9706
9707        let result = engine
9708            .execute("INSERT INTO integers SELECT CAST(NULL AS VARCHAR)")
9709            .expect("insert null literal");
9710        assert!(matches!(
9711            result[0],
9712            RuntimeStatementResult::Insert {
9713                rows_inserted: 1,
9714                ..
9715            }
9716        ));
9717
9718        let mut result = engine
9719            .execute("SELECT * FROM integers")
9720            .expect("select rows");
9721        let select_result = result.remove(0);
9722        let batches = match select_result {
9723            RuntimeStatementResult::Select { execution, .. } => {
9724                execution.collect().expect("collect batches")
9725            }
9726            _ => panic!("expected select result"),
9727        };
9728
9729        let mut values: Vec<Option<i64>> = Vec::new();
9730        for batch in &batches {
9731            let column = batch
9732                .column(0)
9733                .as_any()
9734                .downcast_ref::<Int64Array>()
9735                .expect("int column");
9736            for idx in 0..column.len() {
9737                if column.is_null(idx) {
9738                    values.push(None);
9739                } else {
9740                    values.push(Some(column.value(idx)));
9741                }
9742            }
9743        }
9744
9745        assert_eq!(values, vec![Some(42), None]);
9746    }
9747
9748    #[test]
9749    fn not_null_comparison_filters_all_rows() {
9750        let pager = Arc::new(MemPager::default());
9751        let engine = SqlEngine::new(pager);
9752
9753        engine
9754            .execute("CREATE TABLE single(col INTEGER)")
9755            .expect("create table");
9756        engine
9757            .execute("INSERT INTO single VALUES (1)")
9758            .expect("insert row");
9759
9760        let batches = engine
9761            .sql("SELECT * FROM single WHERE NOT ( NULL ) >= NULL")
9762            .expect("run constant null comparison");
9763
9764        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9765        assert_eq!(total_rows, 0, "expected filter to remove all rows");
9766    }
9767
9768    #[test]
9769    fn not_null_in_list_filters_all_rows() {
9770        let pager = Arc::new(MemPager::default());
9771        let engine = SqlEngine::new(pager);
9772
9773        engine
9774            .execute("CREATE TABLE tab0(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
9775            .expect("create table");
9776        engine
9777            .execute("INSERT INTO tab0 VALUES (1, 2, 3)")
9778            .expect("insert row");
9779
9780        let batches = engine
9781            .sql("SELECT * FROM tab0 WHERE NOT ( NULL ) IN ( - col2 * + col2 )")
9782            .expect("run IN list null comparison");
9783
9784        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9785        assert_eq!(total_rows, 0, "expected IN list filter to remove all rows");
9786    }
9787
9788    #[test]
9789    fn empty_in_list_filters_all_rows() {
9790        let pager = Arc::new(MemPager::default());
9791        let engine = SqlEngine::new(pager);
9792
9793        engine
9794            .execute("CREATE TABLE test_table(col INTEGER)")
9795            .expect("create table");
9796        engine
9797            .execute("INSERT INTO test_table VALUES (1), (2), (3)")
9798            .expect("insert rows");
9799
9800        let batches = engine
9801            .sql("SELECT * FROM test_table WHERE col IN ()")
9802            .expect("run empty IN list");
9803
9804        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9805        assert_eq!(total_rows, 0, "expected empty IN list to filter all rows");
9806    }
9807
9808    #[test]
9809    fn empty_not_in_list_preserves_all_rows() {
9810        let pager = Arc::new(MemPager::default());
9811        let engine = SqlEngine::new(pager);
9812
9813        engine
9814            .execute("CREATE TABLE test_table(col INTEGER)")
9815            .expect("create table");
9816        engine
9817            .execute("INSERT INTO test_table VALUES (1), (2), (3)")
9818            .expect("insert rows");
9819
9820        let batches = engine
9821            .sql("SELECT * FROM test_table WHERE col NOT IN () ORDER BY col")
9822            .expect("run empty NOT IN list");
9823
9824        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9825        assert_eq!(
9826            total_rows, 3,
9827            "expected empty NOT IN list to preserve all rows"
9828        );
9829
9830        let mut values: Vec<i64> = Vec::new();
9831        for batch in &batches {
9832            let column = batch
9833                .column(0)
9834                .as_any()
9835                .downcast_ref::<Int64Array>()
9836                .expect("int column");
9837            for idx in 0..column.len() {
9838                if !column.is_null(idx) {
9839                    values.push(column.value(idx));
9840                }
9841            }
9842        }
9843
9844        assert_eq!(values, vec![1, 2, 3]);
9845    }
9846
9847    #[test]
9848    fn empty_in_list_with_constant_expression() {
9849        let pager = Arc::new(MemPager::default());
9850        let engine = SqlEngine::new(pager);
9851
9852        let batches = engine
9853            .sql("SELECT 1 IN ()")
9854            .expect("run constant empty IN list");
9855
9856        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9857        assert_eq!(total_rows, 1, "expected one result row");
9858
9859        let value = batches[0]
9860            .column(0)
9861            .as_any()
9862            .downcast_ref::<Int64Array>()
9863            .expect("int column")
9864            .value(0);
9865
9866        assert_eq!(value, 0, "expected 1 IN () to evaluate to 0 (false)");
9867    }
9868
9869    #[test]
9870    fn empty_not_in_list_with_constant_expression() {
9871        let pager = Arc::new(MemPager::default());
9872        let engine = SqlEngine::new(pager);
9873
9874        let batches = engine
9875            .sql("SELECT 1 NOT IN ()")
9876            .expect("run constant empty NOT IN list");
9877
9878        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9879        assert_eq!(total_rows, 1, "expected one result row");
9880
9881        let value = batches[0]
9882            .column(0)
9883            .as_any()
9884            .downcast_ref::<Int64Array>()
9885            .expect("int column")
9886            .value(0);
9887
9888        assert_eq!(value, 1, "expected 1 NOT IN () to evaluate to 1 (true)");
9889    }
9890
9891    #[test]
9892    fn not_in_with_cast_preserves_rows_for_self_comparison() {
9893        let pager = Arc::new(MemPager::default());
9894        let engine = SqlEngine::new(pager);
9895
9896        engine
9897            .execute("CREATE TABLE tab2(col1 INTEGER, col2 INTEGER)")
9898            .expect("create tab2");
9899        engine
9900            .execute("INSERT INTO tab2 VALUES (51, 51), (67, 67), (77, 77)")
9901            .expect("seed tab2");
9902
9903        let batches = engine
9904            .sql(
9905                "SELECT col1 FROM tab2 WHERE NOT col2 NOT IN ( + CAST ( + + col2 AS REAL ) ) ORDER BY col1",
9906            )
9907            .expect("run NOT IN self comparison query");
9908
9909        let mut values: Vec<i64> = Vec::new();
9910        for batch in &batches {
9911            let column = batch
9912                .column(0)
9913                .as_any()
9914                .downcast_ref::<Int64Array>()
9915                .expect("int column");
9916            for idx in 0..column.len() {
9917                if !column.is_null(idx) {
9918                    values.push(column.value(idx));
9919                }
9920            }
9921        }
9922
9923        assert_eq!(values, vec![51, 67, 77]);
9924    }
9925
9926    #[test]
9927    fn cross_join_not_null_comparison_filters_all_rows() {
9928        let pager = Arc::new(MemPager::default());
9929        let engine = SqlEngine::new(pager);
9930
9931        engine
9932            .execute("CREATE TABLE left_side(col INTEGER)")
9933            .expect("create left table");
9934        engine
9935            .execute("CREATE TABLE right_side(col INTEGER)")
9936            .expect("create right table");
9937        engine
9938            .execute("INSERT INTO left_side VALUES (1)")
9939            .expect("insert left row");
9940        engine
9941            .execute("INSERT INTO right_side VALUES (2)")
9942            .expect("insert right row");
9943
9944        let batches = engine
9945            .sql("SELECT * FROM left_side CROSS JOIN right_side WHERE NOT ( NULL ) >= NULL")
9946            .expect("run cross join null comparison");
9947
9948        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
9949        assert_eq!(
9950            total_rows, 0,
9951            "expected cross join filter to remove all rows"
9952        );
9953    }
9954
9955    #[test]
9956    fn not_between_null_bounds_matches_sqlite_behavior() {
9957        let pager = Arc::new(MemPager::default());
9958        let engine = SqlEngine::new(pager);
9959
9960        engine
9961            .execute("CREATE TABLE tab2(col1 INTEGER, col2 INTEGER)")
9962            .expect("create tab2");
9963        engine
9964            .execute("INSERT INTO tab2 VALUES (1, 2), (-5, 7), (NULL, 11)")
9965            .expect("seed rows");
9966
9967        let batches = engine
9968            .sql(
9969                "SELECT DISTINCT - col2 AS col1 FROM tab2 WHERE NOT ( col1 ) BETWEEN ( NULL ) AND ( + col1 - col2 )",
9970            )
9971            .expect("run NOT BETWEEN query with NULL bounds");
9972
9973        let mut values: Vec<i64> = Vec::new();
9974        for batch in &batches {
9975            let column = batch.column(0);
9976            match column.data_type() {
9977                arrow::datatypes::DataType::Int64 => {
9978                    let array = column
9979                        .as_any()
9980                        .downcast_ref::<Int64Array>()
9981                        .expect("int64 column");
9982                    for idx in 0..array.len() {
9983                        if !array.is_null(idx) {
9984                            values.push(array.value(idx));
9985                        }
9986                    }
9987                }
9988                arrow::datatypes::DataType::Int32 => {
9989                    let array = column
9990                        .as_any()
9991                        .downcast_ref::<Int32Array>()
9992                        .expect("int32 column");
9993                    for idx in 0..array.len() {
9994                        if !array.is_null(idx) {
9995                            values.push(array.value(idx) as i64);
9996                        }
9997                    }
9998                }
9999                other => panic!("unexpected data type: {other:?}"),
10000            }
10001        }
10002
10003        values.sort_unstable();
10004        assert_eq!(values, vec![-7, -2]);
10005    }
10006
10007    #[test]
10008    fn not_chain_precedence_matches_sqlite_behavior() {
10009        let pager = Arc::new(MemPager::default());
10010        let engine = SqlEngine::new(pager);
10011
10012        engine
10013            .execute("CREATE TABLE tab1(col0 INTEGER)")
10014            .expect("create tab1");
10015        engine
10016            .execute("INSERT INTO tab1 VALUES (1), (2)")
10017            .expect("seed tab1");
10018
10019        use sqlparser::ast::Statement;
10020        use sqlparser::dialect::SQLiteDialect;
10021        use sqlparser::parser::Parser;
10022
10023        let dialect = SQLiteDialect {};
10024        let mut statements = Parser::parse_sql(
10025            &dialect,
10026            "SELECT DISTINCT 85 AS value FROM tab1 WHERE NOT + 84 < - + 69 GROUP BY col0, col0",
10027        )
10028        .expect("parse sql");
10029        let statement = statements.pop().expect("expected single statement");
10030        let Statement::Query(query_ast) = statement else {
10031            panic!("expected SELECT query");
10032        };
10033        let plan = engine
10034            .build_select_plan(*query_ast)
10035            .expect("build select plan");
10036        let filter_expr = plan.filter.expect("expected filter predicate").predicate;
10037        if let llkv_expr::expr::Expr::Not(inner) = &filter_expr {
10038            if !matches!(inner.as_ref(), llkv_expr::expr::Expr::Compare { .. }) {
10039                panic!("expected NOT to wrap comparison, got: {inner:?}");
10040            }
10041        } else {
10042            panic!("expected filter to be NOT-wrapped comparison: {filter_expr:?}");
10043        }
10044
10045        let batches = engine
10046            .sql(
10047                "SELECT DISTINCT 85 AS value FROM tab1 WHERE NOT + 84 < - + 69 GROUP BY col0, col0",
10048            )
10049            .expect("run NOT precedence query");
10050
10051        let mut values: Vec<i64> = Vec::new();
10052        for batch in &batches {
10053            let column = batch.column(0);
10054            match column.data_type() {
10055                arrow::datatypes::DataType::Int64 => {
10056                    let array = column
10057                        .as_any()
10058                        .downcast_ref::<Int64Array>()
10059                        .expect("int64 column");
10060                    for idx in 0..array.len() {
10061                        if !array.is_null(idx) {
10062                            values.push(array.value(idx));
10063                        }
10064                    }
10065                }
10066                arrow::datatypes::DataType::Int32 => {
10067                    let array = column
10068                        .as_any()
10069                        .downcast_ref::<Int32Array>()
10070                        .expect("int32 column");
10071                    for idx in 0..array.len() {
10072                        if !array.is_null(idx) {
10073                            values.push(array.value(idx) as i64);
10074                        }
10075                    }
10076                }
10077                arrow::datatypes::DataType::Float64 => {
10078                    let array = column
10079                        .as_any()
10080                        .downcast_ref::<Float64Array>()
10081                        .expect("float64 column");
10082                    for idx in 0..array.len() {
10083                        if !array.is_null(idx) {
10084                            values.push(array.value(idx) as i64);
10085                        }
10086                    }
10087                }
10088                other => panic!("unexpected data type: {other:?}"),
10089            }
10090        }
10091
10092        values.sort_unstable();
10093        assert_eq!(values, vec![85]);
10094    }
10095
10096    #[test]
10097    fn not_between_null_bounds_matches_harness_fixture() {
10098        let pager = Arc::new(MemPager::default());
10099        let engine = SqlEngine::new(pager);
10100
10101        engine
10102            .execute("CREATE TABLE tab2(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
10103            .expect("create tab2");
10104        engine
10105            .execute("INSERT INTO tab2 VALUES (7, 31, 27), (79, 17, 38), (78, 59, 26)")
10106            .expect("seed rows");
10107
10108        let batches = engine
10109            .sql(
10110                "SELECT DISTINCT - col2 AS col1 FROM tab2 WHERE NOT ( col1 ) BETWEEN ( NULL ) AND ( + col1 - col2 )",
10111            )
10112            .expect("run harness-matched NOT BETWEEN query");
10113
10114        let mut values: Vec<i64> = Vec::new();
10115        for batch in &batches {
10116            let column = batch
10117                .column(0)
10118                .as_any()
10119                .downcast_ref::<Int64Array>()
10120                .expect("integer column");
10121            for idx in 0..column.len() {
10122                if !column.is_null(idx) {
10123                    values.push(column.value(idx));
10124                }
10125            }
10126        }
10127
10128        values.sort_unstable();
10129        assert_eq!(values, vec![-38, -27, -26]);
10130    }
10131
10132    #[test]
10133    fn not_between_null_bounds_parser_negated_flag() {
10134        use sqlparser::ast::{Expr as SqlExprAst, Statement};
10135        use sqlparser::dialect::SQLiteDialect;
10136        use sqlparser::parser::Parser;
10137
10138        let dialect = SQLiteDialect {};
10139        let sql = "SELECT DISTINCT - col2 AS col1 FROM tab2 WHERE NOT ( col1 ) BETWEEN ( NULL ) AND ( + col1 - col2 )";
10140
10141        let mut statements = Parser::parse_sql(&dialect, sql).expect("parse sql");
10142        let statement = statements.pop().expect("expected single statement");
10143        let Statement::Query(query) = statement else {
10144            panic!("expected SELECT query");
10145        };
10146        let select = query.body.as_select().expect("expected SELECT body");
10147        let where_expr = select.selection.as_ref().expect("expected WHERE clause");
10148
10149        match where_expr {
10150            SqlExprAst::UnaryOp {
10151                op: sqlparser::ast::UnaryOperator::Not,
10152                expr,
10153            } => match expr.as_ref() {
10154                SqlExprAst::Between { negated, .. } => {
10155                    assert!(
10156                        !negated,
10157                        "expected BETWEEN parser to treat leading NOT as part of expression"
10158                    );
10159                }
10160                other => panic!("unexpected inner expression: {other:?}"),
10161            },
10162            other => panic!("unexpected where expression: {other:?}"),
10163        }
10164    }
10165
10166    #[test]
10167    fn double_negated_between_null_bounds_filters_all_rows() {
10168        let pager = Arc::new(MemPager::default());
10169        let engine = SqlEngine::new(pager);
10170
10171        engine
10172            .execute("CREATE TABLE tab2(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
10173            .expect("create tab2");
10174        engine
10175            .execute("INSERT INTO tab2 VALUES (1, 2, 3), (-2, -13, 19), (NULL, 5, 7)")
10176            .expect("seed rows");
10177
10178        let batches = engine
10179            .sql(
10180                "SELECT - col1 * + col2 FROM tab2 WHERE NOT ( col1 ) NOT BETWEEN ( NULL ) AND ( col0 )",
10181            )
10182            .expect("run double NOT BETWEEN query with NULL bounds");
10183
10184        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
10185        assert_eq!(
10186            total_rows, 0,
10187            "expected double NOT BETWEEN to filter all rows"
10188        );
10189    }
10190
10191    #[test]
10192    fn not_scalar_less_than_null_filters_all_rows() {
10193        let pager = Arc::new(MemPager::default());
10194        let engine = SqlEngine::new(pager);
10195
10196        engine
10197            .execute("CREATE TABLE tab(col0 INTEGER, col2 INTEGER)")
10198            .expect("create tab");
10199        engine
10200            .execute("INSERT INTO tab VALUES (1, 2), (5, 10), (-3, 7)")
10201            .expect("seed rows");
10202
10203        let batches = engine
10204            .sql("SELECT col0 FROM tab WHERE NOT ( - col0 / - col2 + - col0 ) < NULL")
10205            .expect("run NOT < NULL query");
10206
10207        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
10208        assert_eq!(total_rows, 0, "expected NOT < NULL to filter all rows");
10209    }
10210
10211    #[test]
10212    fn left_join_not_is_not_null_on_literal_flips_to_is_null() {
10213        use sqlparser::ast::Statement;
10214        use sqlparser::dialect::SQLiteDialect;
10215        use sqlparser::parser::Parser;
10216
10217        let pager = Arc::new(MemPager::default());
10218        let engine = SqlEngine::new(pager);
10219
10220        engine
10221            .execute(
10222                "CREATE TABLE tab0(col0 INTEGER, col1 INTEGER, col2 INTEGER);\
10223                 CREATE TABLE tab1(col0 INTEGER, col1 INTEGER, col2 INTEGER);",
10224            )
10225            .expect("create tables");
10226
10227        let sql = "SELECT DISTINCT * FROM tab1 AS cor0 LEFT JOIN tab1 AS cor1 ON NOT 86 IS NOT NULL, tab0 AS cor2";
10228        let dialect = SQLiteDialect {};
10229        let mut statements = Parser::parse_sql(&dialect, sql).expect("parse sql");
10230        let statement = statements.pop().expect("expected statement");
10231        let Statement::Query(query) = statement else {
10232            panic!("expected SELECT query");
10233        };
10234
10235        let plan = engine.build_select_plan(*query).expect("build select plan");
10236
10237        assert_eq!(plan.joins.len(), 1, "expected single explicit join entry");
10238
10239        let left_join = &plan.joins[0];
10240        let on_condition = left_join
10241            .on_condition
10242            .as_ref()
10243            .expect("left join should preserve ON predicate");
10244
10245        match on_condition {
10246            llkv_expr::expr::Expr::IsNull { expr, negated } => {
10247                assert!(!negated, "expected NOT to flip into IS NULL");
10248                assert!(matches!(
10249                    expr,
10250                    llkv_expr::expr::ScalarExpr::Literal(llkv_expr::literal::Literal::Integer(86))
10251                ));
10252            }
10253            other => panic!("unexpected ON predicate: {other:?}"),
10254        }
10255    }
10256
10257    #[test]
10258    fn left_join_constant_false_preserves_left_rows_with_null_right() {
10259        let pager = Arc::new(MemPager::default());
10260        let engine = SqlEngine::new(pager);
10261
10262        engine
10263            .execute(
10264                "CREATE TABLE tab0(col0 INTEGER, col1 INTEGER, col2 INTEGER);\
10265                 CREATE TABLE tab1(col0 INTEGER, col1 INTEGER, col2 INTEGER);",
10266            )
10267            .expect("create tables");
10268
10269        engine
10270            .execute(
10271                "INSERT INTO tab0 VALUES (1, 2, 3), (4, 5, 6);\
10272                 INSERT INTO tab1 VALUES (10, 11, 12), (13, 14, 15);",
10273            )
10274            .expect("seed rows");
10275
10276        let batches = engine
10277            .sql(
10278                "SELECT * FROM tab1 AS cor0 LEFT JOIN tab1 AS cor1 ON NOT 86 IS NOT NULL, tab0 AS cor2 ORDER BY cor0.col0, cor2.col0",
10279            )
10280            .expect("execute join query");
10281
10282        let mut total_rows = 0;
10283        for batch in &batches {
10284            total_rows += batch.num_rows();
10285
10286            // Columns 0-2 belong to cor0, 3-5 to cor1, 6-8 to cor2.
10287            for row_idx in 0..batch.num_rows() {
10288                for col_idx in 3..6 {
10289                    assert!(
10290                        batch.column(col_idx).is_null(row_idx),
10291                        "expected right table column {} to be NULL in row {}",
10292                        col_idx,
10293                        row_idx
10294                    );
10295                }
10296            }
10297        }
10298
10299        // Two left rows cross two tab0 rows -> four total results.
10300        assert_eq!(total_rows, 4, "expected Cartesian product with tab0 only");
10301    }
10302
10303    #[test]
10304    fn cross_join_duplicate_table_name_resolves_columns() {
10305        let pager = Arc::new(MemPager::default());
10306        let engine = SqlEngine::new(pager);
10307
10308        use sqlparser::ast::{SetExpr, Statement};
10309        use sqlparser::dialect::SQLiteDialect;
10310        use sqlparser::parser::Parser;
10311
10312        engine
10313            .execute("CREATE TABLE tab1(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
10314            .expect("create tab1");
10315        engine
10316            .execute("INSERT INTO tab1 VALUES (7, 8, 9)")
10317            .expect("insert tab1 row");
10318
10319        let dialect = SQLiteDialect {};
10320        let ast = Parser::parse_sql(
10321            &dialect,
10322            "SELECT tab1.col2 FROM tab1 AS cor0 CROSS JOIN tab1",
10323        )
10324        .expect("parse cross join query");
10325        let Statement::Query(query) = &ast[0] else {
10326            panic!("expected SELECT query");
10327        };
10328        let select = match query.body.as_ref() {
10329            SetExpr::Select(select) => select.as_ref(),
10330            other => panic!("unexpected query body: {other:?}"),
10331        };
10332        assert_eq!(select.from.len(), 1);
10333        assert!(!select.from[0].joins.is_empty());
10334
10335        let batches = engine
10336            .sql("SELECT tab1.col2 FROM tab1 AS cor0 CROSS JOIN tab1")
10337            .expect("run cross join with alias and base table");
10338
10339        let mut values = Vec::new();
10340        for batch in &batches {
10341            let column = batch
10342                .column(0)
10343                .as_any()
10344                .downcast_ref::<Int64Array>()
10345                .expect("int64 column");
10346            for idx in 0..column.len() {
10347                if !column.is_null(idx) {
10348                    values.push(column.value(idx));
10349                }
10350            }
10351        }
10352        assert_eq!(values, vec![9]);
10353
10354        engine
10355            .execute("CREATE TABLE strings(a TEXT)")
10356            .expect("create table");
10357
10358        engine
10359            .execute("INSERT INTO strings VALUES ('3'), ('4'), (NULL)")
10360            .expect("insert seed rows");
10361
10362        let result = engine
10363            .execute("UPDATE strings SET a = 13 WHERE a = '3'")
10364            .expect("update rows");
10365        assert!(matches!(
10366            result[0],
10367            RuntimeStatementResult::Update {
10368                rows_updated: 1,
10369                ..
10370            }
10371        ));
10372
10373        let mut result = engine
10374            .execute("SELECT * FROM strings ORDER BY cast(a AS INTEGER)")
10375            .expect("select rows");
10376        let select_result = result.remove(0);
10377        let batches = match select_result {
10378            RuntimeStatementResult::Select { execution, .. } => {
10379                execution.collect().expect("collect batches")
10380            }
10381            _ => panic!("expected select result"),
10382        };
10383
10384        let mut values: Vec<Option<String>> = Vec::new();
10385        for batch in &batches {
10386            let column = batch
10387                .column(0)
10388                .as_any()
10389                .downcast_ref::<StringArray>()
10390                .expect("string column");
10391            for idx in 0..column.len() {
10392                if column.is_null(idx) {
10393                    values.push(None);
10394                } else {
10395                    values.push(Some(column.value(idx).to_string()));
10396                }
10397            }
10398        }
10399
10400        values.sort_by(|a, b| match (a, b) {
10401            (None, None) => std::cmp::Ordering::Equal,
10402            (None, Some(_)) => std::cmp::Ordering::Less,
10403            (Some(_), None) => std::cmp::Ordering::Greater,
10404            (Some(av), Some(bv)) => {
10405                let a_val = av.parse::<i64>().unwrap_or_default();
10406                let b_val = bv.parse::<i64>().unwrap_or_default();
10407                a_val.cmp(&b_val)
10408            }
10409        });
10410
10411        assert_eq!(
10412            values,
10413            vec![None, Some("4".to_string()), Some("13".to_string())]
10414        );
10415    }
10416
10417    #[test]
10418    fn order_by_honors_configured_default_null_order() {
10419        let pager = Arc::new(MemPager::default());
10420        let engine = SqlEngine::new(pager);
10421
10422        engine
10423            .execute("CREATE TABLE strings(a VARCHAR)")
10424            .expect("create table");
10425        engine
10426            .execute("INSERT INTO strings VALUES ('3'), ('4'), (NULL)")
10427            .expect("insert values");
10428        engine
10429            .execute("UPDATE strings SET a = 13 WHERE a = '3'")
10430            .expect("update value");
10431
10432        let mut result = engine
10433            .execute("SELECT * FROM strings ORDER BY cast(a AS INTEGER)")
10434            .expect("select rows");
10435        let select_result = result.remove(0);
10436        let batches = match select_result {
10437            RuntimeStatementResult::Select { execution, .. } => {
10438                execution.collect().expect("collect batches")
10439            }
10440            _ => panic!("expected select result"),
10441        };
10442
10443        let values = extract_string_options(&batches);
10444        assert_eq!(
10445            values,
10446            vec![Some("4".to_string()), Some("13".to_string()), None]
10447        );
10448
10449        assert!(!engine.default_nulls_first_for_tests());
10450
10451        engine
10452            .execute("SET default_null_order='nulls_first'")
10453            .expect("set default null order");
10454
10455        assert!(engine.default_nulls_first_for_tests());
10456
10457        let mut result = engine
10458            .execute("SELECT * FROM strings ORDER BY cast(a AS INTEGER)")
10459            .expect("select rows");
10460        let select_result = result.remove(0);
10461        let batches = match select_result {
10462            RuntimeStatementResult::Select { execution, .. } => {
10463                execution.collect().expect("collect batches")
10464            }
10465            _ => panic!("expected select result"),
10466        };
10467
10468        let values = extract_string_options(&batches);
10469        assert_eq!(
10470            values,
10471            vec![None, Some("4".to_string()), Some("13".to_string())]
10472        );
10473    }
10474
10475    #[test]
10476    fn arrow_type_from_row_returns_struct_fields() {
10477        let dialect = GenericDialect {};
10478        let statements = parse_sql_with_recursion_limit(
10479            &dialect,
10480            "CREATE TABLE row_types(payload ROW(a INTEGER, b VARCHAR));",
10481        )
10482        .expect("parse ROW type definition");
10483
10484        let data_type = match &statements[0] {
10485            Statement::CreateTable(stmt) => stmt.columns[0].data_type.clone(),
10486            other => panic!("unexpected statement: {other:?}"),
10487        };
10488
10489        let arrow_type = arrow_type_from_sql(&data_type).expect("convert ROW type");
10490        match arrow_type {
10491            arrow::datatypes::DataType::Struct(fields) => {
10492                assert_eq!(fields.len(), 2, "unexpected field count");
10493                assert_eq!(fields[0].name(), "a");
10494                assert_eq!(fields[1].name(), "b");
10495                assert_eq!(fields[0].data_type(), &arrow::datatypes::DataType::Int64);
10496                assert_eq!(fields[1].data_type(), &arrow::datatypes::DataType::Utf8);
10497            }
10498            other => panic!("expected struct type, got {other:?}"),
10499        }
10500    }
10501}