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