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, SqlValue, interval::parse_interval_literal};
12use arrow::array::{Array, ArrayRef, BooleanArray, Int32Array, StringArray, UInt32Array};
13use arrow::compute::{concat_batches, take};
14use arrow::datatypes::{DataType, Field, Schema};
15use arrow::record_batch::RecordBatch;
16use arrow::row::{RowConverter, SortField};
17
18use llkv_column_map::store::ROW_ID_COLUMN_NAME;
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, ColumnStoreWriteHints, CreateIndexPlan,
29    CreateTablePlan, CreateTableSource, CreateViewPlan, DeletePlan,
30    ForeignKeyAction as PlanForeignKeyAction, ForeignKeySpec, IndexColumnPlan,
31    InsertConflictAction, InsertPlan, InsertSource, MultiColumnUniqueSpec, OrderByPlan,
32    OrderSortType, OrderTarget, PlanColumnSpec, PlanStatement, PlanValue, ReindexPlan,
33    RenameTablePlan, RuntimeContext, RuntimeEngine, RuntimeSession, RuntimeStatementResult,
34    SelectPlan, SelectProjection, TruncatePlan, UpdatePlan, extract_rows_from_range,
35};
36use llkv_storage::pager::{BoxedPager, Pager};
37use llkv_table::catalog::{ColumnResolution, IdentifierContext, IdentifierResolver};
38use llkv_table::{CatalogDdl, ConstraintEnforcementMode, TriggerEventMeta, TriggerTimingMeta};
39use llkv_types::decimal::DecimalValue;
40use regex::Regex;
41use simd_r_drive_entry_handle::EntryHandle;
42use sqlparser::ast::{
43    AlterColumnOperation, AlterTableOperation, Assignment, AssignmentTarget, BeginTransactionKind,
44    BinaryOperator, ColumnOption, ColumnOptionDef, ConstraintCharacteristics, CreateTrigger,
45    DataType as SqlDataType, Delete, Distinct, DropTrigger, ExceptionWhen, Expr as SqlExpr,
46    FromTable, FunctionArg, FunctionArgExpr, FunctionArguments, GroupByExpr, Ident, JoinConstraint,
47    JoinOperator, LimitClause, NullsDistinctOption, ObjectName, ObjectNamePart, ObjectType,
48    OrderBy, OrderByKind, Query, ReferentialAction, SchemaName, Select, SelectItem,
49    SelectItemQualifiedWildcardKind, Set, SetExpr, SetOperator, SetQuantifier, SqlOption,
50    Statement, TableConstraint, TableFactor, TableObject, TableWithJoins, TransactionMode,
51    TransactionModifier, TriggerEvent, TriggerObject, TriggerPeriod, UnaryOperator,
52    UpdateTableFromKind, VacuumStatement, Value, ValueWithSpan,
53};
54use sqlparser::dialect::GenericDialect;
55use sqlparser::parser::Parser;
56use sqlparser::tokenizer::Span;
57
58type SqlPager = BoxedPager;
59type SqlRuntimePager = SqlPager;
60type SqlStatementResult = RuntimeStatementResult<SqlPager>;
61type SqlContext = RuntimeContext<SqlPager>;
62type SqlSession = RuntimeSession;
63type P = SqlRuntimePager;
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66pub enum StatementExpectation {
67    Ok,
68    Error,
69    Count(u64),
70}
71
72thread_local! {
73    static PENDING_STATEMENT_EXPECTATIONS: RefCell<VecDeque<StatementExpectation>> = const {
74        RefCell::new(VecDeque::new())
75    };
76}
77
78pub(crate) const PARAM_SENTINEL_PREFIX: &str = "__llkv_param__";
79pub(crate) const PARAM_SENTINEL_SUFFIX: &str = "__";
80
81thread_local! {
82    static ACTIVE_PARAMETER_STATE: RefCell<Option<ParameterState>> = const {
83        RefCell::new(None)
84    };
85}
86
87#[derive(Default)]
88struct ParameterState {
89    assigned: FxHashMap<String, usize>,
90    next_auto: usize,
91    max_index: usize,
92}
93
94impl ParameterState {
95    fn register(&mut self, raw: &str) -> SqlResult<usize> {
96        if raw == "?" {
97            self.next_auto += 1;
98            let idx = self.next_auto;
99            self.max_index = self.max_index.max(idx);
100            return Ok(idx);
101        }
102
103        if let Some(&idx) = self.assigned.get(raw) {
104            return Ok(idx);
105        }
106
107        let idx = if let Some(rest) = raw.strip_prefix('?') {
108            parse_numeric_placeholder(rest, raw)?
109        } else if let Some(rest) = raw.strip_prefix('$') {
110            parse_numeric_placeholder(rest, raw)?
111        } else if let Some(rest) = raw.strip_prefix(':') {
112            if rest.is_empty() {
113                return Err(Error::InvalidArgumentError(
114                    "named parameters must include an identifier".into(),
115                ));
116            }
117            self.max_index + 1
118        } else {
119            return Err(Error::InvalidArgumentError(format!(
120                "unsupported SQL parameter placeholder: {raw}",
121            )));
122        };
123
124        self.assigned.insert(raw.to_string(), idx);
125        self.max_index = self.max_index.max(idx);
126        self.next_auto = self.next_auto.max(idx);
127        Ok(idx)
128    }
129
130    fn max_index(&self) -> usize {
131        self.max_index
132    }
133}
134
135fn extract_limit_offset(
136    limit_clause: &sqlparser::ast::LimitClause,
137) -> SqlResult<(Option<usize>, Option<usize>)> {
138    use sqlparser::ast::LimitClause;
139    match limit_clause {
140        LimitClause::LimitOffset {
141            limit,
142            offset,
143            limit_by,
144        } => {
145            if !limit_by.is_empty() {
146                return Err(Error::InvalidArgumentError(
147                    "LIMIT BY is not supported".into(),
148                ));
149            }
150            let limit_val = if let Some(expr) = limit {
151                extract_usize_from_expr(expr, "LIMIT")?
152            } else {
153                None
154            };
155            let offset_val = if let Some(offset_struct) = offset {
156                extract_usize_from_expr(&offset_struct.value, "OFFSET")?
157            } else {
158                None
159            };
160            Ok((limit_val, offset_val))
161        }
162        LimitClause::OffsetCommaLimit { offset, limit } => {
163            let offset_val = extract_usize_from_expr(offset, "OFFSET")?;
164            let limit_val = extract_usize_from_expr(limit, "LIMIT")?;
165            Ok((limit_val, offset_val))
166        }
167    }
168}
169
170fn extract_usize_from_expr(expr: &sqlparser::ast::Expr, context: &str) -> SqlResult<Option<usize>> {
171    use sqlparser::ast::{Expr, Value};
172    match expr {
173        Expr::Value(value) => match &value.value {
174            Value::Number(text, _) => {
175                let parsed = text.parse::<i64>().map_err(|_| {
176                    Error::InvalidArgumentError(format!("{} must be an integer", context))
177                })?;
178                if parsed < 0 {
179                    return Err(Error::InvalidArgumentError(format!(
180                        "{} must be non-negative",
181                        context
182                    )));
183                }
184                Ok(Some(parsed as usize))
185            }
186            _ => Err(Error::InvalidArgumentError(format!(
187                "{} must be a constant integer",
188                context
189            ))),
190        },
191        _ => Err(Error::InvalidArgumentError(format!(
192            "{} must be a constant integer",
193            context
194        ))),
195    }
196}
197
198#[derive(Clone, Copy, Debug, PartialEq, Eq)]
199enum SubqueryMaterializationMode {
200    ExecuteScalarSubqueries,
201    PreserveScalarSubqueries,
202}
203
204impl SubqueryMaterializationMode {
205    fn executes_scalar_subqueries(self) -> bool {
206        matches!(self, SubqueryMaterializationMode::ExecuteScalarSubqueries)
207    }
208}
209
210fn parse_numeric_placeholder(text: &str, raw: &str) -> SqlResult<usize> {
211    if text.is_empty() {
212        return Err(Error::InvalidArgumentError(format!(
213            "parameter placeholder '{raw}' is missing an index",
214        )));
215    }
216    text.parse::<usize>().map_err(|_| {
217        Error::InvalidArgumentError(format!(
218            "parameter placeholder '{raw}' must end with digits"
219        ))
220    })
221}
222
223struct ParameterScope {
224    finished: bool,
225}
226
227impl ParameterScope {
228    fn new() -> Self {
229        ACTIVE_PARAMETER_STATE.with(|cell| {
230            debug_assert!(
231                cell.borrow().is_none(),
232                "nested parameter scopes not supported"
233            );
234            *cell.borrow_mut() = Some(ParameterState::default());
235        });
236        Self { finished: false }
237    }
238
239    fn finish(mut self) -> ParameterState {
240        let state = ACTIVE_PARAMETER_STATE
241            .with(|cell| cell.borrow_mut().take())
242            .unwrap_or_default();
243        self.finished = true;
244        state
245    }
246}
247
248impl Drop for ParameterScope {
249    fn drop(&mut self) {
250        if !self.finished {
251            ACTIVE_PARAMETER_STATE.with(|cell| {
252                cell.borrow_mut().take();
253            });
254        }
255    }
256}
257
258pub(crate) fn register_placeholder(raw: &str) -> SqlResult<usize> {
259    ACTIVE_PARAMETER_STATE.with(|cell| {
260        let mut guard = cell.borrow_mut();
261        let state = guard.as_mut().ok_or_else(|| {
262            Error::InvalidArgumentError(
263                "SQL parameters can only be used with prepared statements".into(),
264            )
265        })?;
266        state.register(raw)
267    })
268}
269
270pub(crate) fn placeholder_marker(index: usize) -> String {
271    format!("{PARAM_SENTINEL_PREFIX}{index}{PARAM_SENTINEL_SUFFIX}")
272}
273
274pub(crate) fn literal_placeholder(index: usize) -> Literal {
275    Literal::String(placeholder_marker(index))
276}
277
278fn parse_placeholder_marker(text: &str) -> Option<usize> {
279    let stripped = text.strip_prefix(PARAM_SENTINEL_PREFIX)?;
280    let numeric = stripped.strip_suffix(PARAM_SENTINEL_SUFFIX)?;
281    numeric.parse().ok()
282}
283
284#[derive(Clone, Debug)]
285pub enum SqlParamValue {
286    Null,
287    Integer(i64),
288    Float(f64),
289    Boolean(bool),
290    String(String),
291    Date32(i32),
292}
293
294impl SqlParamValue {
295    fn as_literal(&self) -> Literal {
296        match self {
297            SqlParamValue::Null => Literal::Null,
298            SqlParamValue::Integer(v) => Literal::Int128(i128::from(*v)),
299            SqlParamValue::Float(v) => Literal::Float64(*v),
300            SqlParamValue::Boolean(v) => Literal::Boolean(*v),
301            SqlParamValue::String(s) => Literal::String(s.clone()),
302            SqlParamValue::Date32(days) => Literal::Date32(*days),
303        }
304    }
305
306    fn as_plan_value(&self) -> PlanValue {
307        match self {
308            SqlParamValue::Null => PlanValue::Null,
309            SqlParamValue::Integer(v) => PlanValue::Integer(*v),
310            SqlParamValue::Float(v) => PlanValue::Float(*v),
311            SqlParamValue::Boolean(v) => PlanValue::Integer(if *v { 1 } else { 0 }),
312            SqlParamValue::String(s) => PlanValue::String(s.clone()),
313            SqlParamValue::Date32(days) => PlanValue::Date32(*days),
314        }
315    }
316}
317
318impl From<i64> for SqlParamValue {
319    fn from(value: i64) -> Self {
320        SqlParamValue::Integer(value)
321    }
322}
323
324impl From<f64> for SqlParamValue {
325    fn from(value: f64) -> Self {
326        SqlParamValue::Float(value)
327    }
328}
329
330impl From<bool> for SqlParamValue {
331    fn from(value: bool) -> Self {
332        SqlParamValue::Boolean(value)
333    }
334}
335
336impl From<String> for SqlParamValue {
337    fn from(value: String) -> Self {
338        SqlParamValue::String(value)
339    }
340}
341
342impl From<i32> for SqlParamValue {
343    fn from(value: i32) -> Self {
344        SqlParamValue::Date32(value)
345    }
346}
347
348impl From<&str> for SqlParamValue {
349    fn from(value: &str) -> Self {
350        SqlParamValue::String(value.to_string())
351    }
352}
353
354#[derive(Clone)]
355struct PreparedPlan {
356    plan: PlanStatement,
357    param_count: usize,
358}
359
360#[derive(Clone)]
361pub struct PreparedStatement {
362    inner: Arc<PreparedPlan>,
363}
364
365impl PreparedStatement {
366    fn new(inner: Arc<PreparedPlan>) -> Self {
367        Self { inner }
368    }
369
370    pub fn parameter_count(&self) -> usize {
371        self.inner.param_count
372    }
373}
374
375pub fn register_statement_expectation(expectation: StatementExpectation) {
376    PENDING_STATEMENT_EXPECTATIONS.with(|queue| {
377        queue.borrow_mut().push_back(expectation);
378    });
379}
380
381pub fn clear_pending_statement_expectations() {
382    PENDING_STATEMENT_EXPECTATIONS.with(|queue| {
383        queue.borrow_mut().clear();
384    });
385}
386
387fn next_statement_expectation() -> StatementExpectation {
388    PENDING_STATEMENT_EXPECTATIONS
389        .with(|queue| queue.borrow_mut().pop_front())
390        .unwrap_or(StatementExpectation::Ok)
391}
392
393// TODO: Extract to constants.rs
394// TODO: Rename to SQL_PARSER_RECURSION_LIMIT
395/// Maximum recursion depth for SQL parser.
396///
397/// The default in sqlparser is 50, which can be exceeded by deeply nested queries
398/// (e.g., SQLite test suite). This value allows for more complex expressions while
399/// still preventing stack overflows.
400const PARSER_RECURSION_LIMIT: usize = 200;
401
402trait ScalarSubqueryResolver {
403    fn handle_scalar_subquery(
404        &mut self,
405        subquery: &Query,
406        resolver: &IdentifierResolver<'_>,
407        context: &IdentifierContext,
408        outer_scopes: &[IdentifierContext],
409    ) -> SqlResult<llkv_expr::expr::ScalarExpr<String>>;
410}
411
412/// Helper trait for requesting placeholders directly from catalog resolutions.
413trait SubqueryCorrelatedTrackerExt {
414    fn placeholder_for_resolution(
415        &mut self,
416        resolution: &llkv_table::catalog::ColumnResolution,
417    ) -> Option<String>;
418}
419
420impl SubqueryCorrelatedTrackerExt for SubqueryCorrelatedTracker<'_> {
421    fn placeholder_for_resolution(
422        &mut self,
423        resolution: &llkv_table::catalog::ColumnResolution,
424    ) -> Option<String> {
425        self.placeholder_for_column_path(resolution.column(), resolution.field_path())
426    }
427}
428
429/// Convenience extension so optional tracker references can be reborrowed without
430/// repeating `as_mut` callers across translation helpers.
431trait SubqueryCorrelatedTrackerOptionExt {
432    fn reborrow(&mut self) -> Option<&mut SubqueryCorrelatedColumnTracker>;
433}
434
435impl SubqueryCorrelatedTrackerOptionExt for Option<&mut SubqueryCorrelatedColumnTracker> {
436    fn reborrow(&mut self) -> Option<&mut SubqueryCorrelatedColumnTracker> {
437        self.as_mut().map(|tracker| &mut **tracker)
438    }
439}
440
441/// SQL execution engine built on top of the LLKV runtime.
442///
443/// # Examples
444///
445/// ```
446/// use std::sync::Arc;
447///
448/// use arrow::array::StringArray;
449/// use llkv_sql::{RuntimeStatementResult, SqlEngine};
450/// use llkv_storage::pager::MemPager;
451///
452/// let engine = SqlEngine::new(Arc::new(MemPager::default()));
453///
454/// let setup = r#"
455///     CREATE TABLE users (id INT PRIMARY KEY, name TEXT);
456///     INSERT INTO users (id, name) VALUES (1, 'Ada');
457/// "#;
458///
459/// let results = engine.execute(setup).unwrap();
460/// assert_eq!(results.len(), 2);
461///
462/// assert!(matches!(
463///     results[0],
464///     RuntimeStatementResult::CreateTable { ref table_name } if table_name == "users"
465/// ));
466/// assert!(matches!(
467///     results[1],
468///     RuntimeStatementResult::Insert { rows_inserted, .. } if rows_inserted == 1
469/// ));
470///
471/// let batches = engine.sql("SELECT id, name FROM users ORDER BY id;").unwrap();
472/// assert_eq!(batches.len(), 1);
473///
474/// let batch = &batches[0];
475/// assert_eq!(batch.num_rows(), 1);
476/// assert_eq!(batch.schema().field(1).name(), "name");
477///
478/// let names = batch
479///     .column(1)
480///     .as_any()
481///     .downcast_ref::<StringArray>()
482///     .unwrap();
483///
484/// assert_eq!(names.value(0), "Ada");
485/// ```
486/// Maximum number of literal `VALUES` rows to accumulate before forcing a flush.
487///
488/// This keeps memory usage predictable when ingesting massive SQL scripts while still
489/// providing large batched inserts for throughput.
490const MAX_BUFFERED_INSERT_ROWS: usize = 8192;
491
492/// Accumulates literal `INSERT` payloads so multiple statements can be flushed together.
493///
494/// Each buffered statement tracks its individual row count while sharing a single literal
495/// payload vector. When the buffer flushes we can emit one `RuntimeStatementResult::Insert`
496/// per original statement without re-planning intermediate work.
497struct InsertBuffer {
498    table_name: String,
499    columns: Vec<String>,
500    /// Conflict resolution action
501    on_conflict: InsertConflictAction,
502    /// Total literal rows gathered so far (sums `statement_row_counts`).
503    total_rows: usize,
504    /// Row counts for each original INSERT statement so we can emit per-statement results.
505    statement_row_counts: Vec<usize>,
506    /// Literal row payloads in execution order.
507    rows: Vec<Vec<PlanValue>>,
508}
509
510impl InsertBuffer {
511    fn new(
512        table_name: String,
513        columns: Vec<String>,
514        rows: Vec<Vec<PlanValue>>,
515        on_conflict: InsertConflictAction,
516    ) -> Self {
517        let row_count = rows.len();
518        Self {
519            table_name,
520            columns,
521            on_conflict,
522            total_rows: row_count,
523            statement_row_counts: vec![row_count],
524            rows,
525        }
526    }
527
528    fn can_accept(
529        &self,
530        table_name: &str,
531        columns: &[String],
532        on_conflict: InsertConflictAction,
533    ) -> bool {
534        self.table_name == table_name && self.columns == columns && self.on_conflict == on_conflict
535    }
536
537    fn push_statement(&mut self, rows: Vec<Vec<PlanValue>>) {
538        let row_count = rows.len();
539        self.total_rows += row_count;
540        self.statement_row_counts.push(row_count);
541        self.rows.extend(rows);
542    }
543
544    fn should_flush(&self) -> bool {
545        self.total_rows >= MAX_BUFFERED_INSERT_ROWS
546    }
547}
548
549/// Describes how a parsed `INSERT` should flow through the execution pipeline after we
550/// canonicalize the AST.
551///
552/// Literal `VALUES` payloads (including constant folds such as `SELECT 1`) are rewritten into
553/// [`PlanValue`] rows so they can be stitched together with other buffered statements before we
554/// hit the planner. Non-literal sources stay as full [`InsertPlan`]s and execute immediately.
555enum PreparedInsert {
556    Values {
557        table_name: String,
558        columns: Vec<String>,
559        rows: Vec<Vec<PlanValue>>,
560        on_conflict: InsertConflictAction,
561    },
562    Immediate(InsertPlan),
563}
564
565/// Return value from [`SqlEngine::buffer_insert`], exposing any buffered flush results along with
566/// the row-count placeholder for the currently processed statement.
567struct BufferedInsertResult {
568    flushed: Vec<SqlStatementResult>,
569    current: Option<SqlStatementResult>,
570}
571
572pub struct SqlEngine {
573    engine: RuntimeEngine,
574    default_nulls_first: AtomicBool,
575    /// Buffer for batching INSERTs across execute() calls for massive performance gains.
576    insert_buffer: RefCell<Option<InsertBuffer>>,
577    /// Tracks whether cross-statement INSERT buffering is enabled for this engine instance.
578    ///
579    /// Batch mode is disabled by default so unit tests and non-bulk ingest callers observe the
580    /// runtime's native per-statement semantics. Long-running workloads (for example, the SLT
581    /// harness) can opt in via [`SqlEngine::set_insert_buffering`] to trade immediate visibility
582    /// for much lower planning overhead.
583    insert_buffering_enabled: AtomicBool,
584    information_schema_ready: AtomicBool,
585    statement_cache: RwLock<FxHashMap<String, Arc<PreparedPlan>>>,
586}
587
588const DROPPED_TABLE_TRANSACTION_ERR: &str = "another transaction has dropped this table";
589
590impl Drop for SqlEngine {
591    fn drop(&mut self) {
592        // Flush remaining INSERTs when engine is dropped
593        if let Err(e) = self.flush_buffer_results() {
594            tracing::warn!("Failed to flush INSERT buffer on drop: {:?}", e);
595        }
596    }
597}
598
599impl Clone for SqlEngine {
600    fn clone(&self) -> Self {
601        tracing::warn!(
602            "[SQL_ENGINE] SqlEngine::clone() called - will create new Engine with new session!"
603        );
604        // Create a new session from the same context
605        Self {
606            engine: self.engine.clone(),
607            default_nulls_first: AtomicBool::new(
608                self.default_nulls_first.load(AtomicOrdering::Relaxed),
609            ),
610            insert_buffer: RefCell::new(None),
611            insert_buffering_enabled: AtomicBool::new(
612                self.insert_buffering_enabled.load(AtomicOrdering::Relaxed),
613            ),
614            information_schema_ready: AtomicBool::new(
615                self.information_schema_ready.load(AtomicOrdering::Relaxed),
616            ),
617            statement_cache: RwLock::new(FxHashMap::default()),
618        }
619    }
620}
621
622#[allow(dead_code)]
623impl SqlEngine {
624    /// Instantiate a new SQL engine from an existing context.
625    ///
626    /// The `default_nulls_first` parameter controls the default sort order for `NULL` values.
627    pub fn with_context(context: Arc<SqlContext>, default_nulls_first: bool) -> Self {
628        Self::from_runtime_engine(
629            RuntimeEngine::from_context(context),
630            default_nulls_first,
631            false,
632        )
633    }
634
635    /// Expose the underlying runtime context for advanced callers (bulk loaders, tooling).
636    ///
637    /// This should only be used when the higher-level SQL interface lacks the necessary
638    /// hook—for example, enabling specialized constraint caches or inspecting catalog state.
639    pub fn runtime_context(&self) -> Arc<SqlContext> {
640        self.engine.context()
641    }
642
643    /// Fetch write-sizing hints from the underlying column store.
644    pub fn column_store_write_hints(&self) -> ColumnStoreWriteHints {
645        self.runtime_context().column_store_write_hints()
646    }
647
648    fn ensure_information_schema_ready(&self) -> SqlResult<()> {
649        if !self.information_schema_ready.load(AtomicOrdering::Acquire) {
650            self.engine.refresh_information_schema()?;
651            self.information_schema_ready
652                .store(true, AtomicOrdering::Release);
653        }
654        Ok(())
655    }
656
657    fn invalidate_information_schema(&self) {
658        self.information_schema_ready
659            .store(false, AtomicOrdering::Release);
660    }
661
662    fn from_runtime_engine(
663        engine: RuntimeEngine,
664        default_nulls_first: bool,
665        insert_buffering_enabled: bool,
666    ) -> Self {
667        Self {
668            engine,
669            default_nulls_first: AtomicBool::new(default_nulls_first),
670            insert_buffer: RefCell::new(None),
671            insert_buffering_enabled: AtomicBool::new(insert_buffering_enabled),
672            information_schema_ready: AtomicBool::new(false),
673            statement_cache: RwLock::new(FxHashMap::default()),
674        }
675    }
676
677    fn map_table_error(table_name: &str, err: Error) -> Error {
678        match err {
679            Error::NotFound => Self::table_not_found_error(table_name),
680            Error::InvalidArgumentError(msg) if msg.contains("unknown table") => {
681                Self::table_not_found_error(table_name)
682            }
683            other => other,
684        }
685    }
686
687    fn table_not_found_error(table_name: &str) -> Error {
688        Error::CatalogError(format!(
689            "Catalog Error: Table '{table_name}' does not exist"
690        ))
691    }
692
693    fn is_table_missing_error(err: &Error) -> bool {
694        match err {
695            Error::NotFound => true,
696            Error::CatalogError(msg) => {
697                msg.contains("Catalog Error: Table") || msg.contains("unknown table")
698            }
699            Error::InvalidArgumentError(msg) => {
700                msg.contains("Catalog Error: Table") || msg.contains("unknown table")
701            }
702            _ => false,
703        }
704    }
705
706    fn execute_plan_statement(&self, statement: PlanStatement) -> SqlResult<SqlStatementResult> {
707        // Don't apply table error mapping to CREATE VIEW or DROP VIEW statements
708        // because the "table" name is the view being created/dropped, not a referenced table.
709        // Any "unknown table" errors from CREATE VIEW are about tables referenced in the SELECT.
710        let should_map_error = !matches!(
711            &statement,
712            PlanStatement::CreateView(_) | PlanStatement::DropView(_)
713        );
714
715        // Check if this statement modifies the schema structure
716        let modifies_schema = matches!(
717            &statement,
718            PlanStatement::CreateTable(_)
719                | PlanStatement::DropTable(_)
720                | PlanStatement::AlterTable(_)
721                | PlanStatement::CreateView(_)
722                | PlanStatement::DropView(_)
723        );
724
725        let table = if should_map_error {
726            llkv_runtime::statement_table_name(&statement)
727        } else {
728            None
729        };
730
731        let result = self.engine.execute_statement(statement).map_err(|err| {
732            if let Some(table_name) = table {
733                Self::map_table_error(&table_name, err)
734            } else {
735                err
736            }
737        });
738
739        // Invalidate information schema cache after successful schema modifications
740        if result.is_ok() && modifies_schema {
741            self.invalidate_information_schema();
742        }
743
744        result
745    }
746
747    /// Construct a new engine backed by the provided pager with insert buffering disabled.
748    ///
749    /// Callers that intend to stream large amounts of literal `INSERT ... VALUES` input can
750    /// enable batching later using [`SqlEngine::set_insert_buffering`].
751    pub fn new<Pg>(pager: Arc<Pg>) -> Self
752    where
753        Pg: Pager<Blob = EntryHandle> + Send + Sync + 'static,
754    {
755        let engine = RuntimeEngine::new(pager);
756        Self::from_runtime_engine(engine, false, false)
757    }
758
759    /// Strip TPC-H style `CONNECT TO database;` statements.
760    ///
761    /// The TPCH tooling emits `CONNECT TO <name>;` directives inside the referential
762    /// integrity script even though LLKV always operates within a single database.
763    /// Treat these commands as no-ops so the scripts can run unmodified.
764    fn preprocess_tpch_connect_syntax(sql: &str) -> String {
765        crate::tpch::strip_tpch_connect_statements(sql)
766    }
767
768    /// Preprocess SQL to handle qualified names in EXCLUDE clauses
769    /// Converts EXCLUDE (schema.table.col) to EXCLUDE ("schema.table.col")
770    /// Preprocess CREATE TYPE to CREATE DOMAIN for sqlparser compatibility.
771    ///
772    /// DuckDB uses `CREATE TYPE name AS basetype` for type aliases, but sqlparser
773    /// only supports the SQL standard `CREATE DOMAIN name AS basetype` syntax.
774    /// This method converts the DuckDB syntax to the standard syntax.
775    fn preprocess_create_type_syntax(sql: &str) -> String {
776        static CREATE_TYPE_REGEX: OnceLock<Regex> = OnceLock::new();
777        static DROP_TYPE_REGEX: OnceLock<Regex> = OnceLock::new();
778
779        // Match: CREATE TYPE name AS datatype
780        let create_re = CREATE_TYPE_REGEX.get_or_init(|| {
781            Regex::new(r"(?i)\bCREATE\s+TYPE\s+").expect("valid CREATE TYPE regex")
782        });
783
784        // Match: DROP TYPE [IF EXISTS] name
785        let drop_re = DROP_TYPE_REGEX
786            .get_or_init(|| Regex::new(r"(?i)\bDROP\s+TYPE\s+").expect("valid DROP TYPE regex"));
787
788        // First replace CREATE TYPE with CREATE DOMAIN
789        let sql = create_re.replace_all(sql, "CREATE DOMAIN ").to_string();
790
791        // Then replace DROP TYPE with DROP DOMAIN
792        drop_re.replace_all(&sql, "DROP DOMAIN ").to_string()
793    }
794
795    fn preprocess_exclude_syntax(sql: &str) -> String {
796        static EXCLUDE_REGEX: OnceLock<Regex> = OnceLock::new();
797
798        // Pattern to match EXCLUDE followed by qualified identifiers
799        // Matches: EXCLUDE (identifier.identifier.identifier)
800        let re = EXCLUDE_REGEX.get_or_init(|| {
801            Regex::new(
802                r"(?i)EXCLUDE\s*\(\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\s*\)",
803            )
804            .expect("valid EXCLUDE qualifier regex")
805        });
806
807        re.replace_all(sql, |caps: &regex::Captures| {
808            let qualified_name = &caps[1];
809            format!("EXCLUDE (\"{}\")", qualified_name)
810        })
811        .to_string()
812    }
813
814    /// Preprocess SQL to remove trailing commas in VALUES clauses.
815    /// DuckDB allows trailing commas like VALUES ('v2',) but sqlparser does not.
816    fn preprocess_trailing_commas_in_values(sql: &str) -> String {
817        static TRAILING_COMMA_REGEX: OnceLock<Regex> = OnceLock::new();
818
819        // Pattern to match trailing comma before closing paren in VALUES
820        // Matches: , followed by optional whitespace and )
821        let re = TRAILING_COMMA_REGEX
822            .get_or_init(|| Regex::new(r",(\s*)\)").expect("valid trailing comma regex"));
823
824        re.replace_all(sql, "$1)").to_string()
825    }
826
827    /// Preprocess SQL to handle empty IN lists.
828    ///
829    /// SQLite permits `expr IN ()` and `expr NOT IN ()` as degenerate forms of IN expressions.
830    /// The sqlparser library rejects these, so we convert them to constant boolean expressions:
831    /// `expr IN ()` becomes `expr = NULL AND 0 = 1` (always false), and `expr NOT IN ()`
832    /// becomes `expr = NULL OR 1 = 1` (always true). The `expr = NULL` component ensures the
833    /// original expression is still evaluated (in case it has side effects), while the constant
834    /// comparison determines the final result.
835    fn preprocess_empty_in_lists(sql: &str) -> String {
836        static EMPTY_IN_REGEX: OnceLock<Regex> = OnceLock::new();
837
838        // Match: expression NOT IN () or expression IN ()
839        // Matches: parenthesized expressions, quoted strings, hex literals, identifiers, or numbers
840        let re = EMPTY_IN_REGEX.get_or_init(|| {
841            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*\)")
842                .expect("valid empty IN regex")
843        });
844
845        re.replace_all(sql, |caps: &regex::Captures| {
846            let expr = &caps[1];
847            if caps.get(2).is_some() {
848                // expr NOT IN () → always true (but still evaluate expr)
849                format!("({} = NULL OR 1 = 1)", expr)
850            } else {
851                // expr IN () → always false (but still evaluate expr)
852                format!("({} = NULL AND 0 = 1)", expr)
853            }
854        })
855        .to_string()
856    }
857
858    /// Strip SQLite index hints (INDEXED BY, NOT INDEXED) from table references.
859    ///
860    /// SQLite supports index hints like `FROM table INDEXED BY index_name` or `FROM table NOT INDEXED`.
861    /// These are query optimization hints that guide the query planner's index selection.
862    /// Since sqlparser doesn't support this syntax and our planner makes its own index decisions,
863    /// we strip these hints during preprocessing for compatibility with SQLite SQL Logic Tests.
864    fn preprocess_index_hints(sql: &str) -> String {
865        static INDEX_HINT_REGEX: OnceLock<Regex> = OnceLock::new();
866
867        // Match: INDEXED BY index_name or NOT INDEXED
868        // Pattern captures the table reference and removes the index hint
869        let re = INDEX_HINT_REGEX.get_or_init(|| {
870            Regex::new(r"(?i)\s+(INDEXED\s+BY\s+[a-zA-Z_][a-zA-Z0-9_]*|NOT\s+INDEXED)\b")
871                .expect("valid index hint regex")
872        });
873
874        re.replace_all(sql, "").to_string()
875    }
876
877    /// Convert SQLite standalone REINDEX to VACUUM REINDEX for sqlparser.
878    ///
879    /// SQLite supports `REINDEX index_name` as a standalone statement, but sqlparser
880    /// only recognizes REINDEX as part of the VACUUM statement syntax. This preprocessor
881    /// converts the SQLite form to the VACUUM REINDEX form that sqlparser can parse.
882    fn preprocess_reindex_syntax(sql: &str) -> String {
883        static REINDEX_REGEX: OnceLock<Regex> = OnceLock::new();
884
885        // Match: REINDEX followed by an identifier (index name)
886        // Captures the full statement to replace it with VACUUM REINDEX
887        let re = REINDEX_REGEX.get_or_init(|| {
888            Regex::new(r"(?i)\bREINDEX\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\b")
889                .expect("valid reindex regex")
890        });
891
892        re.replace_all(sql, "VACUUM REINDEX $1").to_string()
893    }
894
895    /// Normalize SQLite trigger shorthand so sqlparser accepts the syntax.
896    ///
897    /// SQLite allows omitting the trigger timing (defaulting to AFTER) and the
898    /// `FOR EACH ROW` clause (defaulting to row-level triggers). sqlparser
899    /// requires both pieces to be explicit, so we inject them before parsing.
900    ///
901    /// # TODO
902    ///
903    /// This is a temporary workaround. The proper fix is to extend sqlparser's
904    /// `SQLiteDialect::parse_statement` to handle CREATE TRIGGER with optional
905    /// timing/FOR EACH ROW clauses, matching SQLite's actual grammar. That would
906    /// eliminate this fragile regex preprocessing entirely.
907    fn preprocess_sqlite_trigger_shorthand(sql: &str) -> String {
908        static IDENT_PATTERN: OnceLock<String> = OnceLock::new();
909        static COLUMN_IDENT_PATTERN: OnceLock<String> = OnceLock::new();
910        static TIMING_REGEX: OnceLock<Regex> = OnceLock::new();
911        static FOR_EACH_BEGIN_REGEX: OnceLock<Regex> = OnceLock::new();
912        static FOR_EACH_WHEN_REGEX: OnceLock<Regex> = OnceLock::new();
913
914        IDENT_PATTERN.get_or_init(|| {
915            // Matches optional dotted identifiers with standard or quoted segments.
916            r#"(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[a-zA-Z_][a-zA-Z0-9_]*)(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*"#
917                .to_string()
918        });
919        COLUMN_IDENT_PATTERN
920            .get_or_init(|| r#"(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[a-zA-Z_][a-zA-Z0-9_]*)"#.to_string());
921
922        let timing_re = TIMING_REGEX.get_or_init(|| {
923            let event = format!(
924                "UPDATE(?:\\s+OF\\s+{col}(?:\\s*,\\s*{col})*)?|DELETE|INSERT",
925                col = COLUMN_IDENT_PATTERN
926                    .get()
927                    .expect("column ident pattern initialized")
928            );
929            let pattern = format!(
930                r"(?ix)(?P<head>CREATE\s+TRIGGER\s+(?:IF\s+NOT\s+EXISTS\s+)?{ident})\s+(?P<event>{event})\s+ON",
931                ident = IDENT_PATTERN
932                    .get()
933                    .expect("ident pattern initialized"),
934                event = event
935            );
936            Regex::new(&pattern).expect("valid trigger timing regex")
937        });
938
939        let with_timing = timing_re
940            .replace_all(sql, |caps: &regex::Captures| {
941                let head = caps.name("head").unwrap().as_str();
942                let event = caps.name("event").unwrap().as_str().trim();
943                format!("{head} AFTER {event} ON")
944            })
945            .to_string();
946
947        let for_each_begin_re = FOR_EACH_BEGIN_REGEX.get_or_init(|| {
948            let pattern = format!(
949                r"(?ix)(?P<prefix>ON\s+{ident})\s+(?P<keyword>BEGIN\b)",
950                ident = IDENT_PATTERN.get().expect("ident pattern initialized"),
951            );
952            Regex::new(&pattern).expect("valid trigger FOR EACH BEGIN regex")
953        });
954
955        let with_for_each_begin = for_each_begin_re
956            .replace_all(&with_timing, |caps: &regex::Captures| {
957                let prefix = caps.name("prefix").unwrap().as_str();
958                let keyword = caps.name("keyword").unwrap().as_str();
959                format!("{prefix} FOR EACH ROW {keyword}")
960            })
961            .to_string();
962
963        let for_each_when_re = FOR_EACH_WHEN_REGEX.get_or_init(|| {
964            let pattern = format!(
965                r"(?ix)(?P<prefix>ON\s+{ident})\s+(?P<keyword>WHEN\b)",
966                ident = IDENT_PATTERN.get().expect("ident pattern initialized"),
967            );
968            Regex::new(&pattern).expect("valid trigger FOR EACH WHEN regex")
969        });
970
971        for_each_when_re
972            .replace_all(&with_for_each_begin, |caps: &regex::Captures| {
973                let prefix = caps.name("prefix").unwrap().as_str();
974                let keyword = caps.name("keyword").unwrap().as_str();
975                format!("{prefix} FOR EACH ROW {keyword}")
976            })
977            .to_string()
978    }
979
980    /// Preprocess SQL to convert bare table names in IN clauses to subqueries.
981    ///
982    /// SQLite allows `expr IN tablename` as shorthand for `expr IN (SELECT * FROM tablename)`.
983    /// The sqlparser library requires parentheses, so we convert the shorthand form.
984    fn preprocess_bare_table_in_clauses(sql: &str) -> String {
985        static BARE_TABLE_IN_REGEX: OnceLock<Regex> = OnceLock::new();
986
987        // Match: [NOT] IN identifier followed by whitespace/newline/end or specific punctuation
988        // Avoid matching "IN (" which is already a valid subquery
989        let re = BARE_TABLE_IN_REGEX.get_or_init(|| {
990            Regex::new(r"(?i)\b(NOT\s+)?IN\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)(\s|$|;|,|\))")
991                .expect("valid bare table IN regex")
992        });
993
994        re.replace_all(sql, |caps: &regex::Captures| {
995            let table_name = &caps[2];
996            let trailing = &caps[3];
997            if let Some(not_keyword) = caps.get(1) {
998                format!(
999                    "{}IN (SELECT * FROM {}){}",
1000                    not_keyword.as_str(),
1001                    table_name,
1002                    trailing
1003                )
1004            } else {
1005                format!("IN (SELECT * FROM {}){}", table_name, trailing)
1006            }
1007        })
1008        .to_string()
1009    }
1010
1011    /// Toggle literal `INSERT` buffering for the engine.
1012    ///
1013    /// When enabled, consecutive `INSERT ... VALUES` statements that target the same table and
1014    /// column list are accumulated and flushed together, dramatically lowering planning and
1015    /// execution overhead for workloads that stream tens of thousands of literal inserts.
1016    /// Disabling buffering reverts to SQLite-style immediate execution and is appropriate for
1017    /// unit tests or workloads that rely on per-statement side effects (errors, triggers,
1018    /// constraint violations) happening synchronously.
1019    ///
1020    /// Calling this method with `false` forces any pending batched rows to flush before
1021    /// returning, guaranteeing that subsequent reads observe the latest state.
1022    pub fn set_insert_buffering(&self, enabled: bool) -> SqlResult<()> {
1023        if !enabled {
1024            let _ = self.flush_buffer_results()?;
1025        }
1026        self.insert_buffering_enabled
1027            .store(enabled, AtomicOrdering::Relaxed);
1028        Ok(())
1029    }
1030
1031    #[cfg(test)]
1032    fn default_nulls_first_for_tests(&self) -> bool {
1033        self.default_nulls_first.load(AtomicOrdering::Relaxed)
1034    }
1035
1036    fn has_active_transaction(&self) -> bool {
1037        self.engine.session().has_active_transaction()
1038    }
1039
1040    /// Get a reference to the underlying session (for advanced use like error handling in test harnesses).
1041    pub fn session(&self) -> &SqlSession {
1042        self.engine.session()
1043    }
1044
1045    /// Execute one or more SQL statements and return their raw [`RuntimeStatementResult`]s.
1046    ///
1047    /// This method is the general-purpose entry point for running SQL against the engine when
1048    /// you need to mix statement types (e.g. `CREATE TABLE`, `INSERT`, `UPDATE`, `SELECT`) or
1049    /// when you care about per-statement status information. Statements are executed in the order
1050    /// they appear in the input string, and the results vector mirrors that ordering.
1051    ///
1052    /// For ad-hoc read queries where you only care about the resulting Arrow [`RecordBatch`]es,
1053    /// prefer [`SqlEngine::sql`], which enforces a single `SELECT` statement and collects its
1054    /// output for you. `execute` remains the right tool for schema migrations, transactional
1055    /// scripts, or workflows that need to inspect the specific runtime response for each
1056    /// statement.
1057    pub fn execute(&self, sql: &str) -> SqlResult<Vec<SqlStatementResult>> {
1058        tracing::trace!("DEBUG SQL execute: {}", sql);
1059
1060        // Preprocess SQL
1061        let processed_sql = Self::preprocess_sql_input(sql);
1062
1063        let dialect = GenericDialect {};
1064        let statements = match parse_sql_with_recursion_limit(&dialect, &processed_sql) {
1065            Ok(stmts) => stmts,
1066            Err(parse_err) => {
1067                // SQLite allows omitting BEFORE/AFTER and FOR EACH ROW in CREATE TRIGGER.
1068                // If parsing fails and the SQL contains CREATE TRIGGER, attempt to expand
1069                // the shorthand form and retry. This is a workaround until sqlparser's
1070                // SQLite dialect properly supports the abbreviated syntax.
1071                if processed_sql.to_uppercase().contains("CREATE TRIGGER") {
1072                    let expanded = Self::preprocess_sqlite_trigger_shorthand(&processed_sql);
1073                    parse_sql_with_recursion_limit(&dialect, &expanded).map_err(|_| {
1074                        Error::InvalidArgumentError(format!("failed to parse SQL: {parse_err}"))
1075                    })?
1076                } else {
1077                    return Err(Error::InvalidArgumentError(format!(
1078                        "failed to parse SQL: {parse_err}"
1079                    )));
1080                }
1081            }
1082        };
1083        let mut results = Vec::with_capacity(statements.len());
1084        for statement in statements.iter() {
1085            let statement_expectation = next_statement_expectation();
1086            match statement {
1087                Statement::Insert(insert) => {
1088                    let mut outcome = self.buffer_insert(insert.clone(), statement_expectation)?;
1089                    if let Some(current) = outcome.current.take() {
1090                        results.push(current);
1091                    }
1092                    results.append(&mut outcome.flushed);
1093                }
1094                Statement::StartTransaction { .. }
1095                | Statement::Commit { .. }
1096                | Statement::Rollback { .. } => {
1097                    // Flush before transaction boundaries
1098                    let mut flushed = self.flush_buffer_results()?;
1099                    let current = self.execute_statement(statement.clone())?;
1100                    results.push(current);
1101                    results.append(&mut flushed);
1102                }
1103                _ => {
1104                    // Flush before any non-INSERT
1105                    let mut flushed = self.flush_buffer_results()?;
1106                    let current = self.execute_statement(statement.clone())?;
1107                    results.push(current);
1108                    results.append(&mut flushed);
1109                }
1110            }
1111        }
1112
1113        Ok(results)
1114    }
1115
1116    fn preprocess_sql_input(sql: &str) -> String {
1117        let processed_sql = Self::preprocess_tpch_connect_syntax(sql);
1118        let processed_sql = Self::preprocess_create_type_syntax(&processed_sql);
1119        let processed_sql = Self::preprocess_exclude_syntax(&processed_sql);
1120        let processed_sql = Self::preprocess_trailing_commas_in_values(&processed_sql);
1121        let processed_sql = Self::preprocess_bare_table_in_clauses(&processed_sql);
1122        let processed_sql = Self::preprocess_empty_in_lists(&processed_sql);
1123        let processed_sql = Self::preprocess_index_hints(&processed_sql);
1124        Self::preprocess_reindex_syntax(&processed_sql)
1125    }
1126
1127    /// Flush any buffered literal `INSERT` statements and return their per-statement results.
1128    ///
1129    /// Workloads that stream many INSERT statements without interleaving reads can invoke this
1130    /// to force persistence without waiting for the next non-INSERT statement or the engine
1131    /// drop hook.
1132    pub fn flush_pending_inserts(&self) -> SqlResult<Vec<SqlStatementResult>> {
1133        self.flush_buffer_results()
1134    }
1135
1136    /// Prepare a single SQL statement for repeated execution.
1137    ///
1138    /// Prepared statements currently support `UPDATE` queries with positional or named
1139    /// parameters. Callers must provide parameter bindings when executing the returned handle.
1140    pub fn prepare(&self, sql: &str) -> SqlResult<PreparedStatement> {
1141        let processed_sql = Self::preprocess_sql_input(sql);
1142
1143        if let Some(existing) = self
1144            .statement_cache
1145            .read()
1146            .expect("statement cache poisoned")
1147            .get(&processed_sql)
1148        {
1149            return Ok(PreparedStatement::new(Arc::clone(existing)));
1150        }
1151
1152        let dialect = GenericDialect {};
1153        let statements = parse_sql_with_recursion_limit(&dialect, &processed_sql)
1154            .map_err(|err| Error::InvalidArgumentError(format!("failed to parse SQL: {err}")))?;
1155
1156        if statements.len() != 1 {
1157            return Err(Error::InvalidArgumentError(
1158                "prepared statements must contain exactly one SQL statement".into(),
1159            ));
1160        }
1161
1162        let statement = statements
1163            .into_iter()
1164            .next()
1165            .expect("statement count checked");
1166
1167        let scope = ParameterScope::new();
1168
1169        let plan = match statement {
1170            Statement::Update {
1171                table,
1172                assignments,
1173                from,
1174                selection,
1175                returning,
1176                ..
1177            } => {
1178                let update_plan =
1179                    self.build_update_plan(table, assignments, from, selection, returning)?;
1180                PlanStatement::Update(update_plan)
1181            }
1182            other => {
1183                return Err(Error::InvalidArgumentError(format!(
1184                    "prepared statements do not yet support {other:?}"
1185                )));
1186            }
1187        };
1188
1189        let parameter_meta = scope.finish();
1190        let prepared = Arc::new(PreparedPlan {
1191            plan,
1192            param_count: parameter_meta.max_index(),
1193        });
1194
1195        self.statement_cache
1196            .write()
1197            .expect("statement cache poisoned")
1198            .insert(processed_sql, Arc::clone(&prepared));
1199
1200        Ok(PreparedStatement::new(prepared))
1201    }
1202
1203    /// Execute a previously prepared statement with the supplied parameters.
1204    pub fn execute_prepared(
1205        &self,
1206        statement: &PreparedStatement,
1207        params: &[SqlParamValue],
1208    ) -> SqlResult<Vec<SqlStatementResult>> {
1209        if params.len() != statement.parameter_count() {
1210            return Err(Error::InvalidArgumentError(format!(
1211                "prepared statement expected {} parameters, received {}",
1212                statement.parameter_count(),
1213                params.len()
1214            )));
1215        }
1216
1217        let mut flushed = self.flush_buffer_results()?;
1218        let mut plan = statement.inner.plan.clone();
1219        bind_plan_parameters(&mut plan, params, statement.parameter_count())?;
1220        let current = self.execute_plan_statement(plan)?;
1221        flushed.insert(0, current);
1222        Ok(flushed)
1223    }
1224
1225    /// Buffer an `INSERT` statement when batching is enabled, or execute it immediately
1226    /// otherwise.
1227    ///
1228    /// The return value includes any flushed results (when a batch boundary is crossed) as well
1229    /// as the per-statement placeholder that preserves the original `RuntimeStatementResult`
1230    /// ordering expected by callers like the SLT harness.
1231    fn buffer_insert(
1232        &self,
1233        insert: sqlparser::ast::Insert,
1234        expectation: StatementExpectation,
1235    ) -> SqlResult<BufferedInsertResult> {
1236        // Expectations serve two purposes: (a) ensure we surface synchronous errors when the
1237        // SLT harness anticipates them, and (b) force a flush when the harness is validating the
1238        // rows-affected count. In both situations we bypass the buffer entirely so the runtime
1239        // executes the statement immediately.
1240        let execute_immediately = matches!(
1241            expectation,
1242            StatementExpectation::Error | StatementExpectation::Count(_)
1243        );
1244        if execute_immediately {
1245            let flushed = self.flush_buffer_results()?;
1246            let current = self.handle_insert(insert)?;
1247            return Ok(BufferedInsertResult {
1248                flushed,
1249                current: Some(current),
1250            });
1251        }
1252
1253        // When buffering is disabled for this engine (the default for unit tests and most
1254        // production callers), short-circuit to immediate execution so callers see the results
1255        // they expect without having to register additional expectations.
1256        if !self.insert_buffering_enabled.load(AtomicOrdering::Relaxed) {
1257            let flushed = self.flush_buffer_results()?;
1258            let current = self.handle_insert(insert)?;
1259            return Ok(BufferedInsertResult {
1260                flushed,
1261                current: Some(current),
1262            });
1263        }
1264
1265        let prepared = self.prepare_insert(insert)?;
1266        match prepared {
1267            PreparedInsert::Values {
1268                table_name,
1269                columns,
1270                rows,
1271                on_conflict,
1272            } => {
1273                let mut flushed = Vec::new();
1274                let statement_rows = rows.len();
1275                let mut buf = self.insert_buffer.borrow_mut();
1276                match buf.as_mut() {
1277                    Some(buffer) if buffer.can_accept(&table_name, &columns, on_conflict) => {
1278                        buffer.push_statement(rows);
1279                        if buffer.should_flush() {
1280                            drop(buf);
1281                            flushed = self.flush_buffer_results()?;
1282                            return Ok(BufferedInsertResult {
1283                                flushed,
1284                                current: None,
1285                            });
1286                        }
1287                        Ok(BufferedInsertResult {
1288                            flushed,
1289                            current: Some(RuntimeStatementResult::Insert {
1290                                table_name,
1291                                rows_inserted: statement_rows,
1292                            }),
1293                        })
1294                    }
1295                    Some(_) => {
1296                        drop(buf);
1297                        flushed = self.flush_buffer_results()?;
1298                        let mut buf = self.insert_buffer.borrow_mut();
1299                        *buf = Some(InsertBuffer::new(
1300                            table_name.clone(),
1301                            columns,
1302                            rows,
1303                            on_conflict,
1304                        ));
1305                        Ok(BufferedInsertResult {
1306                            flushed,
1307                            current: Some(RuntimeStatementResult::Insert {
1308                                table_name,
1309                                rows_inserted: statement_rows,
1310                            }),
1311                        })
1312                    }
1313                    None => {
1314                        *buf = Some(InsertBuffer::new(
1315                            table_name.clone(),
1316                            columns,
1317                            rows,
1318                            on_conflict,
1319                        ));
1320                        Ok(BufferedInsertResult {
1321                            flushed,
1322                            current: Some(RuntimeStatementResult::Insert {
1323                                table_name,
1324                                rows_inserted: statement_rows,
1325                            }),
1326                        })
1327                    }
1328                }
1329            }
1330            PreparedInsert::Immediate(plan) => {
1331                let flushed = self.flush_buffer_results()?;
1332                let executed = self.execute_plan_statement(PlanStatement::Insert(plan))?;
1333                Ok(BufferedInsertResult {
1334                    flushed,
1335                    current: Some(executed),
1336                })
1337            }
1338        }
1339    }
1340
1341    /// Flush buffered INSERTs, returning one result per original statement.
1342    fn flush_buffer_results(&self) -> SqlResult<Vec<SqlStatementResult>> {
1343        let mut buf = self.insert_buffer.borrow_mut();
1344        let buffer = match buf.take() {
1345            Some(b) => b,
1346            None => return Ok(Vec::new()),
1347        };
1348        drop(buf);
1349
1350        let InsertBuffer {
1351            table_name,
1352            columns,
1353            on_conflict,
1354            total_rows,
1355            statement_row_counts,
1356            rows,
1357        } = buffer;
1358
1359        if total_rows == 0 {
1360            return Ok(Vec::new());
1361        }
1362
1363        let plan = InsertPlan {
1364            table: table_name.clone(),
1365            columns,
1366            source: InsertSource::Rows(rows),
1367            on_conflict,
1368        };
1369
1370        let executed = self.execute_plan_statement(PlanStatement::Insert(plan))?;
1371        let inserted = match executed {
1372            RuntimeStatementResult::Insert { rows_inserted, .. } => {
1373                if rows_inserted != total_rows {
1374                    tracing::warn!(
1375                        "Buffered INSERT row count mismatch: expected {}, runtime inserted {}",
1376                        total_rows,
1377                        rows_inserted
1378                    );
1379                }
1380                rows_inserted
1381            }
1382            other => {
1383                return Err(Error::Internal(format!(
1384                    "expected Insert result when flushing buffer, got {other:?}"
1385                )));
1386            }
1387        };
1388
1389        let mut per_statement = Vec::with_capacity(statement_row_counts.len());
1390        let mut assigned = 0usize;
1391        for rows in statement_row_counts {
1392            assigned += rows;
1393            per_statement.push(RuntimeStatementResult::Insert {
1394                table_name: table_name.clone(),
1395                rows_inserted: rows,
1396            });
1397        }
1398
1399        if inserted != assigned {
1400            tracing::warn!(
1401                "Buffered INSERT per-statement totals ({}) do not match runtime ({}).",
1402                assigned,
1403                inserted
1404            );
1405        }
1406
1407        Ok(per_statement)
1408    }
1409
1410    /// Canonicalizes an `INSERT` statement so buffered workloads can share literal payloads while
1411    /// complex sources still execute eagerly.
1412    ///
1413    /// The translation enforces dialect constraints up front, rewrites `VALUES` clauses (and any
1414    /// constant `SELECT` forms) into `PlanValue` rows, and returns them under
1415    /// [`PreparedInsert::Values`] so [`Self::buffer_insert`] can append them to the rolling
1416    /// batch. Statements whose payload must be evaluated at runtime fall back to a fully planned
1417    /// [`InsertPlan`].
1418    ///
1419    /// # Errors
1420    ///
1421    /// Returns [`Error::InvalidArgumentError`] whenever the incoming AST uses syntactic forms we
1422    /// do not currently support or when the literal payload is empty.
1423    fn prepare_insert(&self, stmt: sqlparser::ast::Insert) -> SqlResult<PreparedInsert> {
1424        let table_name_debug =
1425            Self::table_name_from_insert(&stmt).unwrap_or_else(|_| "unknown".to_string());
1426        tracing::trace!(
1427            "DEBUG SQL prepare_insert called for table={}",
1428            table_name_debug
1429        );
1430
1431        if !self.engine.session().has_active_transaction()
1432            && self.is_table_marked_dropped(&table_name_debug)?
1433        {
1434            return Err(Error::TransactionContextError(
1435                DROPPED_TABLE_TRANSACTION_ERR.into(),
1436            ));
1437        }
1438
1439        // Extract conflict resolution action
1440        use sqlparser::ast::SqliteOnConflict;
1441        let on_conflict = if stmt.replace_into {
1442            InsertConflictAction::Replace
1443        } else if stmt.ignore {
1444            InsertConflictAction::Ignore
1445        } else if let Some(or_clause) = stmt.or {
1446            match or_clause {
1447                SqliteOnConflict::Replace => InsertConflictAction::Replace,
1448                SqliteOnConflict::Ignore => InsertConflictAction::Ignore,
1449                SqliteOnConflict::Abort => InsertConflictAction::Abort,
1450                SqliteOnConflict::Fail => InsertConflictAction::Fail,
1451                SqliteOnConflict::Rollback => InsertConflictAction::Rollback,
1452            }
1453        } else {
1454            InsertConflictAction::None
1455        };
1456
1457        if stmt.overwrite {
1458            return Err(Error::InvalidArgumentError(
1459                "INSERT OVERWRITE is not supported".into(),
1460            ));
1461        }
1462        if !stmt.assignments.is_empty() {
1463            return Err(Error::InvalidArgumentError(
1464                "INSERT ... SET is not supported".into(),
1465            ));
1466        }
1467        if stmt.partitioned.is_some() || !stmt.after_columns.is_empty() {
1468            return Err(Error::InvalidArgumentError(
1469                "partitioned INSERT is not supported".into(),
1470            ));
1471        }
1472        if stmt.returning.is_some() {
1473            return Err(Error::InvalidArgumentError(
1474                "INSERT ... RETURNING is not supported".into(),
1475            ));
1476        }
1477        if stmt.format_clause.is_some() || stmt.settings.is_some() {
1478            return Err(Error::InvalidArgumentError(
1479                "INSERT with FORMAT or SETTINGS is not supported".into(),
1480            ));
1481        }
1482
1483        let (display_name, _canonical_name) = match &stmt.table {
1484            TableObject::TableName(name) => canonical_object_name(name)?,
1485            _ => {
1486                return Err(Error::InvalidArgumentError(
1487                    "INSERT requires a plain table name".into(),
1488                ));
1489            }
1490        };
1491
1492        let columns: Vec<String> = stmt
1493            .columns
1494            .iter()
1495            .map(|ident| ident.value.clone())
1496            .collect();
1497
1498        let source_expr = stmt
1499            .source
1500            .as_ref()
1501            .ok_or_else(|| Error::InvalidArgumentError("INSERT requires a VALUES clause".into()))?;
1502        validate_simple_query(source_expr)?;
1503
1504        match source_expr.body.as_ref() {
1505            SetExpr::Values(values) => {
1506                if values.rows.is_empty() {
1507                    return Err(Error::InvalidArgumentError(
1508                        "INSERT VALUES list must contain at least one row".into(),
1509                    ));
1510                }
1511                let mut rows: Vec<Vec<PlanValue>> = Vec::with_capacity(values.rows.len());
1512                for row in &values.rows {
1513                    let mut converted = Vec::with_capacity(row.len());
1514                    for expr in row {
1515                        converted.push(PlanValue::from(SqlValue::try_from_expr(expr)?));
1516                    }
1517                    rows.push(converted);
1518                }
1519                Ok(PreparedInsert::Values {
1520                    table_name: display_name,
1521                    columns,
1522                    rows,
1523                    on_conflict,
1524                })
1525            }
1526            SetExpr::Select(select) => {
1527                if let Some(rows) = extract_constant_select_rows(select.as_ref())? {
1528                    return Ok(PreparedInsert::Values {
1529                        table_name: display_name,
1530                        columns,
1531                        rows,
1532                        on_conflict,
1533                    });
1534                }
1535                if let Some(range_rows) = extract_rows_from_range(select.as_ref())? {
1536                    return Ok(PreparedInsert::Values {
1537                        table_name: display_name,
1538                        columns,
1539                        rows: range_rows.into_rows(),
1540                        on_conflict,
1541                    });
1542                }
1543
1544                let select_plan = self.build_select_plan((**source_expr).clone())?;
1545                Ok(PreparedInsert::Immediate(InsertPlan {
1546                    table: display_name,
1547                    columns,
1548                    source: InsertSource::Select {
1549                        plan: Box::new(select_plan),
1550                    },
1551                    on_conflict,
1552                }))
1553            }
1554            _ => Err(Error::InvalidArgumentError(
1555                "unsupported INSERT source".into(),
1556            )),
1557        }
1558    }
1559
1560    /// Execute a single SELECT statement and return its results as Arrow [`RecordBatch`]es.
1561    ///
1562    /// The SQL passed to this method must contain exactly one statement, and that statement must
1563    /// be a `SELECT`. Statements that modify data (e.g. `INSERT`) should be executed up front
1564    /// using [`SqlEngine::execute`] before calling this helper.
1565    ///
1566    /// # Examples
1567    ///
1568    /// ```
1569    /// use std::sync::Arc;
1570    ///
1571    /// use arrow::array::StringArray;
1572    /// use llkv_sql::SqlEngine;
1573    /// use llkv_storage::pager::MemPager;
1574    ///
1575    /// let engine = SqlEngine::new(Arc::new(MemPager::default()));
1576    /// let _ = engine
1577    ///     .execute(
1578    ///         "CREATE TABLE users (id INT PRIMARY KEY, name TEXT);\n         \
1579    ///          INSERT INTO users (id, name) VALUES (1, 'Ada');",
1580    ///     )
1581    ///     .unwrap();
1582    ///
1583    /// let batches = engine.sql("SELECT id, name FROM users ORDER BY id;").unwrap();
1584    /// assert_eq!(batches.len(), 1);
1585    ///
1586    /// let batch = &batches[0];
1587    /// assert_eq!(batch.num_rows(), 1);
1588    ///
1589    /// let names = batch
1590    ///     .column(1)
1591    ///     .as_any()
1592    ///     .downcast_ref::<StringArray>()
1593    ///     .unwrap();
1594    /// assert_eq!(names.value(0), "Ada");
1595    /// ```
1596    pub fn sql(&self, sql: &str) -> SqlResult<Vec<RecordBatch>> {
1597        let mut results = self.execute(sql)?;
1598        if results.is_empty() {
1599            return Err(Error::InvalidArgumentError(
1600                "SqlEngine::sql expects a SELECT statement".into(),
1601            ));
1602        }
1603
1604        let primary = results.remove(0);
1605
1606        match primary {
1607            RuntimeStatementResult::Select { execution, .. } => execution.collect(),
1608            other => Err(Error::InvalidArgumentError(format!(
1609                "SqlEngine::sql requires a SELECT statement, got {other:?}",
1610            ))),
1611        }
1612    }
1613
1614    fn execute_statement(&self, statement: Statement) -> SqlResult<SqlStatementResult> {
1615        let statement_sql = statement.to_string();
1616        let _query_label_guard = push_query_label(statement_sql.clone());
1617        tracing::debug!("SQL execute_statement: {}", statement_sql.trim());
1618        tracing::trace!(
1619            "DEBUG SQL execute_statement: {:?}",
1620            match &statement {
1621                Statement::Insert(insert) =>
1622                    format!("Insert(table={:?})", Self::table_name_from_insert(insert)),
1623                Statement::Query(_) => "Query".to_string(),
1624                Statement::StartTransaction { .. } => "StartTransaction".to_string(),
1625                Statement::Commit { .. } => "Commit".to_string(),
1626                Statement::Rollback { .. } => "Rollback".to_string(),
1627                Statement::CreateTable(_) => "CreateTable".to_string(),
1628                Statement::Update { .. } => "Update".to_string(),
1629                Statement::Delete(_) => "Delete".to_string(),
1630                other => format!("Other({:?})", other),
1631            }
1632        );
1633        match statement {
1634            Statement::StartTransaction {
1635                modes,
1636                begin,
1637                transaction,
1638                modifier,
1639                statements,
1640                exception,
1641                has_end_keyword,
1642            } => self.handle_start_transaction(
1643                modes,
1644                begin,
1645                transaction,
1646                modifier,
1647                statements,
1648                exception,
1649                has_end_keyword,
1650            ),
1651            Statement::Commit {
1652                chain,
1653                end,
1654                modifier,
1655            } => self.handle_commit(chain, end, modifier),
1656            Statement::Rollback { chain, savepoint } => self.handle_rollback(chain, savepoint),
1657            other => self.execute_statement_non_transactional(other),
1658        }
1659    }
1660
1661    fn execute_statement_non_transactional(
1662        &self,
1663        statement: Statement,
1664    ) -> SqlResult<SqlStatementResult> {
1665        tracing::trace!("DEBUG SQL execute_statement_non_transactional called");
1666        match statement {
1667            Statement::CreateTable(stmt) => {
1668                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateTable");
1669                self.handle_create_table(stmt)
1670            }
1671            Statement::CreateIndex(stmt) => {
1672                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateIndex");
1673                self.handle_create_index(stmt)
1674            }
1675            Statement::CreateSchema {
1676                schema_name,
1677                if_not_exists,
1678                with,
1679                options,
1680                default_collate_spec,
1681                clone,
1682            } => {
1683                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateSchema");
1684                self.handle_create_schema(
1685                    schema_name,
1686                    if_not_exists,
1687                    with,
1688                    options,
1689                    default_collate_spec,
1690                    clone,
1691                )
1692            }
1693            Statement::CreateView {
1694                name,
1695                columns,
1696                query,
1697                materialized,
1698                or_replace,
1699                or_alter,
1700                options,
1701                cluster_by,
1702                comment,
1703                with_no_schema_binding,
1704                if_not_exists,
1705                temporary,
1706                to,
1707                params,
1708                secure,
1709                name_before_not_exists,
1710            } => {
1711                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateView");
1712                self.handle_create_view(
1713                    name,
1714                    columns,
1715                    query,
1716                    materialized,
1717                    or_replace,
1718                    or_alter,
1719                    options,
1720                    cluster_by,
1721                    comment,
1722                    with_no_schema_binding,
1723                    if_not_exists,
1724                    temporary,
1725                    to,
1726                    params,
1727                    secure,
1728                    name_before_not_exists,
1729                )
1730            }
1731            Statement::CreateDomain(create_domain) => {
1732                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateDomain");
1733                self.handle_create_domain(create_domain)
1734            }
1735            Statement::CreateTrigger(create_trigger) => {
1736                tracing::trace!("DEBUG SQL execute_statement_non_transactional: CreateTrigger");
1737                self.handle_create_trigger(create_trigger)
1738            }
1739            Statement::DropDomain(drop_domain) => {
1740                tracing::trace!("DEBUG SQL execute_statement_non_transactional: DropDomain");
1741                self.handle_drop_domain(drop_domain)
1742            }
1743            Statement::Insert(stmt) => {
1744                let table_name =
1745                    Self::table_name_from_insert(&stmt).unwrap_or_else(|_| "unknown".to_string());
1746                tracing::trace!(
1747                    "DEBUG SQL execute_statement_non_transactional: Insert(table={})",
1748                    table_name
1749                );
1750                self.handle_insert(stmt)
1751            }
1752            Statement::Query(query) => {
1753                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Query");
1754                // Eagerly refresh information_schema if the SQL text mentions it.
1755                // This is a simple text-based check that catches all forms (quoted, unquoted, etc.)
1756                // and ensures the schema is ready before any table resolution happens.
1757                let query_sql = query.to_string();
1758                if query_sql
1759                    .to_ascii_lowercase()
1760                    .contains("information_schema")
1761                {
1762                    self.ensure_information_schema_ready()?;
1763                }
1764                self.handle_query(*query)
1765            }
1766            Statement::Update {
1767                table,
1768                assignments,
1769                from,
1770                selection,
1771                returning,
1772                ..
1773            } => {
1774                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Update");
1775                self.handle_update(table, assignments, from, selection, returning)
1776            }
1777            Statement::Delete(delete) => {
1778                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Delete");
1779                self.handle_delete(delete)
1780            }
1781            Statement::Truncate {
1782                ref table_names,
1783                ref partitions,
1784                table,
1785                ref identity,
1786                cascade,
1787                ref on_cluster,
1788            } => {
1789                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Truncate");
1790                self.handle_truncate(
1791                    table_names,
1792                    partitions,
1793                    table,
1794                    identity,
1795                    cascade,
1796                    on_cluster,
1797                )
1798            }
1799            Statement::Drop {
1800                object_type,
1801                if_exists,
1802                names,
1803                cascade,
1804                restrict,
1805                purge,
1806                temporary,
1807                ..
1808            } => {
1809                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Drop");
1810                self.handle_drop(
1811                    object_type,
1812                    if_exists,
1813                    names,
1814                    cascade,
1815                    restrict,
1816                    purge,
1817                    temporary,
1818                )
1819            }
1820            Statement::DropTrigger(drop_trigger) => {
1821                tracing::trace!("DEBUG SQL execute_statement_non_transactional: DropTrigger");
1822                self.handle_drop_trigger(drop_trigger)
1823            }
1824            Statement::AlterTable {
1825                name,
1826                if_exists,
1827                only,
1828                operations,
1829                ..
1830            } => {
1831                tracing::trace!("DEBUG SQL execute_statement_non_transactional: AlterTable");
1832                self.handle_alter_table(name, if_exists, only, operations)
1833            }
1834            Statement::Set(set_stmt) => {
1835                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Set");
1836                self.handle_set(set_stmt)
1837            }
1838            Statement::Pragma { name, value, is_eq } => {
1839                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Pragma");
1840                self.handle_pragma(name, value, is_eq)
1841            }
1842            Statement::Vacuum(vacuum) => {
1843                tracing::trace!("DEBUG SQL execute_statement_non_transactional: Vacuum");
1844                self.handle_vacuum(vacuum)
1845            }
1846            other => {
1847                tracing::trace!(
1848                    "DEBUG SQL execute_statement_non_transactional: Other({:?})",
1849                    other
1850                );
1851                Err(Error::InvalidArgumentError(format!(
1852                    "unsupported SQL statement: {other:?}"
1853                )))
1854            }
1855        }
1856    }
1857
1858    fn table_name_from_insert(insert: &sqlparser::ast::Insert) -> SqlResult<String> {
1859        match &insert.table {
1860            TableObject::TableName(name) => Self::object_name_to_string(name),
1861            _ => Err(Error::InvalidArgumentError(
1862                "INSERT requires a plain table name".into(),
1863            )),
1864        }
1865    }
1866
1867    fn table_name_from_update(table: &TableWithJoins) -> SqlResult<Option<String>> {
1868        if !table.joins.is_empty() {
1869            return Err(Error::InvalidArgumentError(
1870                "UPDATE with JOIN targets is not supported yet".into(),
1871            ));
1872        }
1873        Self::table_with_joins_name(table)
1874    }
1875
1876    fn table_name_from_delete(delete: &Delete) -> SqlResult<Option<String>> {
1877        if !delete.tables.is_empty() {
1878            return Err(Error::InvalidArgumentError(
1879                "multi-table DELETE is not supported yet".into(),
1880            ));
1881        }
1882        let from_tables = match &delete.from {
1883            FromTable::WithFromKeyword(tables) | FromTable::WithoutKeyword(tables) => tables,
1884        };
1885        if from_tables.is_empty() {
1886            return Ok(None);
1887        }
1888        if from_tables.len() != 1 {
1889            return Err(Error::InvalidArgumentError(
1890                "DELETE over multiple tables is not supported yet".into(),
1891            ));
1892        }
1893        Self::table_with_joins_name(&from_tables[0])
1894    }
1895
1896    fn object_name_to_string(name: &ObjectName) -> SqlResult<String> {
1897        let (display, _) = canonical_object_name(name)?;
1898        Ok(display)
1899    }
1900
1901    #[allow(dead_code)]
1902    fn table_object_to_name(table: &TableObject) -> SqlResult<Option<String>> {
1903        match table {
1904            TableObject::TableName(name) => Ok(Some(Self::object_name_to_string(name)?)),
1905            TableObject::TableFunction(_) => Ok(None),
1906        }
1907    }
1908
1909    fn table_with_joins_name(table: &TableWithJoins) -> SqlResult<Option<String>> {
1910        match &table.relation {
1911            TableFactor::Table { name, .. } => Ok(Some(Self::object_name_to_string(name)?)),
1912            _ => Ok(None),
1913        }
1914    }
1915
1916    fn tables_in_query(query: &Query) -> SqlResult<Vec<String>> {
1917        let mut tables = Vec::new();
1918        if let sqlparser::ast::SetExpr::Select(select) = query.body.as_ref() {
1919            for table in &select.from {
1920                if let TableFactor::Table { name, .. } = &table.relation {
1921                    tables.push(Self::object_name_to_string(name)?);
1922                }
1923            }
1924        }
1925        Ok(tables)
1926    }
1927
1928    fn collect_available_columns_for_tables(
1929        &self,
1930        tables: &[llkv_plan::TableRef],
1931    ) -> SqlResult<FxHashSet<String>> {
1932        let mut columns = FxHashSet::default();
1933        for table_ref in tables {
1934            let display_name = table_ref.qualified_name();
1935            let canonical = display_name.to_ascii_lowercase();
1936            let table_columns = self.collect_known_columns(&display_name, &canonical)?;
1937            columns.extend(table_columns);
1938        }
1939        Ok(columns)
1940    }
1941
1942    fn collect_known_columns(
1943        &self,
1944        display_name: &str,
1945        canonical_name: &str,
1946    ) -> SqlResult<FxHashSet<String>> {
1947        let context = self.engine.context();
1948
1949        if context.is_table_marked_dropped(canonical_name) {
1950            return Err(Self::table_not_found_error(display_name));
1951        }
1952
1953        // First, check if the table was created in the current transaction
1954        if let Some(specs) = self
1955            .engine
1956            .session()
1957            .table_column_specs_from_transaction(canonical_name)
1958        {
1959            let mut columns = specs
1960                .into_iter()
1961                .map(|spec| spec.name.to_ascii_lowercase())
1962                .collect();
1963            Self::inject_system_column_aliases(&mut columns);
1964            return Ok(columns);
1965        }
1966
1967        // Otherwise, look it up in the committed catalog
1968        let (_, canonical_name) = llkv_table::canonical_table_name(display_name)
1969            .map_err(|e| arrow::error::ArrowError::ExternalError(Box::new(e)))?;
1970
1971        match context.catalog().table_column_specs(&canonical_name) {
1972            Ok(specs) if !specs.is_empty() => {
1973                let mut columns = specs
1974                    .into_iter()
1975                    .map(|spec| spec.name.to_ascii_lowercase())
1976                    .collect();
1977                Self::inject_system_column_aliases(&mut columns);
1978                Ok(columns)
1979            }
1980            Ok(_) => {
1981                if let Some(table_id) = context.catalog().table_id(&canonical_name)
1982                    && let Some(resolver) = context.catalog().field_resolver(table_id)
1983                {
1984                    let mut fallback: FxHashSet<String> = resolver
1985                        .field_names()
1986                        .into_iter()
1987                        .map(|name| name.to_ascii_lowercase())
1988                        .collect();
1989                    Self::inject_system_column_aliases(&mut fallback);
1990                    tracing::debug!(
1991                        "collect_known_columns: using resolver fallback for '{}': {:?}",
1992                        display_name,
1993                        fallback
1994                    );
1995                    return Ok(fallback);
1996                }
1997                Ok(FxHashSet::default())
1998            }
1999            Err(err) => {
2000                if Self::is_table_missing_error(&err) {
2001                    Ok(FxHashSet::default())
2002                } else {
2003                    Err(Self::map_table_error(display_name, err))
2004                }
2005            }
2006        }
2007    }
2008
2009    fn inject_system_column_aliases(columns: &mut FxHashSet<String>) {
2010        columns.insert(ROW_ID_COLUMN_NAME.to_ascii_lowercase());
2011    }
2012
2013    fn parse_view_query(sql: &str) -> SqlResult<Query> {
2014        use sqlparser::ast::Statement;
2015
2016        let dialect = GenericDialect {};
2017        let mut statements = Parser::parse_sql(&dialect, sql).map_err(|err| {
2018            Error::InvalidArgumentError(format!("failed to parse view definition: {}", err))
2019        })?;
2020
2021        if statements.len() != 1 {
2022            return Err(Error::InvalidArgumentError(
2023                "view definition must contain a single SELECT statement".into(),
2024            ));
2025        }
2026
2027        match statements.pop().unwrap() {
2028            Statement::Query(query) => Ok(*query),
2029            _ => Err(Error::InvalidArgumentError(
2030                "view definition must be expressed as a SELECT query".into(),
2031            )),
2032        }
2033    }
2034
2035    fn is_table_marked_dropped(&self, table_name: &str) -> SqlResult<bool> {
2036        let canonical = table_name.to_ascii_lowercase();
2037        Ok(self.engine.context().is_table_marked_dropped(&canonical))
2038    }
2039
2040    fn handle_create_table(
2041        &self,
2042        mut stmt: sqlparser::ast::CreateTable,
2043    ) -> SqlResult<SqlStatementResult> {
2044        validate_create_table_common(&stmt)?;
2045
2046        let (mut schema_name, table_name) = parse_schema_qualified_name(&stmt.name)?;
2047
2048        // Validate that the table name doesn't conflict with reserved schemas
2049        validate_reserved_table_name(schema_name.as_deref(), &table_name)?;
2050
2051        let namespace = if stmt.temporary {
2052            if schema_name.is_some() {
2053                return Err(Error::InvalidArgumentError(
2054                    "temporary tables cannot specify an explicit schema".into(),
2055                ));
2056            }
2057            schema_name = None;
2058            Some(TEMPORARY_NAMESPACE_ID.to_string())
2059        } else {
2060            None
2061        };
2062
2063        // Validate schema exists if specified
2064        if let Some(ref schema) = schema_name {
2065            let catalog = self.engine.context().table_catalog();
2066            if !catalog.schema_exists(schema) {
2067                return Err(Error::CatalogError(format!(
2068                    "Schema '{}' does not exist",
2069                    schema
2070                )));
2071            }
2072        }
2073
2074        // Use full qualified name (schema.table or just table)
2075        let display_name = match &schema_name {
2076            Some(schema) => format!("{}.{}", schema, table_name),
2077            None => table_name.clone(),
2078        };
2079        let canonical_name = display_name.to_ascii_lowercase();
2080        tracing::trace!(
2081            "\n=== HANDLE_CREATE_TABLE: table='{}' columns={} ===",
2082            display_name,
2083            stmt.columns.len()
2084        );
2085        if display_name.is_empty() {
2086            return Err(Error::InvalidArgumentError(
2087                "table name must not be empty".into(),
2088            ));
2089        }
2090
2091        if let Some(query) = stmt.query.take() {
2092            validate_create_table_as(&stmt)?;
2093            if let Some(result) = self.try_handle_range_ctas(
2094                &display_name,
2095                &canonical_name,
2096                &query,
2097                stmt.if_not_exists,
2098                stmt.or_replace,
2099                namespace.clone(),
2100            )? {
2101                return Ok(result);
2102            }
2103            return self.handle_create_table_as(
2104                display_name,
2105                canonical_name,
2106                *query,
2107                stmt.if_not_exists,
2108                stmt.or_replace,
2109                namespace.clone(),
2110            );
2111        }
2112
2113        if stmt.columns.is_empty() {
2114            return Err(Error::InvalidArgumentError(
2115                "CREATE TABLE requires at least one column".into(),
2116            ));
2117        }
2118
2119        validate_create_table_definition(&stmt)?;
2120
2121        let column_defs_ast = std::mem::take(&mut stmt.columns);
2122        let constraints = std::mem::take(&mut stmt.constraints);
2123
2124        let column_names: Vec<String> = column_defs_ast
2125            .iter()
2126            .map(|column_def| column_def.name.value.clone())
2127            .collect();
2128        ensure_unique_case_insensitive(column_names.iter().map(|name| name.as_str()), |dup| {
2129            format!(
2130                "duplicate column name '{}' in table '{}'",
2131                dup, display_name
2132            )
2133        })?;
2134        let column_names_lower: FxHashSet<String> = column_names
2135            .iter()
2136            .map(|name| name.to_ascii_lowercase())
2137            .collect();
2138
2139        let mut columns: Vec<PlanColumnSpec> = Vec::with_capacity(column_defs_ast.len());
2140        let mut primary_key_columns: FxHashSet<String> = FxHashSet::default();
2141        let mut foreign_keys: Vec<ForeignKeySpec> = Vec::new();
2142        let mut multi_column_uniques: Vec<MultiColumnUniqueSpec> = Vec::new();
2143
2144        // Second pass: process columns including CHECK validation and column-level FKs
2145        for column_def in column_defs_ast {
2146            let is_nullable = column_def
2147                .options
2148                .iter()
2149                .all(|opt| !matches!(opt.option, ColumnOption::NotNull));
2150
2151            let is_primary_key = column_def.options.iter().any(|opt| {
2152                matches!(
2153                    opt.option,
2154                    ColumnOption::Unique {
2155                        is_primary: true,
2156                        characteristics: _
2157                    }
2158                )
2159            });
2160
2161            let has_unique_constraint = column_def
2162                .options
2163                .iter()
2164                .any(|opt| matches!(opt.option, ColumnOption::Unique { .. }));
2165
2166            // Extract CHECK constraint if present and validate it
2167            let check_expr = column_def.options.iter().find_map(|opt| {
2168                if let ColumnOption::Check(expr) = &opt.option {
2169                    Some(expr)
2170                } else {
2171                    None
2172                }
2173            });
2174
2175            // Validate CHECK constraint if present (now we have all column names)
2176            if let Some(check_expr) = check_expr {
2177                let all_col_refs: Vec<&str> = column_names.iter().map(|s| s.as_str()).collect();
2178                validate_check_constraint(check_expr, &display_name, &all_col_refs)?;
2179            }
2180
2181            let check_expr_str = check_expr.map(|e| e.to_string());
2182
2183            // Extract column-level FOREIGN KEY (REFERENCES clause)
2184            for opt in &column_def.options {
2185                if let ColumnOption::ForeignKey {
2186                    foreign_table,
2187                    referred_columns,
2188                    on_delete,
2189                    on_update,
2190                    characteristics,
2191                } = &opt.option
2192                {
2193                    let spec = self.build_foreign_key_spec(
2194                        &display_name,
2195                        &canonical_name,
2196                        vec![column_def.name.value.clone()],
2197                        foreign_table,
2198                        referred_columns,
2199                        *on_delete,
2200                        *on_update,
2201                        characteristics,
2202                        &column_names_lower,
2203                        None,
2204                    )?;
2205                    foreign_keys.push(spec);
2206                }
2207            }
2208
2209            tracing::trace!(
2210                "DEBUG CREATE TABLE column '{}' is_primary_key={} has_unique={} check_expr={:?}",
2211                column_def.name.value,
2212                is_primary_key,
2213                has_unique_constraint,
2214                check_expr_str
2215            );
2216
2217            // Resolve custom type aliases to their base types
2218            let resolved_data_type = self.engine.context().resolve_type(&column_def.data_type);
2219
2220            let mut column = PlanColumnSpec::new(
2221                column_def.name.value.clone(),
2222                arrow_type_from_sql(&resolved_data_type)?,
2223                is_nullable,
2224            );
2225            tracing::trace!(
2226                "DEBUG PlanColumnSpec after new(): primary_key={} unique={}",
2227                column.primary_key,
2228                column.unique
2229            );
2230
2231            column = column
2232                .with_primary_key(is_primary_key)
2233                .with_unique(has_unique_constraint)
2234                .with_check(check_expr_str);
2235
2236            if is_primary_key {
2237                column.nullable = false;
2238                primary_key_columns.insert(column.name.to_ascii_lowercase());
2239            }
2240            tracing::trace!(
2241                "DEBUG PlanColumnSpec after with_primary_key({})/with_unique({}): primary_key={} unique={} check_expr={:?}",
2242                is_primary_key,
2243                has_unique_constraint,
2244                column.primary_key,
2245                column.unique,
2246                column.check_expr
2247            );
2248
2249            columns.push(column);
2250        }
2251
2252        // Apply supported table-level constraints (e.g., PRIMARY KEY)
2253        if !constraints.is_empty() {
2254            let mut column_lookup: FxHashMap<String, usize> =
2255                FxHashMap::with_capacity_and_hasher(columns.len(), Default::default());
2256            for (idx, column) in columns.iter().enumerate() {
2257                column_lookup.insert(column.name.to_ascii_lowercase(), idx);
2258            }
2259
2260            for constraint in constraints {
2261                match constraint {
2262                    TableConstraint::PrimaryKey {
2263                        columns: constraint_columns,
2264                        ..
2265                    } => {
2266                        if !primary_key_columns.is_empty() {
2267                            return Err(Error::InvalidArgumentError(
2268                                "multiple PRIMARY KEY constraints are not supported".into(),
2269                            ));
2270                        }
2271
2272                        ensure_non_empty(&constraint_columns, || {
2273                            "PRIMARY KEY requires at least one column".into()
2274                        })?;
2275
2276                        let mut pk_column_names: Vec<String> =
2277                            Vec::with_capacity(constraint_columns.len());
2278
2279                        for index_col in &constraint_columns {
2280                            let column_ident = extract_index_column_name(
2281                                index_col,
2282                                "PRIMARY KEY",
2283                                false, // no sort options allowed
2284                                false, // only simple identifiers
2285                            )?;
2286                            pk_column_names.push(column_ident);
2287                        }
2288
2289                        ensure_unique_case_insensitive(
2290                            pk_column_names.iter().map(|name| name.as_str()),
2291                            |dup| format!("duplicate column '{}' in PRIMARY KEY constraint", dup),
2292                        )?;
2293
2294                        ensure_known_columns_case_insensitive(
2295                            pk_column_names.iter().map(|name| name.as_str()),
2296                            &column_names_lower,
2297                            |unknown| {
2298                                format!("unknown column '{}' in PRIMARY KEY constraint", unknown)
2299                            },
2300                        )?;
2301
2302                        for column_ident in pk_column_names {
2303                            let normalized = column_ident.to_ascii_lowercase();
2304                            let idx = column_lookup.get(&normalized).copied().ok_or_else(|| {
2305                                Error::InvalidArgumentError(format!(
2306                                    "unknown column '{}' in PRIMARY KEY constraint",
2307                                    column_ident
2308                                ))
2309                            })?;
2310
2311                            let column = columns.get_mut(idx).expect("column index valid");
2312                            column.primary_key = true;
2313                            column.unique = true;
2314                            column.nullable = false;
2315
2316                            primary_key_columns.insert(normalized);
2317                        }
2318                    }
2319                    TableConstraint::Unique {
2320                        columns: constraint_columns,
2321                        index_type,
2322                        index_options,
2323                        characteristics,
2324                        nulls_distinct,
2325                        name,
2326                        ..
2327                    } => {
2328                        if !matches!(nulls_distinct, NullsDistinctOption::None) {
2329                            return Err(Error::InvalidArgumentError(
2330                                "UNIQUE constraints with NULLS DISTINCT/NOT DISTINCT are not supported yet".into(),
2331                            ));
2332                        }
2333
2334                        if index_type.is_some() {
2335                            return Err(Error::InvalidArgumentError(
2336                                "UNIQUE constraints with index types are not supported yet".into(),
2337                            ));
2338                        }
2339
2340                        if !index_options.is_empty() {
2341                            return Err(Error::InvalidArgumentError(
2342                                "UNIQUE constraints with index options are not supported yet"
2343                                    .into(),
2344                            ));
2345                        }
2346
2347                        if characteristics.is_some() {
2348                            return Err(Error::InvalidArgumentError(
2349                                "UNIQUE constraint characteristics are not supported yet".into(),
2350                            ));
2351                        }
2352
2353                        ensure_non_empty(&constraint_columns, || {
2354                            "UNIQUE constraint requires at least one column".into()
2355                        })?;
2356
2357                        let mut unique_column_names: Vec<String> =
2358                            Vec::with_capacity(constraint_columns.len());
2359
2360                        for index_column in &constraint_columns {
2361                            let column_ident = extract_index_column_name(
2362                                index_column,
2363                                "UNIQUE constraint",
2364                                false, // no sort options allowed
2365                                false, // only simple identifiers
2366                            )?;
2367                            unique_column_names.push(column_ident);
2368                        }
2369
2370                        ensure_unique_case_insensitive(
2371                            unique_column_names.iter().map(|name| name.as_str()),
2372                            |dup| format!("duplicate column '{}' in UNIQUE constraint", dup),
2373                        )?;
2374
2375                        ensure_known_columns_case_insensitive(
2376                            unique_column_names.iter().map(|name| name.as_str()),
2377                            &column_names_lower,
2378                            |unknown| format!("unknown column '{}' in UNIQUE constraint", unknown),
2379                        )?;
2380
2381                        if unique_column_names.len() > 1 {
2382                            // Multi-column UNIQUE constraint
2383                            multi_column_uniques.push(MultiColumnUniqueSpec {
2384                                name: name.map(|n| n.value),
2385                                columns: unique_column_names,
2386                            });
2387                        } else {
2388                            // Single-column UNIQUE constraint
2389                            let column_ident = unique_column_names
2390                                .into_iter()
2391                                .next()
2392                                .expect("unique constraint checked for emptiness");
2393                            let normalized = column_ident.to_ascii_lowercase();
2394                            let idx = column_lookup.get(&normalized).copied().ok_or_else(|| {
2395                                Error::InvalidArgumentError(format!(
2396                                    "unknown column '{}' in UNIQUE constraint",
2397                                    column_ident
2398                                ))
2399                            })?;
2400
2401                            let column = columns
2402                                .get_mut(idx)
2403                                .expect("column index from lookup must be valid");
2404                            column.unique = true;
2405                        }
2406                    }
2407                    TableConstraint::ForeignKey {
2408                        name,
2409                        index_name,
2410                        columns: fk_columns,
2411                        foreign_table,
2412                        referred_columns,
2413                        on_delete,
2414                        on_update,
2415                        characteristics,
2416                        ..
2417                    } => {
2418                        if index_name.is_some() {
2419                            return Err(Error::InvalidArgumentError(
2420                                "FOREIGN KEY index clauses are not supported yet".into(),
2421                            ));
2422                        }
2423
2424                        let referencing_columns: Vec<String> =
2425                            fk_columns.into_iter().map(|ident| ident.value).collect();
2426                        let spec = self.build_foreign_key_spec(
2427                            &display_name,
2428                            &canonical_name,
2429                            referencing_columns,
2430                            &foreign_table,
2431                            &referred_columns,
2432                            on_delete,
2433                            on_update,
2434                            &characteristics,
2435                            &column_names_lower,
2436                            name.map(|ident| ident.value),
2437                        )?;
2438
2439                        foreign_keys.push(spec);
2440                    }
2441                    unsupported => {
2442                        return Err(Error::InvalidArgumentError(format!(
2443                            "table-level constraint {:?} is not supported",
2444                            unsupported
2445                        )));
2446                    }
2447                }
2448            }
2449        }
2450
2451        let plan = CreateTablePlan {
2452            name: display_name,
2453            if_not_exists: stmt.if_not_exists,
2454            or_replace: stmt.or_replace,
2455            columns,
2456            source: None,
2457            namespace,
2458            foreign_keys,
2459            multi_column_uniques,
2460        };
2461        self.execute_plan_statement(PlanStatement::CreateTable(plan))
2462    }
2463
2464    fn handle_create_index(
2465        &self,
2466        stmt: sqlparser::ast::CreateIndex,
2467    ) -> SqlResult<RuntimeStatementResult<P>> {
2468        let sqlparser::ast::CreateIndex {
2469            name,
2470            table_name,
2471            using,
2472            columns,
2473            unique,
2474            concurrently,
2475            if_not_exists,
2476            include,
2477            nulls_distinct,
2478            with,
2479            predicate,
2480            index_options,
2481            alter_options,
2482            ..
2483        } = stmt;
2484
2485        if concurrently {
2486            return Err(Error::InvalidArgumentError(
2487                "CREATE INDEX CONCURRENTLY is not supported".into(),
2488            ));
2489        }
2490        if using.is_some() {
2491            return Err(Error::InvalidArgumentError(
2492                "CREATE INDEX USING clauses are not supported".into(),
2493            ));
2494        }
2495        if !include.is_empty() {
2496            return Err(Error::InvalidArgumentError(
2497                "CREATE INDEX INCLUDE columns are not supported".into(),
2498            ));
2499        }
2500        if nulls_distinct.is_some() {
2501            return Err(Error::InvalidArgumentError(
2502                "CREATE INDEX NULLS DISTINCT is not supported".into(),
2503            ));
2504        }
2505        if !with.is_empty() {
2506            return Err(Error::InvalidArgumentError(
2507                "CREATE INDEX WITH options are not supported".into(),
2508            ));
2509        }
2510        if predicate.is_some() {
2511            return Err(Error::InvalidArgumentError(
2512                "partial CREATE INDEX is not supported".into(),
2513            ));
2514        }
2515        if !index_options.is_empty() {
2516            return Err(Error::InvalidArgumentError(
2517                "CREATE INDEX options are not supported".into(),
2518            ));
2519        }
2520        if !alter_options.is_empty() {
2521            return Err(Error::InvalidArgumentError(
2522                "CREATE INDEX ALTER options are not supported".into(),
2523            ));
2524        }
2525        if columns.is_empty() {
2526            return Err(Error::InvalidArgumentError(
2527                "CREATE INDEX requires at least one column".into(),
2528            ));
2529        }
2530
2531        let (schema_name, base_table_name) = parse_schema_qualified_name(&table_name)?;
2532        if let Some(ref schema) = schema_name {
2533            let catalog = self.engine.context().table_catalog();
2534            if !catalog.schema_exists(schema) {
2535                return Err(Error::CatalogError(format!(
2536                    "Schema '{}' does not exist",
2537                    schema
2538                )));
2539            }
2540        }
2541
2542        let display_table_name = schema_name
2543            .as_ref()
2544            .map(|schema| format!("{}.{}", schema, base_table_name))
2545            .unwrap_or_else(|| base_table_name.clone());
2546        let canonical_table_name = display_table_name.to_ascii_lowercase();
2547
2548        let known_columns =
2549            self.collect_known_columns(&display_table_name, &canonical_table_name)?;
2550        let enforce_known_columns = !known_columns.is_empty();
2551
2552        let index_name = match name {
2553            Some(name_obj) => Some(Self::object_name_to_string(&name_obj)?),
2554            None => None,
2555        };
2556
2557        let mut index_columns: Vec<IndexColumnPlan> = Vec::with_capacity(columns.len());
2558        let mut seen_column_names: FxHashSet<String> = FxHashSet::default();
2559        for item in columns {
2560            // Check WITH FILL before calling helper (not part of standard validation)
2561            if item.column.with_fill.is_some() {
2562                return Err(Error::InvalidArgumentError(
2563                    "CREATE INDEX column WITH FILL is not supported".into(),
2564                ));
2565            }
2566
2567            let column_name = extract_index_column_name(
2568                &item,
2569                "CREATE INDEX",
2570                true, // allow and validate sort options
2571                true, // allow compound identifiers
2572            )?;
2573
2574            // Get sort options (already validated by helper)
2575            let order_expr = &item.column;
2576            let ascending = order_expr.options.asc.unwrap_or(true);
2577            let nulls_first = order_expr.options.nulls_first.unwrap_or(false);
2578
2579            let normalized = column_name.to_ascii_lowercase();
2580            if !seen_column_names.insert(normalized.clone()) {
2581                return Err(Error::InvalidArgumentError(format!(
2582                    "duplicate column '{}' in CREATE INDEX",
2583                    column_name
2584                )));
2585            }
2586
2587            if enforce_known_columns && !known_columns.contains(&normalized) {
2588                return Err(Error::InvalidArgumentError(format!(
2589                    "column '{}' does not exist in table '{}'",
2590                    column_name, display_table_name
2591                )));
2592            }
2593
2594            let column_plan = IndexColumnPlan::new(column_name).with_sort(ascending, nulls_first);
2595            index_columns.push(column_plan);
2596        }
2597
2598        let plan = CreateIndexPlan::new(display_table_name)
2599            .with_name(index_name)
2600            .with_unique(unique)
2601            .with_if_not_exists(if_not_exists)
2602            .with_columns(index_columns);
2603
2604        self.execute_plan_statement(PlanStatement::CreateIndex(plan))
2605    }
2606
2607    fn map_referential_action(
2608        action: Option<ReferentialAction>,
2609        kind: &str,
2610    ) -> SqlResult<PlanForeignKeyAction> {
2611        match action {
2612            None | Some(ReferentialAction::NoAction) => Ok(PlanForeignKeyAction::NoAction),
2613            Some(ReferentialAction::Restrict) => Ok(PlanForeignKeyAction::Restrict),
2614            Some(other) => Err(Error::InvalidArgumentError(format!(
2615                "FOREIGN KEY ON {kind} {:?} is not supported yet",
2616                other
2617            ))),
2618        }
2619    }
2620
2621    #[allow(clippy::too_many_arguments)]
2622    fn build_foreign_key_spec(
2623        &self,
2624        _referencing_display: &str,
2625        referencing_canonical: &str,
2626        referencing_columns: Vec<String>,
2627        foreign_table: &ObjectName,
2628        referenced_columns: &[Ident],
2629        on_delete: Option<ReferentialAction>,
2630        on_update: Option<ReferentialAction>,
2631        characteristics: &Option<ConstraintCharacteristics>,
2632        known_columns_lower: &FxHashSet<String>,
2633        name: Option<String>,
2634    ) -> SqlResult<ForeignKeySpec> {
2635        if characteristics.is_some() {
2636            return Err(Error::InvalidArgumentError(
2637                "FOREIGN KEY constraint characteristics are not supported yet".into(),
2638            ));
2639        }
2640
2641        ensure_non_empty(&referencing_columns, || {
2642            "FOREIGN KEY constraint requires at least one referencing column".into()
2643        })?;
2644        ensure_unique_case_insensitive(
2645            referencing_columns.iter().map(|name| name.as_str()),
2646            |dup| format!("duplicate column '{}' in FOREIGN KEY constraint", dup),
2647        )?;
2648        ensure_known_columns_case_insensitive(
2649            referencing_columns.iter().map(|name| name.as_str()),
2650            known_columns_lower,
2651            |unknown| format!("unknown column '{}' in FOREIGN KEY constraint", unknown),
2652        )?;
2653
2654        let referenced_columns_vec: Vec<String> = referenced_columns
2655            .iter()
2656            .map(|ident| ident.value.clone())
2657            .collect();
2658        ensure_unique_case_insensitive(
2659            referenced_columns_vec.iter().map(|name| name.as_str()),
2660            |dup| {
2661                format!(
2662                    "duplicate referenced column '{}' in FOREIGN KEY constraint",
2663                    dup
2664                )
2665            },
2666        )?;
2667
2668        if !referenced_columns_vec.is_empty()
2669            && referenced_columns_vec.len() != referencing_columns.len()
2670        {
2671            return Err(Error::InvalidArgumentError(
2672                "FOREIGN KEY referencing and referenced column counts must match".into(),
2673            ));
2674        }
2675
2676        let (referenced_display, referenced_canonical) = canonical_object_name(foreign_table)?;
2677
2678        // Check if the referenced table is a VIEW - VIEWs cannot be referenced by foreign keys
2679        let catalog = self.engine.context().table_catalog();
2680        if let Some(table_id) = catalog.table_id(&referenced_canonical) {
2681            let context = self.engine.context();
2682            if context.is_view(table_id)? {
2683                return Err(Error::CatalogError(format!(
2684                    "Binder Error: cannot reference a VIEW with a FOREIGN KEY: {}",
2685                    referenced_display
2686                )));
2687            }
2688        }
2689
2690        if referenced_canonical == referencing_canonical {
2691            ensure_known_columns_case_insensitive(
2692                referenced_columns_vec.iter().map(|name| name.as_str()),
2693                known_columns_lower,
2694                |unknown| {
2695                    format!(
2696                        "Binder Error: table '{}' does not have a column named '{}'",
2697                        referenced_display, unknown
2698                    )
2699                },
2700            )?;
2701        } else {
2702            let known_columns =
2703                self.collect_known_columns(&referenced_display, &referenced_canonical)?;
2704            if !known_columns.is_empty() {
2705                ensure_known_columns_case_insensitive(
2706                    referenced_columns_vec.iter().map(|name| name.as_str()),
2707                    &known_columns,
2708                    |unknown| {
2709                        format!(
2710                            "Binder Error: table '{}' does not have a column named '{}'",
2711                            referenced_display, unknown
2712                        )
2713                    },
2714                )?;
2715            }
2716        }
2717
2718        let on_delete_action = Self::map_referential_action(on_delete, "DELETE")?;
2719        let on_update_action = Self::map_referential_action(on_update, "UPDATE")?;
2720
2721        Ok(ForeignKeySpec {
2722            name,
2723            columns: referencing_columns,
2724            referenced_table: referenced_display,
2725            referenced_columns: referenced_columns_vec,
2726            on_delete: on_delete_action,
2727            on_update: on_update_action,
2728        })
2729    }
2730
2731    fn handle_create_schema(
2732        &self,
2733        schema_name: SchemaName,
2734        _if_not_exists: bool,
2735        with: Option<Vec<SqlOption>>,
2736        options: Option<Vec<SqlOption>>,
2737        default_collate_spec: Option<SqlExpr>,
2738        clone: Option<ObjectName>,
2739    ) -> SqlResult<RuntimeStatementResult<P>> {
2740        if clone.is_some() {
2741            return Err(Error::InvalidArgumentError(
2742                "CREATE SCHEMA ... CLONE is not supported".into(),
2743            ));
2744        }
2745        if with.as_ref().is_some_and(|opts| !opts.is_empty()) {
2746            return Err(Error::InvalidArgumentError(
2747                "CREATE SCHEMA ... WITH options are not supported".into(),
2748            ));
2749        }
2750        if options.as_ref().is_some_and(|opts| !opts.is_empty()) {
2751            return Err(Error::InvalidArgumentError(
2752                "CREATE SCHEMA options are not supported".into(),
2753            ));
2754        }
2755        if default_collate_spec.is_some() {
2756            return Err(Error::InvalidArgumentError(
2757                "CREATE SCHEMA DEFAULT COLLATE is not supported".into(),
2758            ));
2759        }
2760
2761        let schema_name = match schema_name {
2762            SchemaName::Simple(name) => name,
2763            _ => {
2764                return Err(Error::InvalidArgumentError(
2765                    "CREATE SCHEMA authorization is not supported".into(),
2766                ));
2767            }
2768        };
2769
2770        let (display_name, canonical) = canonical_object_name(&schema_name)?;
2771        if display_name.is_empty() {
2772            return Err(Error::InvalidArgumentError(
2773                "schema name must not be empty".into(),
2774            ));
2775        }
2776
2777        // Register schema in the catalog
2778        let catalog = self.engine.context().table_catalog();
2779
2780        if _if_not_exists && catalog.schema_exists(&canonical) {
2781            return Ok(RuntimeStatementResult::NoOp);
2782        }
2783
2784        catalog.register_schema(&canonical).map_err(|err| {
2785            Error::CatalogError(format!(
2786                "Failed to create schema '{}': {}",
2787                display_name, err
2788            ))
2789        })?;
2790
2791        Ok(RuntimeStatementResult::NoOp)
2792    }
2793
2794    #[allow(clippy::too_many_arguments)]
2795    fn handle_create_view(
2796        &self,
2797        name: ObjectName,
2798        columns: Vec<sqlparser::ast::ViewColumnDef>,
2799        query: Box<sqlparser::ast::Query>,
2800        materialized: bool,
2801        or_replace: bool,
2802        or_alter: bool,
2803        _options: sqlparser::ast::CreateTableOptions,
2804        _cluster_by: Vec<sqlparser::ast::Ident>,
2805        _comment: Option<String>,
2806        _with_no_schema_binding: bool,
2807        if_not_exists: bool,
2808        temporary: bool,
2809        _to: Option<ObjectName>,
2810        _params: Option<sqlparser::ast::CreateViewParams>,
2811        _secure: bool,
2812        _name_before_not_exists: bool,
2813    ) -> SqlResult<RuntimeStatementResult<P>> {
2814        // Validate unsupported features
2815        if materialized {
2816            return Err(Error::InvalidArgumentError(
2817                "MATERIALIZED VIEWS are not supported".into(),
2818            ));
2819        }
2820        if or_replace {
2821            return Err(Error::InvalidArgumentError(
2822                "CREATE OR REPLACE VIEW is not supported".into(),
2823            ));
2824        }
2825        if or_alter {
2826            return Err(Error::InvalidArgumentError(
2827                "CREATE OR ALTER VIEW is not supported".into(),
2828            ));
2829        }
2830
2831        // Parse view name (same as table parsing)
2832        let (schema_name, view_name) = parse_schema_qualified_name(&name)?;
2833
2834        // Validate schema exists if specified
2835        if let Some(ref schema) = schema_name {
2836            let catalog = self.engine.context().table_catalog();
2837            if !catalog.schema_exists(schema) {
2838                return Err(Error::CatalogError(format!(
2839                    "Schema '{}' does not exist",
2840                    schema
2841                )));
2842            }
2843        }
2844
2845        // Use full qualified name (schema.view or just view)
2846        let display_name = match &schema_name {
2847            Some(schema) => format!("{}.{}", schema, view_name),
2848            None => view_name.clone(),
2849        };
2850        let canonical_name = display_name.to_ascii_lowercase();
2851
2852        // Check if view already exists
2853        let catalog = self.engine.context().table_catalog();
2854        if catalog.table_exists(&canonical_name) {
2855            if if_not_exists {
2856                return Ok(RuntimeStatementResult::NoOp);
2857            }
2858            return Err(Error::CatalogError(format!(
2859                "Table or view '{}' already exists",
2860                display_name
2861            )));
2862        }
2863
2864        // Capture and normalize optional column list definitions.
2865        let column_names: Vec<String> = columns
2866            .into_iter()
2867            .map(|column| column.name.value)
2868            .collect();
2869
2870        if !column_names.is_empty() {
2871            ensure_unique_case_insensitive(column_names.iter().map(|name| name.as_str()), |dup| {
2872                format!("duplicate column name '{}' in CREATE VIEW column list", dup)
2873            })?;
2874        }
2875
2876        let mut query_ast = *query;
2877
2878        if !column_names.is_empty() {
2879            let select = match query_ast.body.as_mut() {
2880                sqlparser::ast::SetExpr::Select(select) => select,
2881                _ => {
2882                    return Err(Error::InvalidArgumentError(
2883                        "CREATE VIEW column list requires a SELECT query".into(),
2884                    ));
2885                }
2886            };
2887
2888            for item in &select.projection {
2889                if matches!(
2890                    item,
2891                    SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _)
2892                ) {
2893                    return Err(Error::InvalidArgumentError(
2894                        "CREATE VIEW column lists with wildcard projections are not supported yet"
2895                            .into(),
2896                    ));
2897                }
2898            }
2899
2900            if select.projection.len() != column_names.len() {
2901                return Err(Error::InvalidArgumentError(format!(
2902                    "CREATE VIEW column list specifies {} column(s) but SELECT projection yields {}",
2903                    column_names.len(),
2904                    select.projection.len()
2905                )));
2906            }
2907
2908            for (item, column_name) in select.projection.iter_mut().zip(column_names.iter()) {
2909                match item {
2910                    SelectItem::ExprWithAlias { alias, .. } => {
2911                        alias.value = column_name.clone();
2912                    }
2913                    SelectItem::UnnamedExpr(expr) => {
2914                        *item = SelectItem::ExprWithAlias {
2915                            expr: expr.clone(),
2916                            alias: Ident::new(column_name.clone()),
2917                        };
2918                    }
2919                    _ => {
2920                        return Err(Error::InvalidArgumentError(
2921                            "CREATE VIEW column list requires simple SELECT projections".into(),
2922                        ));
2923                    }
2924                }
2925            }
2926        }
2927
2928        // Validate the view SELECT plan after applying optional column aliases and capture it.
2929        let select_plan = self.build_select_plan(query_ast.clone())?;
2930
2931        // Convert query to SQL string for storage (after applying column aliases when present)
2932        let view_definition = query_ast.to_string();
2933
2934        // Build CreateViewPlan with namespace routing (same pattern as CREATE TABLE)
2935        let namespace = if temporary {
2936            Some(TEMPORARY_NAMESPACE_ID.to_string())
2937        } else {
2938            None
2939        };
2940
2941        let plan = CreateViewPlan {
2942            name: display_name.clone(),
2943            if_not_exists,
2944            view_definition,
2945            select_plan: Box::new(select_plan),
2946            namespace,
2947        };
2948
2949        self.execute_plan_statement(PlanStatement::CreateView(plan))?;
2950
2951        tracing::debug!("Created view: {}", display_name);
2952        Ok(RuntimeStatementResult::NoOp)
2953    }
2954
2955    fn handle_create_domain(
2956        &self,
2957        create_domain: sqlparser::ast::CreateDomain,
2958    ) -> SqlResult<RuntimeStatementResult<P>> {
2959        use llkv_table::CustomTypeMeta;
2960        use std::time::{SystemTime, UNIX_EPOCH};
2961
2962        // Extract the type alias name
2963        let type_name = create_domain.name.to_string();
2964
2965        // Convert the data type to SQL string for persistence
2966        let base_type_sql = create_domain.data_type.to_string();
2967
2968        // Register the type alias in the runtime context (in-memory)
2969        self.engine
2970            .context()
2971            .register_type(type_name.clone(), create_domain.data_type.clone());
2972
2973        // Persist to catalog
2974        let context = self.engine.context();
2975        let catalog = llkv_table::SysCatalog::new(context.store());
2976
2977        let created_at_micros = SystemTime::now()
2978            .duration_since(UNIX_EPOCH)
2979            .unwrap_or_default()
2980            .as_micros() as u64;
2981
2982        let meta = CustomTypeMeta {
2983            name: type_name.clone(),
2984            base_type_sql,
2985            created_at_micros,
2986        };
2987
2988        catalog.put_custom_type_meta(&meta)?;
2989
2990        tracing::debug!("Created and persisted type alias: {}", type_name);
2991        Ok(RuntimeStatementResult::NoOp)
2992    }
2993
2994    fn handle_create_trigger(
2995        &self,
2996        create_trigger: CreateTrigger,
2997    ) -> SqlResult<RuntimeStatementResult<P>> {
2998        let CreateTrigger {
2999            or_alter,
3000            or_replace,
3001            is_constraint,
3002            name,
3003            period,
3004            period_before_table: _,
3005            events,
3006            table_name,
3007            referenced_table_name,
3008            referencing,
3009            trigger_object,
3010            include_each: _,
3011            condition,
3012            exec_body,
3013            statements_as,
3014            statements,
3015            characteristics,
3016        } = create_trigger;
3017
3018        if or_alter {
3019            return Err(Error::InvalidArgumentError(
3020                "CREATE OR ALTER TRIGGER is not supported".into(),
3021            ));
3022        }
3023
3024        if or_replace {
3025            return Err(Error::InvalidArgumentError(
3026                "CREATE OR REPLACE TRIGGER is not supported".into(),
3027            ));
3028        }
3029
3030        if is_constraint {
3031            return Err(Error::InvalidArgumentError(
3032                "CREATE TRIGGER ... CONSTRAINT is not supported".into(),
3033            ));
3034        }
3035
3036        if referenced_table_name.is_some() {
3037            return Err(Error::InvalidArgumentError(
3038                "CREATE TRIGGER referencing another table is not supported".into(),
3039            ));
3040        }
3041
3042        if !referencing.is_empty() {
3043            return Err(Error::InvalidArgumentError(
3044                "CREATE TRIGGER REFERENCING clauses are not supported".into(),
3045            ));
3046        }
3047
3048        if characteristics.is_some() {
3049            return Err(Error::InvalidArgumentError(
3050                "CREATE TRIGGER constraint characteristics are not supported".into(),
3051            ));
3052        }
3053
3054        if events.is_empty() {
3055            return Err(Error::InvalidArgumentError(
3056                "CREATE TRIGGER requires at least one event".into(),
3057            ));
3058        }
3059
3060        if events.len() != 1 {
3061            return Err(Error::InvalidArgumentError(
3062                "CREATE TRIGGER currently supports exactly one trigger event".into(),
3063            ));
3064        }
3065
3066        let timing = match period {
3067            TriggerPeriod::Before => TriggerTimingMeta::Before,
3068            TriggerPeriod::After | TriggerPeriod::For => TriggerTimingMeta::After,
3069            TriggerPeriod::InsteadOf => TriggerTimingMeta::InsteadOf,
3070        };
3071
3072        let event_meta = match events.into_iter().next().expect("checked length") {
3073            TriggerEvent::Insert => TriggerEventMeta::Insert,
3074            TriggerEvent::Delete => TriggerEventMeta::Delete,
3075            TriggerEvent::Update(columns) => TriggerEventMeta::Update {
3076                columns: columns
3077                    .into_iter()
3078                    .map(|ident| ident.value.to_ascii_lowercase())
3079                    .collect(),
3080            },
3081            TriggerEvent::Truncate => {
3082                return Err(Error::InvalidArgumentError(
3083                    "CREATE TRIGGER for TRUNCATE events is not supported".into(),
3084                ));
3085            }
3086        };
3087
3088        let (trigger_display_name, canonical_trigger_name) = canonical_object_name(&name)?;
3089        let (table_display_name, canonical_table_name) = canonical_object_name(&table_name)?;
3090
3091        let condition_sql = condition.map(|expr| expr.to_string());
3092
3093        let body_sql = if let Some(exec_body) = exec_body {
3094            format!("EXECUTE {exec_body}")
3095        } else if let Some(statements) = statements {
3096            let rendered = statements.to_string();
3097            if statements_as {
3098                format!("AS {rendered}")
3099            } else {
3100                rendered
3101            }
3102        } else {
3103            return Err(Error::InvalidArgumentError(
3104                "CREATE TRIGGER requires a trigger body".into(),
3105            ));
3106        };
3107
3108        let for_each_row = matches!(trigger_object, TriggerObject::Row);
3109
3110        self.engine.context().create_trigger(
3111            &trigger_display_name,
3112            &canonical_trigger_name,
3113            &table_display_name,
3114            &canonical_table_name,
3115            timing,
3116            event_meta,
3117            for_each_row,
3118            condition_sql,
3119            body_sql,
3120            false,
3121        )?;
3122
3123        Ok(RuntimeStatementResult::NoOp)
3124    }
3125
3126    fn handle_drop_domain(
3127        &self,
3128        drop_domain: sqlparser::ast::DropDomain,
3129    ) -> SqlResult<RuntimeStatementResult<P>> {
3130        let if_exists = drop_domain.if_exists;
3131        let type_name = drop_domain.name.to_string();
3132
3133        // Drop the type from the registry (in-memory)
3134        let result = self.engine.context().drop_type(&type_name);
3135
3136        if let Err(err) = result {
3137            if !if_exists {
3138                return Err(err);
3139            }
3140            // if_exists = true, so ignore the error
3141        } else {
3142            // Persist deletion to catalog
3143            let context = self.engine.context();
3144            let catalog = llkv_table::SysCatalog::new(context.store());
3145            catalog.delete_custom_type_meta(&type_name)?;
3146
3147            tracing::debug!("Dropped and removed from catalog type alias: {}", type_name);
3148        }
3149
3150        Ok(RuntimeStatementResult::NoOp)
3151    }
3152
3153    fn handle_drop_trigger(
3154        &self,
3155        drop_trigger: DropTrigger,
3156    ) -> SqlResult<RuntimeStatementResult<P>> {
3157        let DropTrigger {
3158            if_exists,
3159            trigger_name,
3160            table_name,
3161            option,
3162        } = drop_trigger;
3163
3164        if option.is_some() {
3165            return Err(Error::InvalidArgumentError(
3166                "DROP TRIGGER CASCADE/RESTRICT options are not supported".into(),
3167            ));
3168        }
3169
3170        let (trigger_display_name, canonical_trigger_name) = canonical_object_name(&trigger_name)?;
3171
3172        let (table_display, table_canonical) = if let Some(table_name) = table_name {
3173            let (display, canonical) = canonical_object_name(&table_name)?;
3174            (Some(display), Some(canonical))
3175        } else {
3176            (None, None)
3177        };
3178
3179        let table_display_hint = table_display.as_deref();
3180        let table_canonical_hint = table_canonical.as_deref();
3181
3182        self.engine.context().drop_trigger(
3183            &trigger_display_name,
3184            &canonical_trigger_name,
3185            table_display_hint,
3186            table_canonical_hint,
3187            if_exists,
3188        )?;
3189
3190        Ok(RuntimeStatementResult::NoOp)
3191    }
3192
3193    fn try_handle_range_ctas(
3194        &self,
3195        display_name: &str,
3196        _canonical_name: &str,
3197        query: &Query,
3198        if_not_exists: bool,
3199        or_replace: bool,
3200        namespace: Option<String>,
3201    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
3202        let select = match query.body.as_ref() {
3203            SetExpr::Select(select) => select,
3204            _ => return Ok(None),
3205        };
3206        if select.from.len() != 1 {
3207            return Ok(None);
3208        }
3209        let table_with_joins = &select.from[0];
3210        if !table_with_joins.joins.is_empty() {
3211            return Ok(None);
3212        }
3213        let (range_size, range_alias) = match &table_with_joins.relation {
3214            TableFactor::Table {
3215                name,
3216                args: Some(args),
3217                alias,
3218                ..
3219            } => {
3220                let func_name = name.to_string().to_ascii_lowercase();
3221                if func_name != "range" {
3222                    return Ok(None);
3223                }
3224                if args.args.len() != 1 {
3225                    return Err(Error::InvalidArgumentError(
3226                        "range table function expects a single argument".into(),
3227                    ));
3228                }
3229                let size_expr = &args.args[0];
3230                let range_size = match size_expr {
3231                    FunctionArg::Unnamed(FunctionArgExpr::Expr(SqlExpr::Value(value))) => {
3232                        match &value.value {
3233                            Value::Number(raw, _) => raw.parse::<i64>().map_err(|e| {
3234                                Error::InvalidArgumentError(format!(
3235                                    "invalid range size literal {}: {}",
3236                                    raw, e
3237                                ))
3238                            })?,
3239                            other => {
3240                                return Err(Error::InvalidArgumentError(format!(
3241                                    "unsupported range size value: {:?}",
3242                                    other
3243                                )));
3244                            }
3245                        }
3246                    }
3247                    _ => {
3248                        return Err(Error::InvalidArgumentError(
3249                            "unsupported range argument".into(),
3250                        ));
3251                    }
3252                };
3253                (range_size, alias.as_ref().map(|a| a.name.value.clone()))
3254            }
3255            _ => return Ok(None),
3256        };
3257
3258        if range_size < 0 {
3259            return Err(Error::InvalidArgumentError(
3260                "range size must be non-negative".into(),
3261            ));
3262        }
3263
3264        if select.projection.is_empty() {
3265            return Err(Error::InvalidArgumentError(
3266                "CREATE TABLE AS SELECT requires at least one projected column".into(),
3267            ));
3268        }
3269
3270        let mut column_specs = Vec::with_capacity(select.projection.len());
3271        let mut column_names = Vec::with_capacity(select.projection.len());
3272        let mut row_template = Vec::with_capacity(select.projection.len());
3273        for item in &select.projection {
3274            match item {
3275                SelectItem::ExprWithAlias { expr, alias } => {
3276                    let (value, data_type) = match expr {
3277                        SqlExpr::Value(value_with_span) => match &value_with_span.value {
3278                            Value::Number(raw, _) => {
3279                                let parsed = raw.parse::<i64>().map_err(|e| {
3280                                    Error::InvalidArgumentError(format!(
3281                                        "invalid numeric literal {}: {}",
3282                                        raw, e
3283                                    ))
3284                                })?;
3285                                (
3286                                    PlanValue::Integer(parsed),
3287                                    arrow::datatypes::DataType::Int64,
3288                                )
3289                            }
3290                            Value::SingleQuotedString(s) => (
3291                                PlanValue::String(s.clone()),
3292                                arrow::datatypes::DataType::Utf8,
3293                            ),
3294                            other => {
3295                                return Err(Error::InvalidArgumentError(format!(
3296                                    "unsupported SELECT expression in range CTAS: {:?}",
3297                                    other
3298                                )));
3299                            }
3300                        },
3301                        SqlExpr::Identifier(ident) => {
3302                            let ident_lower = ident.value.to_ascii_lowercase();
3303                            if range_alias
3304                                .as_ref()
3305                                .map(|a| a.eq_ignore_ascii_case(&ident_lower))
3306                                .unwrap_or(false)
3307                                || ident_lower == "range"
3308                            {
3309                                return Err(Error::InvalidArgumentError(
3310                                    "range() table function columns are not supported yet".into(),
3311                                ));
3312                            }
3313                            return Err(Error::InvalidArgumentError(format!(
3314                                "unsupported identifier '{}' in range CTAS projection",
3315                                ident.value
3316                            )));
3317                        }
3318                        other => {
3319                            return Err(Error::InvalidArgumentError(format!(
3320                                "unsupported SELECT expression in range CTAS: {:?}",
3321                                other
3322                            )));
3323                        }
3324                    };
3325                    let column_name = alias.value.clone();
3326                    column_specs.push(PlanColumnSpec::new(column_name.clone(), data_type, true));
3327                    column_names.push(column_name);
3328                    row_template.push(value);
3329                }
3330                other => {
3331                    return Err(Error::InvalidArgumentError(format!(
3332                        "unsupported projection {:?} in range CTAS",
3333                        other
3334                    )));
3335                }
3336            }
3337        }
3338
3339        let plan = CreateTablePlan {
3340            name: display_name.to_string(),
3341            if_not_exists,
3342            or_replace,
3343            columns: column_specs,
3344            source: None,
3345            namespace,
3346            foreign_keys: Vec::new(),
3347            multi_column_uniques: Vec::new(),
3348        };
3349        let create_result = self.execute_plan_statement(PlanStatement::CreateTable(plan))?;
3350
3351        let row_count = range_size
3352            .try_into()
3353            .map_err(|_| Error::InvalidArgumentError("range size exceeds usize".into()))?;
3354        if row_count > 0 {
3355            let rows = vec![row_template; row_count];
3356            let insert_plan = InsertPlan {
3357                table: display_name.to_string(),
3358                columns: column_names,
3359                source: InsertSource::Rows(rows),
3360                on_conflict: InsertConflictAction::None,
3361            };
3362            self.execute_plan_statement(PlanStatement::Insert(insert_plan))?;
3363        }
3364
3365        Ok(Some(create_result))
3366    }
3367
3368    // TODO: Refactor into runtime or executor layer?
3369    // NOTE: PRAGMA handling lives in the SQL layer until a shared runtime hook exists.
3370    /// Try to handle pragma_table_info('table_name') table function
3371    fn try_handle_pragma_table_info(
3372        &self,
3373        query: &Query,
3374    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
3375        let select = match query.body.as_ref() {
3376            SetExpr::Select(select) => select,
3377            _ => return Ok(None),
3378        };
3379
3380        if select.from.len() != 1 {
3381            return Ok(None);
3382        }
3383
3384        let table_with_joins = &select.from[0];
3385        if !table_with_joins.joins.is_empty() {
3386            return Ok(None);
3387        }
3388
3389        // Check if this is pragma_table_info function call
3390        let table_name = match &table_with_joins.relation {
3391            TableFactor::Table {
3392                name,
3393                args: Some(args),
3394                ..
3395            } => {
3396                let func_name = name.to_string().to_ascii_lowercase();
3397                if func_name != "pragma_table_info" {
3398                    return Ok(None);
3399                }
3400
3401                // Extract table name from argument
3402                if args.args.len() != 1 {
3403                    return Err(Error::InvalidArgumentError(
3404                        "pragma_table_info expects exactly one argument".into(),
3405                    ));
3406                }
3407
3408                match &args.args[0] {
3409                    FunctionArg::Unnamed(FunctionArgExpr::Expr(SqlExpr::Value(value))) => {
3410                        match &value.value {
3411                            Value::SingleQuotedString(s) => s.clone(),
3412                            Value::DoubleQuotedString(s) => s.clone(),
3413                            _ => {
3414                                return Err(Error::InvalidArgumentError(
3415                                    "pragma_table_info argument must be a string".into(),
3416                                ));
3417                            }
3418                        }
3419                    }
3420                    _ => {
3421                        return Err(Error::InvalidArgumentError(
3422                            "pragma_table_info argument must be a string literal".into(),
3423                        ));
3424                    }
3425                }
3426            }
3427            _ => return Ok(None),
3428        };
3429
3430        // Get table column specs from runtime context
3431        let context = self.engine.context();
3432        let (_, canonical_name) = llkv_table::canonical_table_name(&table_name)?;
3433        let columns = context.catalog().table_column_specs(&canonical_name)?;
3434
3435        let alias = match &table_with_joins.relation {
3436            TableFactor::Table { alias, .. } => alias.as_ref().map(|a| a.name.value.clone()),
3437            _ => None,
3438        };
3439
3440        let mut view = InlineView::new(vec![
3441            InlineColumn::int32("cid", false),
3442            InlineColumn::utf8("name", false),
3443            InlineColumn::utf8("type", false),
3444            InlineColumn::bool("notnull", false),
3445            InlineColumn::utf8("dflt_value", true),
3446            InlineColumn::bool("pk", false),
3447        ]);
3448
3449        for (idx, col) in columns.iter().enumerate() {
3450            view.add_row(vec![
3451                InlineValue::Int32(Some(idx as i32)),
3452                InlineValue::String(Some(col.name.clone())),
3453                InlineValue::String(Some(format!("{:?}", col.data_type))),
3454                InlineValue::Bool(Some(!col.nullable)),
3455                InlineValue::String(None),
3456                InlineValue::Bool(Some(col.primary_key)),
3457            ])?;
3458        }
3459
3460        self.execute_inline_view(select, query, table_name, alias.as_deref(), view)
3461    }
3462
3463    fn execute_inline_view(
3464        &self,
3465        select: &Select,
3466        query: &Query,
3467        table_label: String,
3468        table_alias: Option<&str>,
3469        mut view: InlineView,
3470    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
3471        ensure_inline_select_supported(select, query)?;
3472
3473        if let Some(selection) = &select.selection {
3474            view.apply_selection(selection, table_alias)?;
3475        }
3476
3477        let (schema, batch) = view.into_record_batch()?;
3478        self.finalize_inline_select(select, query, table_label, schema, batch)
3479    }
3480
3481    fn finalize_inline_select(
3482        &self,
3483        select: &Select,
3484        query: &Query,
3485        table_name: String,
3486        schema: Arc<Schema>,
3487        batch: RecordBatch,
3488    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
3489        use arrow::array::ArrayRef;
3490
3491        let mut working_schema = schema;
3492        let mut working_batch = batch;
3493
3494        let projection_indices: Vec<usize> = select
3495            .projection
3496            .iter()
3497            .filter_map(|item| match item {
3498                SelectItem::UnnamedExpr(SqlExpr::Identifier(ident)) => {
3499                    working_schema.index_of(&ident.value).ok()
3500                }
3501                SelectItem::ExprWithAlias { expr, .. } => {
3502                    if let SqlExpr::Identifier(ident) = expr {
3503                        working_schema.index_of(&ident.value).ok()
3504                    } else {
3505                        None
3506                    }
3507                }
3508                SelectItem::Wildcard(_) => None,
3509                _ => None,
3510            })
3511            .collect();
3512
3513        if !projection_indices.is_empty() {
3514            let projected_fields: Vec<Field> = projection_indices
3515                .iter()
3516                .map(|&idx| working_schema.field(idx).clone())
3517                .collect();
3518            let projected_schema = Arc::new(Schema::new(projected_fields));
3519            let projected_columns: Vec<ArrayRef> = projection_indices
3520                .iter()
3521                .map(|&idx| Arc::clone(working_batch.column(idx)))
3522                .collect();
3523            working_batch = RecordBatch::try_new(Arc::clone(&projected_schema), projected_columns)
3524                .map_err(|e| Error::Internal(format!("failed to project columns: {}", e)))?;
3525            working_schema = projected_schema;
3526        }
3527
3528        if let Some(order_by) = &query.order_by {
3529            use arrow::compute::{SortColumn, lexsort_to_indices};
3530            use sqlparser::ast::OrderByKind;
3531
3532            let exprs = match &order_by.kind {
3533                OrderByKind::Expressions(exprs) => exprs,
3534                _ => {
3535                    return Err(Error::InvalidArgumentError(
3536                        "unsupported ORDER BY clause".into(),
3537                    ));
3538                }
3539            };
3540
3541            let mut sort_columns = Vec::new();
3542            for order_expr in exprs {
3543                if let SqlExpr::Identifier(ident) = &order_expr.expr
3544                    && let Ok(col_idx) = working_schema.index_of(&ident.value)
3545                {
3546                    let options = arrow::compute::SortOptions {
3547                        descending: !order_expr.options.asc.unwrap_or(true),
3548                        nulls_first: order_expr.options.nulls_first.unwrap_or(false),
3549                    };
3550                    sort_columns.push(SortColumn {
3551                        values: Arc::clone(working_batch.column(col_idx)),
3552                        options: Some(options),
3553                    });
3554                }
3555            }
3556
3557            if !sort_columns.is_empty() {
3558                let indices = lexsort_to_indices(&sort_columns, None)
3559                    .map_err(|e| Error::Internal(format!("failed to sort: {}", e)))?;
3560                let sorted_columns: Result<Vec<ArrayRef>, _> = working_batch
3561                    .columns()
3562                    .iter()
3563                    .map(|col| take(col.as_ref(), &indices, None))
3564                    .collect();
3565                working_batch = RecordBatch::try_new(
3566                    Arc::clone(&working_schema),
3567                    sorted_columns
3568                        .map_err(|e| Error::Internal(format!("failed to apply sort: {}", e)))?,
3569                )
3570                .map_err(|e| Error::Internal(format!("failed to create sorted batch: {}", e)))?;
3571            }
3572        }
3573
3574        if let Some(limit) = extract_limit_count(query)?
3575            && limit < working_batch.num_rows()
3576        {
3577            let indices: Vec<u32> = (0..limit as u32).collect();
3578            let take_indices = UInt32Array::from(indices);
3579            let limited_columns: Result<Vec<ArrayRef>, _> = working_batch
3580                .columns()
3581                .iter()
3582                .map(|col| take(col.as_ref(), &take_indices, None))
3583                .collect();
3584            working_batch = RecordBatch::try_new(
3585                Arc::clone(&working_schema),
3586                limited_columns
3587                    .map_err(|e| Error::Internal(format!("failed to apply limit: {}", e)))?,
3588            )
3589            .map_err(|e| Error::Internal(format!("failed to create limited batch: {}", e)))?;
3590        }
3591
3592        let execution = SelectExecution::new_single_batch(
3593            table_name.clone(),
3594            Arc::clone(&working_schema),
3595            working_batch,
3596        );
3597
3598        Ok(Some(RuntimeStatementResult::Select {
3599            table_name,
3600            schema: working_schema,
3601            execution: Box::new(execution),
3602        }))
3603    }
3604
3605    fn handle_create_table_as(
3606        &self,
3607        display_name: String,
3608        _canonical_name: String,
3609        query: Query,
3610        if_not_exists: bool,
3611        or_replace: bool,
3612        namespace: Option<String>,
3613    ) -> SqlResult<RuntimeStatementResult<P>> {
3614        // Check if this is a SELECT from VALUES in a derived table
3615        // Pattern: SELECT * FROM (VALUES ...) alias(col1, col2, ...)
3616        if let SetExpr::Select(select) = query.body.as_ref()
3617            && let Some((rows, column_names)) = extract_values_from_derived_table(&select.from)?
3618        {
3619            // Convert VALUES rows to Arrow RecordBatches
3620            return self.handle_create_table_from_values(
3621                display_name,
3622                rows,
3623                column_names,
3624                if_not_exists,
3625                or_replace,
3626                namespace,
3627            );
3628        }
3629
3630        // Regular CTAS with SELECT from existing tables
3631        let select_plan = self.build_select_plan(query)?;
3632
3633        if select_plan.projections.is_empty() && select_plan.aggregates.is_empty() {
3634            return Err(Error::InvalidArgumentError(
3635                "CREATE TABLE AS SELECT requires at least one projected column".into(),
3636            ));
3637        }
3638
3639        let plan = CreateTablePlan {
3640            name: display_name,
3641            if_not_exists,
3642            or_replace,
3643            columns: Vec::new(),
3644            source: Some(CreateTableSource::Select {
3645                plan: Box::new(select_plan),
3646            }),
3647            namespace,
3648            foreign_keys: Vec::new(),
3649            multi_column_uniques: Vec::new(),
3650        };
3651        self.execute_plan_statement(PlanStatement::CreateTable(plan))
3652    }
3653
3654    fn handle_create_table_from_values(
3655        &self,
3656        display_name: String,
3657        rows: Vec<Vec<PlanValue>>,
3658        column_names: Vec<String>,
3659        if_not_exists: bool,
3660        or_replace: bool,
3661        namespace: Option<String>,
3662    ) -> SqlResult<RuntimeStatementResult<P>> {
3663        use arrow::array::{ArrayRef, Date32Builder, Float64Builder, Int64Builder, StringBuilder};
3664        use arrow::datatypes::{DataType, Field, Schema};
3665        use arrow::record_batch::RecordBatch;
3666        use std::sync::Arc;
3667
3668        if rows.is_empty() {
3669            return Err(Error::InvalidArgumentError(
3670                "VALUES must have at least one row".into(),
3671            ));
3672        }
3673
3674        let num_cols = column_names.len();
3675
3676        // Infer schema from first row
3677        let first_row = &rows[0];
3678        if first_row.len() != num_cols {
3679            return Err(Error::InvalidArgumentError(
3680                "VALUES row column count mismatch".into(),
3681            ));
3682        }
3683
3684        let mut fields = Vec::with_capacity(num_cols);
3685        let mut column_types = Vec::with_capacity(num_cols);
3686
3687        for (idx, value) in first_row.iter().enumerate() {
3688            let (data_type, nullable) = match value {
3689                PlanValue::Integer(_) => (DataType::Int64, false),
3690                PlanValue::Float(_) => (DataType::Float64, false),
3691                PlanValue::String(_) => (DataType::Utf8, false),
3692                PlanValue::Date32(_) => (DataType::Date32, false),
3693                PlanValue::Null => (DataType::Utf8, true), // Default NULL to string type
3694                _ => {
3695                    return Err(Error::InvalidArgumentError(format!(
3696                        "unsupported value type in VALUES for column '{}'",
3697                        column_names.get(idx).unwrap_or(&format!("column{}", idx))
3698                    )));
3699                }
3700            };
3701
3702            column_types.push(data_type.clone());
3703            fields.push(Field::new(&column_names[idx], data_type, nullable));
3704        }
3705
3706        let schema = Arc::new(Schema::new(fields));
3707
3708        // Build Arrow arrays for each column
3709        let mut arrays: Vec<ArrayRef> = Vec::with_capacity(num_cols);
3710
3711        for col_idx in 0..num_cols {
3712            let col_type = &column_types[col_idx];
3713
3714            match col_type {
3715                DataType::Int64 => {
3716                    let mut builder = Int64Builder::with_capacity(rows.len());
3717                    for row in &rows {
3718                        match &row[col_idx] {
3719                            PlanValue::Integer(v) => builder.append_value(*v),
3720                            PlanValue::Null => builder.append_null(),
3721                            other => {
3722                                return Err(Error::InvalidArgumentError(format!(
3723                                    "type mismatch in VALUES: expected Integer, got {:?}",
3724                                    other
3725                                )));
3726                            }
3727                        }
3728                    }
3729                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3730                }
3731                DataType::Float64 => {
3732                    let mut builder = Float64Builder::with_capacity(rows.len());
3733                    for row in &rows {
3734                        match &row[col_idx] {
3735                            PlanValue::Float(v) => builder.append_value(*v),
3736                            PlanValue::Null => builder.append_null(),
3737                            other => {
3738                                return Err(Error::InvalidArgumentError(format!(
3739                                    "type mismatch in VALUES: expected Float, got {:?}",
3740                                    other
3741                                )));
3742                            }
3743                        }
3744                    }
3745                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3746                }
3747                DataType::Utf8 => {
3748                    let mut builder = StringBuilder::with_capacity(rows.len(), 1024);
3749                    for row in &rows {
3750                        match &row[col_idx] {
3751                            PlanValue::String(v) => builder.append_value(v),
3752                            PlanValue::Null => builder.append_null(),
3753                            other => {
3754                                return Err(Error::InvalidArgumentError(format!(
3755                                    "type mismatch in VALUES: expected String, got {:?}",
3756                                    other
3757                                )));
3758                            }
3759                        }
3760                    }
3761                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3762                }
3763                DataType::Date32 => {
3764                    let mut builder = Date32Builder::with_capacity(rows.len());
3765                    for row in &rows {
3766                        match &row[col_idx] {
3767                            PlanValue::Date32(v) => builder.append_value(*v),
3768                            PlanValue::Null => builder.append_null(),
3769                            other => {
3770                                return Err(Error::InvalidArgumentError(format!(
3771                                    "type mismatch in VALUES: expected DATE, got {:?}",
3772                                    other
3773                                )));
3774                            }
3775                        }
3776                    }
3777                    arrays.push(Arc::new(builder.finish()) as ArrayRef);
3778                }
3779                other => {
3780                    return Err(Error::InvalidArgumentError(format!(
3781                        "unsupported column type in VALUES: {:?}",
3782                        other
3783                    )));
3784                }
3785            }
3786        }
3787
3788        let batch = RecordBatch::try_new(Arc::clone(&schema), arrays).map_err(|e| {
3789            Error::Internal(format!("failed to create RecordBatch from VALUES: {}", e))
3790        })?;
3791
3792        let plan = CreateTablePlan {
3793            name: display_name.clone(),
3794            if_not_exists,
3795            or_replace,
3796            columns: Vec::new(),
3797            source: Some(CreateTableSource::Batches {
3798                schema: Arc::clone(&schema),
3799                batches: vec![batch],
3800            }),
3801            namespace,
3802            foreign_keys: Vec::new(),
3803            multi_column_uniques: Vec::new(),
3804        };
3805
3806        self.execute_plan_statement(PlanStatement::CreateTable(plan))
3807    }
3808
3809    fn handle_insert(&self, stmt: sqlparser::ast::Insert) -> SqlResult<RuntimeStatementResult<P>> {
3810        match self.prepare_insert(stmt)? {
3811            PreparedInsert::Values {
3812                table_name,
3813                columns,
3814                rows,
3815                on_conflict,
3816            } => {
3817                tracing::trace!(
3818                    "DEBUG SQL handle_insert executing buffered-values insert for table={}",
3819                    table_name
3820                );
3821                let plan = InsertPlan {
3822                    table: table_name,
3823                    columns,
3824                    source: InsertSource::Rows(rows),
3825                    on_conflict,
3826                };
3827                self.execute_plan_statement(PlanStatement::Insert(plan))
3828            }
3829            PreparedInsert::Immediate(plan) => {
3830                let table_name = plan.table.clone();
3831                tracing::trace!(
3832                    "DEBUG SQL handle_insert executing immediate insert for table={}",
3833                    table_name
3834                );
3835                self.execute_plan_statement(PlanStatement::Insert(plan))
3836            }
3837        }
3838    }
3839
3840    fn build_update_plan(
3841        &self,
3842        table: TableWithJoins,
3843        assignments: Vec<Assignment>,
3844        from: Option<UpdateTableFromKind>,
3845        selection: Option<SqlExpr>,
3846        returning: Option<Vec<SelectItem>>,
3847    ) -> SqlResult<UpdatePlan> {
3848        if from.is_some() {
3849            return Err(Error::InvalidArgumentError(
3850                "UPDATE ... FROM is not supported yet".into(),
3851            ));
3852        }
3853        if returning.is_some() {
3854            return Err(Error::InvalidArgumentError(
3855                "UPDATE ... RETURNING is not supported".into(),
3856            ));
3857        }
3858        if assignments.is_empty() {
3859            return Err(Error::InvalidArgumentError(
3860                "UPDATE requires at least one assignment".into(),
3861            ));
3862        }
3863
3864        let (display_name, canonical_name) = extract_single_table(std::slice::from_ref(&table))?;
3865
3866        if !self.engine.session().has_active_transaction()
3867            && self
3868                .engine
3869                .context()
3870                .is_table_marked_dropped(&canonical_name)
3871        {
3872            return Err(Error::TransactionContextError(
3873                DROPPED_TABLE_TRANSACTION_ERR.into(),
3874            ));
3875        }
3876
3877        let catalog = self.engine.context().table_catalog();
3878        let resolver = catalog.identifier_resolver();
3879        let table_id = catalog.table_id(&canonical_name);
3880
3881        // Use a HashMap to track column assignments. If a column appears multiple times,
3882        // the last assignment wins (SQLite-compatible behavior).
3883        let mut assignments_map: FxHashMap<String, (String, sqlparser::ast::Expr)> =
3884            FxHashMap::with_capacity_and_hasher(assignments.len(), FxBuildHasher);
3885        for assignment in assignments {
3886            let column_name = resolve_assignment_column_name(&assignment.target)?;
3887            let normalized = column_name.to_ascii_lowercase();
3888            // Store in map - last assignment wins
3889            assignments_map.insert(normalized, (column_name, assignment.value.clone()));
3890        }
3891
3892        let mut column_assignments = Vec::with_capacity(assignments_map.len());
3893        for (_normalized, (column_name, expr)) in assignments_map {
3894            let value = match SqlValue::try_from_expr(&expr) {
3895                Ok(literal) => AssignmentValue::Literal(PlanValue::from(literal)),
3896                Err(Error::InvalidArgumentError(msg))
3897                    if msg.contains("unsupported literal expression") =>
3898                {
3899                    let normalized_expr = self.materialize_in_subquery(
3900                        expr.clone(),
3901                        SubqueryMaterializationMode::ExecuteScalarSubqueries,
3902                    )?;
3903                    let translated = translate_scalar_with_context(
3904                        &resolver,
3905                        IdentifierContext::new(table_id),
3906                        &normalized_expr,
3907                    )?;
3908                    AssignmentValue::Expression(translated)
3909                }
3910                Err(err) => return Err(err),
3911            };
3912            column_assignments.push(ColumnAssignment {
3913                column: column_name,
3914                value,
3915            });
3916        }
3917
3918        let filter = match selection {
3919            Some(expr) => {
3920                let materialized_expr = self.materialize_in_subquery(
3921                    expr,
3922                    SubqueryMaterializationMode::ExecuteScalarSubqueries,
3923                )?;
3924                let mut subqueries = Vec::new();
3925                let mut scalar_subqueries = Vec::new();
3926                let predicate = translate_condition_with_context(
3927                    self,
3928                    &resolver,
3929                    IdentifierContext::new(table_id),
3930                    &materialized_expr,
3931                    &[],
3932                    &mut subqueries,
3933                    &mut scalar_subqueries,
3934                    None,
3935                )?;
3936                if !subqueries.is_empty() {
3937                    return Err(Error::InvalidArgumentError(
3938                        "EXISTS subqueries are not supported in UPDATE WHERE clauses".into(),
3939                    ));
3940                }
3941                if !scalar_subqueries.is_empty() {
3942                    return Err(Error::InvalidArgumentError(
3943                        "scalar subqueries are not supported in UPDATE WHERE clauses".into(),
3944                    ));
3945                }
3946                Some(predicate)
3947            }
3948            None => None,
3949        };
3950
3951        let plan = UpdatePlan {
3952            table: display_name.clone(),
3953            assignments: column_assignments,
3954            filter,
3955        };
3956        Ok(plan)
3957    }
3958
3959    fn handle_update(
3960        &self,
3961        table: TableWithJoins,
3962        assignments: Vec<Assignment>,
3963        from: Option<UpdateTableFromKind>,
3964        selection: Option<SqlExpr>,
3965        returning: Option<Vec<SelectItem>>,
3966    ) -> SqlResult<RuntimeStatementResult<P>> {
3967        let plan = self.build_update_plan(table, assignments, from, selection, returning)?;
3968        self.execute_plan_statement(PlanStatement::Update(plan))
3969    }
3970
3971    #[allow(clippy::collapsible_if)]
3972    fn handle_delete(&self, delete: Delete) -> SqlResult<RuntimeStatementResult<P>> {
3973        let Delete {
3974            tables,
3975            from,
3976            using,
3977            selection,
3978            returning,
3979            order_by,
3980            limit,
3981        } = delete;
3982
3983        if !tables.is_empty() {
3984            return Err(Error::InvalidArgumentError(
3985                "multi-table DELETE is not supported yet".into(),
3986            ));
3987        }
3988        if let Some(using_tables) = using {
3989            if !using_tables.is_empty() {
3990                return Err(Error::InvalidArgumentError(
3991                    "DELETE ... USING is not supported yet".into(),
3992                ));
3993            }
3994        }
3995        if returning.is_some() {
3996            return Err(Error::InvalidArgumentError(
3997                "DELETE ... RETURNING is not supported".into(),
3998            ));
3999        }
4000        if !order_by.is_empty() {
4001            return Err(Error::InvalidArgumentError(
4002                "DELETE ... ORDER BY is not supported yet".into(),
4003            ));
4004        }
4005        if limit.is_some() {
4006            return Err(Error::InvalidArgumentError(
4007                "DELETE ... LIMIT is not supported yet".into(),
4008            ));
4009        }
4010
4011        let from_tables = match from {
4012            FromTable::WithFromKeyword(tables) | FromTable::WithoutKeyword(tables) => tables,
4013        };
4014        let (display_name, canonical_name) = extract_single_table(&from_tables)?;
4015
4016        if !self.engine.session().has_active_transaction()
4017            && self
4018                .engine
4019                .context()
4020                .is_table_marked_dropped(&canonical_name)
4021        {
4022            return Err(Error::TransactionContextError(
4023                DROPPED_TABLE_TRANSACTION_ERR.into(),
4024            ));
4025        }
4026
4027        let catalog = self.engine.context().table_catalog();
4028        let resolver = catalog.identifier_resolver();
4029        let table_id = catalog.table_id(&canonical_name);
4030
4031        let filter = if let Some(expr) = selection {
4032            let materialized_expr = self.materialize_in_subquery(
4033                expr,
4034                SubqueryMaterializationMode::ExecuteScalarSubqueries,
4035            )?;
4036            let mut subqueries = Vec::new();
4037            let mut scalar_subqueries = Vec::new();
4038            let predicate = translate_condition_with_context(
4039                self,
4040                &resolver,
4041                IdentifierContext::new(table_id),
4042                &materialized_expr,
4043                &[],
4044                &mut subqueries,
4045                &mut scalar_subqueries,
4046                None,
4047            )?;
4048            if !subqueries.is_empty() {
4049                return Err(Error::InvalidArgumentError(
4050                    "EXISTS subqueries are not supported in DELETE WHERE clauses".into(),
4051                ));
4052            }
4053            if !scalar_subqueries.is_empty() {
4054                return Err(Error::InvalidArgumentError(
4055                    "scalar subqueries are not supported in DELETE WHERE clauses".into(),
4056                ));
4057            }
4058            Some(predicate)
4059        } else {
4060            None
4061        };
4062
4063        let plan = DeletePlan {
4064            table: display_name.clone(),
4065            filter,
4066        };
4067        self.execute_plan_statement(PlanStatement::Delete(plan))
4068    }
4069
4070    fn handle_truncate(
4071        &self,
4072        table_names: &[sqlparser::ast::TruncateTableTarget],
4073        partitions: &Option<Vec<SqlExpr>>,
4074        _table: bool, // boolean field in sqlparser, not the table name
4075        identity: &Option<sqlparser::ast::TruncateIdentityOption>,
4076        cascade: Option<sqlparser::ast::CascadeOption>,
4077        on_cluster: &Option<Ident>,
4078    ) -> SqlResult<RuntimeStatementResult<P>> {
4079        // Validate unsupported features
4080        if table_names.len() > 1 {
4081            return Err(Error::InvalidArgumentError(
4082                "TRUNCATE with multiple tables is not supported yet".into(),
4083            ));
4084        }
4085        if partitions.is_some() {
4086            return Err(Error::InvalidArgumentError(
4087                "TRUNCATE ... PARTITION is not supported".into(),
4088            ));
4089        }
4090        if identity.is_some() {
4091            return Err(Error::InvalidArgumentError(
4092                "TRUNCATE ... RESTART/CONTINUE IDENTITY is not supported".into(),
4093            ));
4094        }
4095        use sqlparser::ast::CascadeOption;
4096        if matches!(cascade, Some(CascadeOption::Cascade)) {
4097            return Err(Error::InvalidArgumentError(
4098                "TRUNCATE ... CASCADE is not supported".into(),
4099            ));
4100        }
4101        if on_cluster.is_some() {
4102            return Err(Error::InvalidArgumentError(
4103                "TRUNCATE ... ON CLUSTER is not supported".into(),
4104            ));
4105        }
4106
4107        // Extract table name from table_names
4108        let table_name = if let Some(target) = table_names.first() {
4109            // TruncateTableTarget has a name field
4110            let table_obj = &target.name;
4111            let display_name = table_obj.to_string();
4112            let canonical_name = display_name.to_ascii_lowercase();
4113
4114            // Check if table is dropped in transaction
4115            if !self.engine.session().has_active_transaction()
4116                && self
4117                    .engine
4118                    .context()
4119                    .is_table_marked_dropped(&canonical_name)
4120            {
4121                return Err(Error::TransactionContextError(
4122                    DROPPED_TABLE_TRANSACTION_ERR.into(),
4123                ));
4124            }
4125
4126            display_name
4127        } else {
4128            return Err(Error::InvalidArgumentError(
4129                "TRUNCATE requires a table name".into(),
4130            ));
4131        };
4132
4133        let plan = TruncatePlan {
4134            table: table_name.clone(),
4135        };
4136        self.execute_plan_statement(PlanStatement::Truncate(plan))
4137    }
4138
4139    #[allow(clippy::too_many_arguments)] // NOTE: Signature mirrors SQL grammar; keep grouped until command builder is introduced.
4140    fn handle_drop(
4141        &self,
4142        object_type: ObjectType,
4143        if_exists: bool,
4144        names: Vec<ObjectName>,
4145        cascade: bool,
4146        restrict: bool,
4147        purge: bool,
4148        temporary: bool,
4149    ) -> SqlResult<RuntimeStatementResult<P>> {
4150        if purge || temporary {
4151            return Err(Error::InvalidArgumentError(
4152                "DROP purge/temporary options are not supported".into(),
4153            ));
4154        }
4155
4156        match object_type {
4157            ObjectType::Table => {
4158                if cascade || restrict {
4159                    return Err(Error::InvalidArgumentError(
4160                        "DROP TABLE CASCADE/RESTRICT is not supported".into(),
4161                    ));
4162                }
4163
4164                for name in names {
4165                    let table_name = Self::object_name_to_string(&name)?;
4166                    let mut plan = llkv_plan::DropTablePlan::new(table_name.clone());
4167                    plan.if_exists = if_exists;
4168
4169                    self.execute_plan_statement(llkv_plan::PlanStatement::DropTable(plan))
4170                        .map_err(|err| Self::map_table_error(&table_name, err))?;
4171                }
4172
4173                Ok(RuntimeStatementResult::NoOp)
4174            }
4175            ObjectType::View => {
4176                if cascade || restrict {
4177                    return Err(Error::InvalidArgumentError(
4178                        "DROP VIEW CASCADE/RESTRICT is not supported".into(),
4179                    ));
4180                }
4181
4182                for name in names {
4183                    let view_name = Self::object_name_to_string(&name)?;
4184                    let plan = llkv_plan::DropViewPlan::new(view_name).if_exists(if_exists);
4185                    self.execute_plan_statement(llkv_plan::PlanStatement::DropView(plan))?;
4186                }
4187
4188                Ok(RuntimeStatementResult::NoOp)
4189            }
4190            ObjectType::Index => {
4191                if cascade || restrict {
4192                    return Err(Error::InvalidArgumentError(
4193                        "DROP INDEX CASCADE/RESTRICT is not supported".into(),
4194                    ));
4195                }
4196
4197                for name in names {
4198                    let index_name = Self::object_name_to_string(&name)?;
4199                    let plan = llkv_plan::DropIndexPlan::new(index_name).if_exists(if_exists);
4200                    self.execute_plan_statement(llkv_plan::PlanStatement::DropIndex(plan))?;
4201                }
4202
4203                Ok(RuntimeStatementResult::NoOp)
4204            }
4205            ObjectType::Schema => {
4206                if restrict {
4207                    return Err(Error::InvalidArgumentError(
4208                        "DROP SCHEMA RESTRICT is not supported".into(),
4209                    ));
4210                }
4211
4212                let catalog = self.engine.context().table_catalog();
4213
4214                for name in names {
4215                    let (display_name, canonical_name) = canonical_object_name(&name)?;
4216
4217                    if !catalog.schema_exists(&canonical_name) {
4218                        if if_exists {
4219                            continue;
4220                        }
4221                        return Err(Error::CatalogError(format!(
4222                            "Schema '{}' does not exist",
4223                            display_name
4224                        )));
4225                    }
4226
4227                    if cascade {
4228                        // Drop all tables in this schema
4229                        let all_tables = catalog.table_names();
4230                        let schema_prefix = format!("{}.", canonical_name);
4231
4232                        for table in all_tables {
4233                            if table.to_ascii_lowercase().starts_with(&schema_prefix) {
4234                                let mut plan = llkv_plan::DropTablePlan::new(table.clone());
4235                                plan.if_exists = false;
4236                                self.execute_plan_statement(llkv_plan::PlanStatement::DropTable(
4237                                    plan,
4238                                ))?;
4239                            }
4240                        }
4241                    } else {
4242                        // Check if schema has any tables
4243                        let all_tables = catalog.table_names();
4244                        let schema_prefix = format!("{}.", canonical_name);
4245                        let has_tables = all_tables
4246                            .iter()
4247                            .any(|t| t.to_ascii_lowercase().starts_with(&schema_prefix));
4248
4249                        if has_tables {
4250                            return Err(Error::CatalogError(format!(
4251                                "Schema '{}' is not empty. Use CASCADE to drop schema and all its tables",
4252                                display_name
4253                            )));
4254                        }
4255                    }
4256
4257                    // Drop the schema
4258                    if !catalog.unregister_schema(&canonical_name) && !if_exists {
4259                        return Err(Error::CatalogError(format!(
4260                            "Schema '{}' does not exist",
4261                            display_name
4262                        )));
4263                    }
4264                }
4265
4266                Ok(RuntimeStatementResult::NoOp)
4267            }
4268            _ => Err(Error::InvalidArgumentError(format!(
4269                "DROP {} is not supported",
4270                object_type
4271            ))),
4272        }
4273    }
4274
4275    fn handle_alter_table(
4276        &self,
4277        name: ObjectName,
4278        if_exists: bool,
4279        only: bool,
4280        operations: Vec<AlterTableOperation>,
4281    ) -> SqlResult<RuntimeStatementResult<P>> {
4282        if only {
4283            return Err(Error::InvalidArgumentError(
4284                "ALTER TABLE ONLY is not supported yet".into(),
4285            ));
4286        }
4287
4288        if operations.is_empty() {
4289            return Ok(RuntimeStatementResult::NoOp);
4290        }
4291
4292        if operations.len() != 1 {
4293            return Err(Error::InvalidArgumentError(
4294                "ALTER TABLE currently supports exactly one operation".into(),
4295            ));
4296        }
4297
4298        let operation = operations.into_iter().next().expect("checked length");
4299        match operation {
4300            AlterTableOperation::RenameTable { table_name } => {
4301                let new_name_raw = table_name.to_string();
4302                // Strip "TO " prefix if present (sqlparser may include it)
4303                let new_name = new_name_raw
4304                    .strip_prefix("TO ")
4305                    .or_else(|| new_name_raw.strip_prefix("to "))
4306                    .unwrap_or(&new_name_raw)
4307                    .to_string();
4308                self.handle_alter_table_rename(name, new_name, if_exists)
4309            }
4310            AlterTableOperation::RenameColumn {
4311                old_column_name,
4312                new_column_name,
4313            } => {
4314                let plan = llkv_plan::AlterTablePlan {
4315                    table_name: name.to_string(),
4316                    if_exists,
4317                    operation: llkv_plan::AlterTableOperation::RenameColumn {
4318                        old_column_name: old_column_name.to_string(),
4319                        new_column_name: new_column_name.to_string(),
4320                    },
4321                };
4322                self.execute_plan_statement(PlanStatement::AlterTable(plan))
4323            }
4324            AlterTableOperation::AlterColumn { column_name, op } => {
4325                // Only support SET DATA TYPE for now
4326                if let AlterColumnOperation::SetDataType {
4327                    data_type,
4328                    using,
4329                    had_set: _,
4330                } = op
4331                {
4332                    if using.is_some() {
4333                        return Err(Error::InvalidArgumentError(
4334                            "ALTER COLUMN SET DATA TYPE USING clause is not yet supported".into(),
4335                        ));
4336                    }
4337
4338                    let plan = llkv_plan::AlterTablePlan {
4339                        table_name: name.to_string(),
4340                        if_exists,
4341                        operation: llkv_plan::AlterTableOperation::SetColumnDataType {
4342                            column_name: column_name.to_string(),
4343                            new_data_type: data_type.to_string(),
4344                        },
4345                    };
4346                    self.execute_plan_statement(PlanStatement::AlterTable(plan))
4347                } else {
4348                    Err(Error::InvalidArgumentError(format!(
4349                        "unsupported ALTER COLUMN operation: {:?}",
4350                        op
4351                    )))
4352                }
4353            }
4354            AlterTableOperation::DropColumn {
4355                has_column_keyword: _,
4356                column_names,
4357                if_exists: column_if_exists,
4358                drop_behavior,
4359            } => {
4360                if column_names.len() != 1 {
4361                    return Err(Error::InvalidArgumentError(
4362                        "DROP COLUMN currently supports dropping one column at a time".into(),
4363                    ));
4364                }
4365
4366                let column_name = column_names.into_iter().next().unwrap().to_string();
4367                let cascade = matches!(drop_behavior, Some(sqlparser::ast::DropBehavior::Cascade));
4368
4369                let plan = llkv_plan::AlterTablePlan {
4370                    table_name: name.to_string(),
4371                    if_exists,
4372                    operation: llkv_plan::AlterTableOperation::DropColumn {
4373                        column_name,
4374                        if_exists: column_if_exists,
4375                        cascade,
4376                    },
4377                };
4378                self.execute_plan_statement(PlanStatement::AlterTable(plan))
4379            }
4380            other => Err(Error::InvalidArgumentError(format!(
4381                "unsupported ALTER TABLE operation: {:?}",
4382                other
4383            ))),
4384        }
4385    }
4386
4387    fn handle_alter_table_rename(
4388        &self,
4389        original_name: ObjectName,
4390        new_table_name: String,
4391        if_exists: bool,
4392    ) -> SqlResult<RuntimeStatementResult<P>> {
4393        let (schema_opt, table_name) = parse_schema_qualified_name(&original_name)?;
4394
4395        let new_table_name_clean = new_table_name.trim();
4396
4397        if new_table_name_clean.is_empty() {
4398            return Err(Error::InvalidArgumentError(
4399                "ALTER TABLE RENAME requires a non-empty table name".into(),
4400            ));
4401        }
4402
4403        let (raw_new_schema_opt, raw_new_table) =
4404            if let Some((schema_part, table_part)) = new_table_name_clean.split_once('.') {
4405                (
4406                    Some(schema_part.trim().to_string()),
4407                    table_part.trim().to_string(),
4408                )
4409            } else {
4410                (None, new_table_name_clean.to_string())
4411            };
4412
4413        if schema_opt.is_none() && raw_new_schema_opt.is_some() {
4414            return Err(Error::InvalidArgumentError(
4415                "ALTER TABLE RENAME cannot add a schema qualifier".into(),
4416            ));
4417        }
4418
4419        let new_table_trimmed = raw_new_table.trim_matches('"');
4420        if new_table_trimmed.is_empty() {
4421            return Err(Error::InvalidArgumentError(
4422                "ALTER TABLE RENAME requires a non-empty table name".into(),
4423            ));
4424        }
4425
4426        if let (Some(existing_schema), Some(new_schema_raw)) =
4427            (schema_opt.as_ref(), raw_new_schema_opt.as_ref())
4428        {
4429            let new_schema_trimmed = new_schema_raw.trim_matches('"');
4430            if !existing_schema.eq_ignore_ascii_case(new_schema_trimmed) {
4431                return Err(Error::InvalidArgumentError(
4432                    "ALTER TABLE RENAME cannot change table schema".into(),
4433                ));
4434            }
4435        }
4436
4437        let new_table_display = raw_new_table;
4438        let new_schema_opt = raw_new_schema_opt;
4439
4440        fn join_schema_table(schema: &str, table: &str) -> String {
4441            let mut qualified = String::with_capacity(schema.len() + table.len() + 1);
4442            qualified.push_str(schema);
4443            qualified.push('.');
4444            qualified.push_str(table);
4445            qualified
4446        }
4447
4448        let current_display = schema_opt
4449            .as_ref()
4450            .map(|schema| join_schema_table(schema, &table_name))
4451            .unwrap_or_else(|| table_name.clone());
4452
4453        let new_display = if let Some(new_schema_raw) = new_schema_opt.clone() {
4454            join_schema_table(&new_schema_raw, &new_table_display)
4455        } else if let Some(schema) = schema_opt.as_ref() {
4456            join_schema_table(schema, &new_table_display)
4457        } else {
4458            new_table_display.clone()
4459        };
4460
4461        let plan = RenameTablePlan::new(&current_display, &new_display).if_exists(if_exists);
4462
4463        match CatalogDdl::rename_table(self.engine.session(), plan) {
4464            Ok(()) => {
4465                // Invalidate information schema after successful rename
4466                self.invalidate_information_schema();
4467                Ok(RuntimeStatementResult::NoOp)
4468            }
4469            Err(err) => Err(Self::map_table_error(&current_display, err)),
4470        }
4471    }
4472
4473    /// Materialize IN (SELECT ...) subqueries by executing them and converting to IN lists.
4474    ///
4475    /// This preprocesses SQL expressions to execute subqueries and replace them with
4476    /// their materialized results before translation to the execution plan.
4477    ///
4478    /// Uses iterative traversal with an explicit work stack to handle deeply nested
4479    /// expressions without stack overflow.
4480    fn try_materialize_avg_subquery(&self, query: &Query) -> SqlResult<Option<SqlExpr>> {
4481        use sqlparser::ast::{
4482            DuplicateTreatment, FunctionArg, FunctionArgExpr, FunctionArguments, ObjectName,
4483            ObjectNamePart, SelectItem, SetExpr,
4484        };
4485
4486        let select = match query.body.as_ref() {
4487            SetExpr::Select(select) => select.as_ref(),
4488            _ => return Ok(None),
4489        };
4490
4491        if select.projection.len() != 1
4492            || select.distinct.is_some()
4493            || select.top.is_some()
4494            || select.value_table_mode.is_some()
4495            || select.having.is_some()
4496            || !group_by_is_empty(&select.group_by)
4497            || select.into.is_some()
4498            || !select.lateral_views.is_empty()
4499        {
4500            return Ok(None);
4501        }
4502
4503        let func = match &select.projection[0] {
4504            SelectItem::UnnamedExpr(SqlExpr::Function(func)) => func,
4505            _ => return Ok(None),
4506        };
4507
4508        if func.uses_odbc_syntax
4509            || func.filter.is_some()
4510            || func.null_treatment.is_some()
4511            || func.over.is_some()
4512            || !func.within_group.is_empty()
4513        {
4514            return Ok(None);
4515        }
4516
4517        let func_name = func.name.to_string().to_ascii_lowercase();
4518        if func_name != "avg" {
4519            return Ok(None);
4520        }
4521
4522        let args = match &func.args {
4523            FunctionArguments::List(list) => {
4524                if matches!(list.duplicate_treatment, Some(DuplicateTreatment::Distinct))
4525                    || !list.clauses.is_empty()
4526                {
4527                    return Ok(None);
4528                }
4529                &list.args
4530            }
4531            _ => return Ok(None),
4532        };
4533
4534        if args.len() != 1 {
4535            return Ok(None);
4536        }
4537
4538        match &args[0] {
4539            FunctionArg::Unnamed(FunctionArgExpr::Expr(_)) => {}
4540            _ => return Ok(None),
4541        };
4542
4543        let mut sum_query = query.clone();
4544        let mut count_query = query.clone();
4545
4546        let build_replacement = |target_query: &mut Query, name: &str| -> SqlResult<()> {
4547            let select = match target_query.body.as_mut() {
4548                SetExpr::Select(select) => select,
4549                _ => {
4550                    return Err(Error::Internal(
4551                        "expected SELECT query in AVG materialization".into(),
4552                    ));
4553                }
4554            };
4555
4556            let mut replacement_func = func.clone();
4557            replacement_func.name = ObjectName(vec![ObjectNamePart::Identifier(Ident {
4558                value: name.to_string(),
4559                quote_style: None,
4560                span: Span::empty(),
4561            })]);
4562            select.projection = vec![SelectItem::UnnamedExpr(SqlExpr::Function(replacement_func))];
4563            Ok(())
4564        };
4565
4566        build_replacement(&mut sum_query, "sum")?;
4567        build_replacement(&mut count_query, "count")?;
4568
4569        let sum_value = self.execute_scalar_int64(sum_query)?;
4570        let count_value = self.execute_scalar_int64(count_query)?;
4571
4572        let Some(count_value) = count_value else {
4573            return Ok(Some(SqlExpr::Value(ValueWithSpan {
4574                value: Value::Null,
4575                span: Span::empty(),
4576            })));
4577        };
4578
4579        if count_value == 0 {
4580            return Ok(Some(SqlExpr::Value(ValueWithSpan {
4581                value: Value::Null,
4582                span: Span::empty(),
4583            })));
4584        }
4585
4586        let sum_value = match sum_value {
4587            Some(value) => value,
4588            None => {
4589                return Ok(Some(SqlExpr::Value(ValueWithSpan {
4590                    value: Value::Null,
4591                    span: Span::empty(),
4592                })));
4593            }
4594        };
4595
4596        let avg = (sum_value as f64) / (count_value as f64);
4597        let value = ValueWithSpan {
4598            value: Value::Number(avg.to_string(), false),
4599            span: Span::empty(),
4600        };
4601        Ok(Some(SqlExpr::Value(value)))
4602    }
4603
4604    fn execute_scalar_int64(&self, query: Query) -> SqlResult<Option<i64>> {
4605        let result = self.handle_query(query)?;
4606        let execution = match result {
4607            RuntimeStatementResult::Select { execution, .. } => execution,
4608            _ => {
4609                return Err(Error::InvalidArgumentError(
4610                    "scalar aggregate must be a SELECT statement".into(),
4611                ));
4612            }
4613        };
4614
4615        let batches = execution.collect()?;
4616        let mut captured: Option<Option<i64>> = None;
4617
4618        for batch in batches {
4619            if batch.num_columns() == 0 {
4620                continue;
4621            }
4622            if batch.num_columns() != 1 {
4623                return Err(Error::InvalidArgumentError(
4624                    "scalar aggregate must return exactly one column".into(),
4625                ));
4626            }
4627
4628            let array = batch.column(0);
4629            let values = array
4630                .as_any()
4631                .downcast_ref::<arrow::array::Int64Array>()
4632                .ok_or_else(|| {
4633                    Error::InvalidArgumentError(
4634                        "scalar aggregate result must be an INT64 value".into(),
4635                    )
4636                })?;
4637
4638            for idx in 0..values.len() {
4639                if captured.is_some() {
4640                    return Err(Error::InvalidArgumentError(
4641                        "scalar aggregate returned more than one row".into(),
4642                    ));
4643                }
4644                if values.is_null(idx) {
4645                    captured = Some(None);
4646                } else {
4647                    captured = Some(Some(values.value(idx)));
4648                }
4649            }
4650        }
4651
4652        Ok(captured.unwrap_or(None))
4653    }
4654
4655    fn materialize_scalar_subquery(&self, subquery: Query) -> SqlResult<SqlExpr> {
4656        if let Some(avg_literal) = self.try_materialize_avg_subquery(&subquery)? {
4657            return Ok(avg_literal);
4658        }
4659
4660        let result = self.handle_query(subquery)?;
4661        let execution = match result {
4662            RuntimeStatementResult::Select { execution, .. } => execution,
4663            _ => {
4664                return Err(Error::InvalidArgumentError(
4665                    "scalar subquery must be a SELECT statement".into(),
4666                ));
4667            }
4668        };
4669
4670        let batches = execution.collect()?;
4671        let mut captured_value: Option<ValueWithSpan> = None;
4672
4673        for batch in batches {
4674            if batch.num_columns() == 0 {
4675                continue;
4676            }
4677            if batch.num_columns() != 1 {
4678                return Err(Error::InvalidArgumentError(
4679                    "scalar subquery must return exactly one column".into(),
4680                ));
4681            }
4682
4683            let column = batch.column(0);
4684            for row_idx in 0..batch.num_rows() {
4685                if captured_value.is_some() {
4686                    return Err(Error::InvalidArgumentError(
4687                        "scalar subquery returned more than one row".into(),
4688                    ));
4689                }
4690
4691                let value = if column.is_null(row_idx) {
4692                    Value::Null
4693                } else {
4694                    use arrow::array::{BooleanArray, Float64Array, Int64Array, StringArray};
4695                    match column.data_type() {
4696                        arrow::datatypes::DataType::Int64 => {
4697                            let array =
4698                                column
4699                                    .as_any()
4700                                    .downcast_ref::<Int64Array>()
4701                                    .ok_or_else(|| {
4702                                        Error::Internal(
4703                                            "expected Int64 array for scalar subquery".into(),
4704                                        )
4705                                    })?;
4706                            Value::Number(array.value(row_idx).to_string(), false)
4707                        }
4708                        arrow::datatypes::DataType::Float64 => {
4709                            let array = column.as_any().downcast_ref::<Float64Array>().ok_or_else(
4710                                || {
4711                                    Error::Internal(
4712                                        "expected Float64 array for scalar subquery".into(),
4713                                    )
4714                                },
4715                            )?;
4716                            Value::Number(array.value(row_idx).to_string(), false)
4717                        }
4718                        arrow::datatypes::DataType::Utf8 => {
4719                            let array =
4720                                column
4721                                    .as_any()
4722                                    .downcast_ref::<StringArray>()
4723                                    .ok_or_else(|| {
4724                                        Error::Internal(
4725                                            "expected String array for scalar subquery".into(),
4726                                        )
4727                                    })?;
4728                            Value::SingleQuotedString(array.value(row_idx).to_string())
4729                        }
4730                        arrow::datatypes::DataType::Boolean => {
4731                            let array = column.as_any().downcast_ref::<BooleanArray>().ok_or_else(
4732                                || {
4733                                    Error::Internal(
4734                                        "expected Boolean array for scalar subquery".into(),
4735                                    )
4736                                },
4737                            )?;
4738                            Value::Boolean(array.value(row_idx))
4739                        }
4740                        other => {
4741                            return Err(Error::InvalidArgumentError(format!(
4742                                "unsupported data type in scalar subquery result: {other:?}"
4743                            )));
4744                        }
4745                    }
4746                };
4747
4748                captured_value = Some(ValueWithSpan {
4749                    value,
4750                    span: Span::empty(),
4751                });
4752            }
4753        }
4754
4755        let final_value = captured_value.unwrap_or(ValueWithSpan {
4756            value: Value::Null,
4757            span: Span::empty(),
4758        });
4759        Ok(SqlExpr::Value(final_value))
4760    }
4761
4762    fn materialize_in_subquery(
4763        &self,
4764        root_expr: SqlExpr,
4765        mode: SubqueryMaterializationMode,
4766    ) -> SqlResult<SqlExpr> {
4767        // Stack-based iterative traversal to avoid recursion
4768        enum WorkItem {
4769            Process(Box<SqlExpr>),
4770            BuildBinaryOp {
4771                op: BinaryOperator,
4772                left: Box<SqlExpr>,
4773                right_done: bool,
4774            },
4775            BuildUnaryOp {
4776                op: UnaryOperator,
4777            },
4778            BuildNested,
4779            BuildIsNull,
4780            BuildIsNotNull,
4781            FinishBetween {
4782                negated: bool,
4783            },
4784        }
4785
4786        let mut work_stack: Vec<WorkItem> = vec![WorkItem::Process(Box::new(root_expr))];
4787        let mut result_stack: Vec<SqlExpr> = Vec::new();
4788
4789        while let Some(item) = work_stack.pop() {
4790            match item {
4791                WorkItem::Process(expr) => {
4792                    match *expr {
4793                        SqlExpr::InSubquery {
4794                            expr: left_expr,
4795                            subquery,
4796                            negated,
4797                        } => {
4798                            // Execute the subquery
4799                            let result = self.handle_query(*subquery)?;
4800
4801                            // Extract values from first column
4802                            let values = match result {
4803                                RuntimeStatementResult::Select { execution, .. } => {
4804                                    let batches = execution.collect()?;
4805                                    let mut collected_values = Vec::new();
4806
4807                                    for batch in batches {
4808                                        if batch.num_columns() == 0 {
4809                                            continue;
4810                                        }
4811                                        if batch.num_columns() > 1 {
4812                                            return Err(Error::InvalidArgumentError(format!(
4813                                                "IN subquery must return exactly one column, got {}",
4814                                                batch.num_columns()
4815                                            )));
4816                                        }
4817                                        let column = batch.column(0);
4818
4819                                        for row_idx in 0..column.len() {
4820                                            use arrow::datatypes::DataType;
4821                                            let value = if column.is_null(row_idx) {
4822                                                Value::Null
4823                                            } else {
4824                                                match column.data_type() {
4825                                                    DataType::Int64 => {
4826                                                        let arr = column
4827                                                        .as_any()
4828                                                        .downcast_ref::<arrow::array::Int64Array>()
4829                                                        .unwrap();
4830                                                        Value::Number(
4831                                                            arr.value(row_idx).to_string(),
4832                                                            false,
4833                                                        )
4834                                                    }
4835                                                    DataType::Float64 => {
4836                                                        let arr = column
4837                                                        .as_any()
4838                                                        .downcast_ref::<arrow::array::Float64Array>()
4839                                                        .unwrap();
4840                                                        Value::Number(
4841                                                            arr.value(row_idx).to_string(),
4842                                                            false,
4843                                                        )
4844                                                    }
4845                                                    DataType::Utf8 => {
4846                                                        let arr = column
4847                                                        .as_any()
4848                                                        .downcast_ref::<arrow::array::StringArray>()
4849                                                        .unwrap();
4850                                                        Value::SingleQuotedString(
4851                                                            arr.value(row_idx).to_string(),
4852                                                        )
4853                                                    }
4854                                                    DataType::Boolean => {
4855                                                        let arr = column
4856                                                        .as_any()
4857                                                        .downcast_ref::<arrow::array::BooleanArray>()
4858                                                        .unwrap();
4859                                                        Value::Boolean(arr.value(row_idx))
4860                                                    }
4861                                                    other => {
4862                                                        return Err(Error::InvalidArgumentError(
4863                                                            format!(
4864                                                                "unsupported data type in IN subquery: {other:?}"
4865                                                            ),
4866                                                        ));
4867                                                    }
4868                                                }
4869                                            };
4870                                            collected_values.push(ValueWithSpan {
4871                                                value,
4872                                                span: Span::empty(),
4873                                            });
4874                                        }
4875                                    }
4876
4877                                    collected_values
4878                                }
4879                                _ => {
4880                                    return Err(Error::InvalidArgumentError(
4881                                        "IN subquery must be a SELECT statement".into(),
4882                                    ));
4883                                }
4884                            };
4885
4886                            // Convert to IN list with materialized values
4887                            result_stack.push(SqlExpr::InList {
4888                                expr: left_expr,
4889                                list: values.into_iter().map(SqlExpr::Value).collect(),
4890                                negated,
4891                            });
4892                        }
4893                        SqlExpr::Subquery(subquery) => {
4894                            if mode.executes_scalar_subqueries() {
4895                                let scalar_expr = self.materialize_scalar_subquery(*subquery)?;
4896                                result_stack.push(scalar_expr);
4897                            } else {
4898                                result_stack.push(SqlExpr::Subquery(subquery));
4899                            }
4900                        }
4901                        SqlExpr::Case {
4902                            case_token,
4903                            end_token,
4904                            operand,
4905                            conditions,
4906                            else_result,
4907                        } => {
4908                            let new_operand = match operand {
4909                                Some(expr) => {
4910                                    Some(Box::new(self.materialize_in_subquery(*expr, mode)?))
4911                                }
4912                                None => None,
4913                            };
4914                            let mut new_conditions = Vec::with_capacity(conditions.len());
4915                            for branch in conditions {
4916                                let condition =
4917                                    self.materialize_in_subquery(branch.condition, mode)?;
4918                                let result = self.materialize_in_subquery(branch.result, mode)?;
4919                                new_conditions.push(sqlparser::ast::CaseWhen { condition, result });
4920                            }
4921                            let new_else = match else_result {
4922                                Some(expr) => {
4923                                    Some(Box::new(self.materialize_in_subquery(*expr, mode)?))
4924                                }
4925                                None => None,
4926                            };
4927                            result_stack.push(SqlExpr::Case {
4928                                case_token,
4929                                end_token,
4930                                operand: new_operand,
4931                                conditions: new_conditions,
4932                                else_result: new_else,
4933                            });
4934                        }
4935                        SqlExpr::BinaryOp { left, op, right } => {
4936                            // Push builder, then schedule the left operand followed by the right.
4937                            // Because the work stack is LIFO, the right-hand side executes first,
4938                            // leaving the left result on top for the builder to consume without
4939                            // swapping operand order.
4940                            work_stack.push(WorkItem::BuildBinaryOp {
4941                                op,
4942                                left: left.clone(),
4943                                right_done: false,
4944                            });
4945                            // Evaluate the right-hand side first so the left result remains on top
4946                            // of the result stack when the builder consumes it. This preserves the
4947                            // original operand ordering when reconstructing the expression tree.
4948                            work_stack.push(WorkItem::Process(left));
4949                            work_stack.push(WorkItem::Process(right));
4950                        }
4951                        SqlExpr::UnaryOp { op, expr } => {
4952                            work_stack.push(WorkItem::BuildUnaryOp { op });
4953                            work_stack.push(WorkItem::Process(expr));
4954                        }
4955                        SqlExpr::Nested(inner) => {
4956                            work_stack.push(WorkItem::BuildNested);
4957                            work_stack.push(WorkItem::Process(inner));
4958                        }
4959                        SqlExpr::IsNull(inner) => {
4960                            work_stack.push(WorkItem::BuildIsNull);
4961                            work_stack.push(WorkItem::Process(inner));
4962                        }
4963                        SqlExpr::IsNotNull(inner) => {
4964                            work_stack.push(WorkItem::BuildIsNotNull);
4965                            work_stack.push(WorkItem::Process(inner));
4966                        }
4967                        SqlExpr::Between {
4968                            expr,
4969                            negated,
4970                            low,
4971                            high,
4972                        } => {
4973                            work_stack.push(WorkItem::FinishBetween { negated });
4974                            work_stack.push(WorkItem::Process(high));
4975                            work_stack.push(WorkItem::Process(low));
4976                            work_stack.push(WorkItem::Process(expr));
4977                        }
4978                        // All other expressions: push as-is to result stack
4979                        other => {
4980                            result_stack.push(other);
4981                        }
4982                    }
4983                }
4984                WorkItem::BuildBinaryOp {
4985                    op,
4986                    left,
4987                    right_done,
4988                } => {
4989                    if !right_done {
4990                        // Left done, now mark that right will be done next
4991                        let left_result = result_stack.pop().unwrap();
4992                        work_stack.push(WorkItem::BuildBinaryOp {
4993                            op,
4994                            left: Box::new(left_result),
4995                            right_done: true,
4996                        });
4997                    } else {
4998                        // Both done, build the BinaryOp
4999                        let right_result = result_stack.pop().unwrap();
5000                        let left_result = *left;
5001                        result_stack.push(SqlExpr::BinaryOp {
5002                            left: Box::new(left_result),
5003                            op,
5004                            right: Box::new(right_result),
5005                        });
5006                    }
5007                }
5008                WorkItem::BuildUnaryOp { op } => {
5009                    let inner = result_stack.pop().unwrap();
5010                    result_stack.push(SqlExpr::UnaryOp {
5011                        op,
5012                        expr: Box::new(inner),
5013                    });
5014                }
5015                WorkItem::BuildNested => {
5016                    let inner = result_stack.pop().unwrap();
5017                    result_stack.push(SqlExpr::Nested(Box::new(inner)));
5018                }
5019                WorkItem::BuildIsNull => {
5020                    let inner = result_stack.pop().unwrap();
5021                    result_stack.push(SqlExpr::IsNull(Box::new(inner)));
5022                }
5023                WorkItem::BuildIsNotNull => {
5024                    let inner = result_stack.pop().unwrap();
5025                    result_stack.push(SqlExpr::IsNotNull(Box::new(inner)));
5026                }
5027                WorkItem::FinishBetween { negated } => {
5028                    let high_result = result_stack.pop().unwrap();
5029                    let low_result = result_stack.pop().unwrap();
5030                    let expr_result = result_stack.pop().unwrap();
5031                    result_stack.push(SqlExpr::Between {
5032                        expr: Box::new(expr_result),
5033                        negated,
5034                        low: Box::new(low_result),
5035                        high: Box::new(high_result),
5036                    });
5037                }
5038            }
5039        }
5040
5041        // Final result should be the only item on the result stack
5042        Ok(result_stack
5043            .pop()
5044            .expect("result stack should have exactly one item"))
5045    }
5046
5047    fn handle_query(&self, query: Query) -> SqlResult<RuntimeStatementResult<P>> {
5048        tracing::debug!("handle_query: query={:?}", query);
5049        let mut visited_views = FxHashSet::default();
5050        self.execute_query_with_view_support(query, &mut visited_views)
5051    }
5052
5053    fn execute_query_with_view_support(
5054        &self,
5055        query: Query,
5056        visited_views: &mut FxHashSet<String>,
5057    ) -> SqlResult<RuntimeStatementResult<P>> {
5058        // Lazily refresh information_schema only if this query references it.
5059        // This must happen before any table resolution to ensure the schema tables exist.
5060        let needs_info_schema = query_references_information_schema(&query);
5061        if needs_info_schema {
5062            tracing::debug!("Query references information_schema, ensuring it's ready");
5063            self.ensure_information_schema_ready()?;
5064        }
5065
5066        if let Some(result) = self.try_execute_simple_view_select(&query, visited_views)? {
5067            return Ok(result);
5068        }
5069
5070        if let Some(result) = self.try_execute_view_set_operation(&query, visited_views)? {
5071            return Ok(result);
5072        }
5073
5074        if let Some(result) = self.try_execute_simple_derived_select(&query, visited_views)? {
5075            return Ok(result);
5076        }
5077
5078        // Check for pragma_table_info() table function first
5079        if let Some(result) = self.try_handle_pragma_table_info(&query)? {
5080            return Ok(result);
5081        }
5082
5083        let select_plan = self.build_select_plan(query)?;
5084        self.execute_plan_statement(PlanStatement::Select(Box::new(select_plan)))
5085    }
5086
5087    fn try_execute_simple_view_select(
5088        &self,
5089        query: &Query,
5090        visited_views: &mut FxHashSet<String>,
5091    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
5092        use sqlparser::ast::SetExpr;
5093
5094        // Reject complex query forms upfront.
5095        if query.with.is_some() || query.order_by.is_some() || query.limit_clause.is_some() {
5096            return Ok(None);
5097        }
5098
5099        let select = match query.body.as_ref() {
5100            SetExpr::Select(select) => select,
5101            _ => return Ok(None),
5102        };
5103
5104        if select.distinct.is_some()
5105            || select.selection.is_some()
5106            || !group_by_is_empty(&select.group_by)
5107            || select.having.is_some()
5108            || !select.cluster_by.is_empty()
5109            || !select.distribute_by.is_empty()
5110            || !select.sort_by.is_empty()
5111            || select.top.is_some()
5112            || select.value_table_mode.is_some()
5113            || !select.named_window.is_empty()
5114            || select.qualify.is_some()
5115            || select.connect_by.is_some()
5116        {
5117            return Ok(None);
5118        }
5119
5120        if select.from.len() != 1 {
5121            return Ok(None);
5122        }
5123
5124        let table_with_joins = &select.from[0];
5125        if table_with_joins_has_join(table_with_joins) {
5126            return Ok(None);
5127        }
5128
5129        let (view_display_name, view_canonical_name, table_alias) = match &table_with_joins.relation
5130        {
5131            TableFactor::Table { name, alias, .. } => {
5132                let (display, canonical) = canonical_object_name(name)?;
5133                let catalog = self.engine.context().table_catalog();
5134                let Some(table_id) = catalog.table_id(&canonical) else {
5135                    return Ok(None);
5136                };
5137                if !self.engine.context().is_view(table_id)? {
5138                    return Ok(None);
5139                }
5140                let alias_name = alias.as_ref().map(|a| a.name.value.clone());
5141                (display, canonical, alias_name)
5142            }
5143            _ => return Ok(None),
5144        };
5145
5146        // Gather projection mapping for simple column selection.
5147        enum ProjectionKind {
5148            All,
5149            Columns(Vec<(String, String)>), // (source column, output name)
5150        }
5151
5152        let projection = if select
5153            .projection
5154            .iter()
5155            .any(|item| matches!(item, SelectItem::Wildcard(_)))
5156        {
5157            if select.projection.len() != 1 {
5158                return Ok(None);
5159            }
5160            ProjectionKind::All
5161        } else {
5162            let mut columns = Vec::with_capacity(select.projection.len());
5163            for item in &select.projection {
5164                match item {
5165                    SelectItem::UnnamedExpr(SqlExpr::Identifier(ident)) => {
5166                        let name = ident.value.clone();
5167                        columns.push((name.clone(), name));
5168                    }
5169                    SelectItem::ExprWithAlias { expr, alias } => {
5170                        let source = match expr {
5171                            SqlExpr::Identifier(ident) => ident.value.clone(),
5172                            SqlExpr::CompoundIdentifier(idents) => {
5173                                if idents.is_empty() {
5174                                    return Ok(None);
5175                                }
5176                                idents.last().unwrap().value.clone()
5177                            }
5178                            _ => return Ok(None),
5179                        };
5180                        columns.push((source, alias.value.clone()));
5181                    }
5182                    SelectItem::UnnamedExpr(SqlExpr::CompoundIdentifier(parts)) => {
5183                        if parts.is_empty() {
5184                            return Ok(None);
5185                        }
5186                        // Allow optional table qualifier matching alias or view name.
5187                        if parts.len() == 2 {
5188                            let qualifier = parts[0].value.to_ascii_lowercase();
5189                            if let Some(ref alias_name) = table_alias {
5190                                if qualifier != alias_name.to_ascii_lowercase() {
5191                                    return Ok(None);
5192                                }
5193                            } else if qualifier != view_display_name.to_ascii_lowercase() {
5194                                return Ok(None);
5195                            }
5196                        } else if parts.len() != 1 {
5197                            return Ok(None);
5198                        }
5199                        let column_name = parts.last().unwrap().value.clone();
5200                        columns.push((column_name.clone(), column_name));
5201                    }
5202                    _ => return Ok(None),
5203                }
5204            }
5205            ProjectionKind::Columns(columns)
5206        };
5207
5208        let context = self.engine.context();
5209        let definition = context
5210            .view_definition(&view_canonical_name)?
5211            .ok_or_else(|| {
5212                Error::CatalogError(format!(
5213                    "Binder Error: view '{}' does not have a stored definition",
5214                    view_display_name
5215                ))
5216            })?;
5217
5218        if !visited_views.insert(view_canonical_name.clone()) {
5219            return Err(Error::CatalogError(format!(
5220                "Binder Error: cyclic view reference involving '{}'",
5221                view_display_name
5222            )));
5223        }
5224
5225        let view_query = Self::parse_view_query(&definition)?;
5226        let view_result = self.execute_query_with_view_support(view_query, visited_views);
5227        visited_views.remove(&view_canonical_name);
5228        let view_result = view_result?;
5229
5230        let RuntimeStatementResult::Select {
5231            execution: view_execution,
5232            schema: view_schema,
5233            ..
5234        } = view_result
5235        else {
5236            return Err(Error::InvalidArgumentError(format!(
5237                "view '{}' definition did not produce a SELECT result",
5238                view_display_name
5239            )));
5240        };
5241
5242        match projection {
5243            ProjectionKind::All => {
5244                // Reuse the original execution and schema.
5245                let select_result = RuntimeStatementResult::Select {
5246                    execution: view_execution,
5247                    table_name: view_display_name,
5248                    schema: view_schema,
5249                };
5250                Ok(Some(select_result))
5251            }
5252            ProjectionKind::Columns(columns) => {
5253                let exec = *view_execution;
5254                let batches = exec.collect()?;
5255                let view_fields = view_schema.fields();
5256                let mut name_to_index =
5257                    FxHashMap::with_capacity_and_hasher(view_fields.len(), Default::default());
5258                for (idx, field) in view_fields.iter().enumerate() {
5259                    name_to_index.insert(field.name().to_ascii_lowercase(), idx);
5260                }
5261
5262                let mut column_indices = Vec::with_capacity(columns.len());
5263                let mut projected_fields = Vec::with_capacity(columns.len());
5264
5265                for (source, output) in columns {
5266                    let lookup = source.to_ascii_lowercase();
5267                    let Some(&idx) = name_to_index.get(&lookup) else {
5268                        return Err(Error::InvalidArgumentError(format!(
5269                            "Binder Error: view '{}' does not have a column named '{}'",
5270                            view_display_name, source
5271                        )));
5272                    };
5273                    column_indices.push(idx);
5274                    let origin_field = view_fields[idx].clone();
5275                    let projected_field = Field::new(
5276                        &output,
5277                        origin_field.data_type().clone(),
5278                        origin_field.is_nullable(),
5279                    )
5280                    .with_metadata(origin_field.metadata().clone());
5281                    projected_fields.push(projected_field);
5282                }
5283
5284                let projected_schema = Arc::new(Schema::new(projected_fields));
5285
5286                let mut projected_batches = Vec::with_capacity(batches.len());
5287                for batch in batches {
5288                    let mut arrays: Vec<ArrayRef> = Vec::with_capacity(column_indices.len());
5289                    for idx in &column_indices {
5290                        arrays.push(Arc::clone(batch.column(*idx)));
5291                    }
5292                    let projected = RecordBatch::try_new(Arc::clone(&projected_schema), arrays)?;
5293                    projected_batches.push(projected);
5294                }
5295
5296                let combined_batch = if projected_batches.is_empty() {
5297                    RecordBatch::new_empty(Arc::clone(&projected_schema))
5298                } else if projected_batches.len() == 1 {
5299                    projected_batches.remove(0)
5300                } else {
5301                    concat_batches(&projected_schema, projected_batches.iter())?
5302                };
5303
5304                let select_execution = SelectExecution::from_batch(
5305                    view_display_name.clone(),
5306                    Arc::clone(&projected_schema),
5307                    combined_batch,
5308                );
5309
5310                Ok(Some(RuntimeStatementResult::Select {
5311                    execution: Box::new(select_execution),
5312                    table_name: view_display_name,
5313                    schema: projected_schema,
5314                }))
5315            }
5316        }
5317    }
5318
5319    fn try_execute_simple_derived_select(
5320        &self,
5321        query: &Query,
5322        visited_views: &mut FxHashSet<String>,
5323    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
5324        use sqlparser::ast::{Expr as SqlExpr, SelectItem, SetExpr};
5325
5326        // Support only plain SELECT queries without WITH, ORDER BY, LIMIT, or FETCH clauses.
5327        if query.with.is_some() || query.order_by.is_some() || query.limit_clause.is_some() {
5328            return Ok(None);
5329        }
5330        if query.fetch.is_some() {
5331            return Ok(None);
5332        }
5333
5334        let select = match query.body.as_ref() {
5335            SetExpr::Select(select) => select,
5336            _ => return Ok(None),
5337        };
5338
5339        if select.from.len() != 1 {
5340            return Ok(None);
5341        }
5342
5343        let table_with_joins = &select.from[0];
5344        let (subquery, alias, lateral) = match &table_with_joins.relation {
5345            TableFactor::Derived {
5346                subquery,
5347                alias,
5348                lateral,
5349                ..
5350            } => (subquery, alias.as_ref(), *lateral),
5351            _ => return Ok(None),
5352        };
5353
5354        if table_with_joins_has_join(table_with_joins) {
5355            return Err(Error::InvalidArgumentError(
5356                "Binder Error: derived table queries with JOINs are not supported yet".into(),
5357            ));
5358        }
5359
5360        if lateral {
5361            return Err(Error::InvalidArgumentError(
5362                "Binder Error: LATERAL derived tables are not supported yet".into(),
5363            ));
5364        }
5365        if select.distinct.is_some()
5366            || select.selection.is_some()
5367            || !group_by_is_empty(&select.group_by)
5368            || select.having.is_some()
5369            || !select.cluster_by.is_empty()
5370            || !select.distribute_by.is_empty()
5371            || !select.sort_by.is_empty()
5372            || select.top.is_some()
5373            || select.value_table_mode.is_some()
5374            || !select.named_window.is_empty()
5375            || select.qualify.is_some()
5376            || select.connect_by.is_some()
5377        {
5378            return Err(Error::InvalidArgumentError(
5379                "Binder Error: advanced clauses are not supported for derived table queries yet"
5380                    .into(),
5381            ));
5382        }
5383
5384        let inner_query = *subquery.clone();
5385        let inner_result = self.execute_query_with_view_support(inner_query, visited_views)?;
5386        let (inner_exec, inner_schema, inner_table_name) =
5387            self.extract_select_result(inner_result)?;
5388
5389        let alias_name = alias.map(|a| a.name.value.clone());
5390        let alias_columns = alias.and_then(|a| {
5391            if a.columns.is_empty() {
5392                None
5393            } else {
5394                Some(
5395                    a.columns
5396                        .iter()
5397                        .map(|col| col.name.value.clone())
5398                        .collect::<Vec<_>>(),
5399                )
5400            }
5401        });
5402
5403        if let Some(ref columns) = alias_columns
5404            && columns.len() != inner_schema.fields().len()
5405        {
5406            return Err(Error::InvalidArgumentError(
5407                "Binder Error: derived table column alias count must match projection".into(),
5408            ));
5409        }
5410
5411        let alias_lower = alias_name.as_ref().map(|name| name.to_ascii_lowercase());
5412        let inner_lower = inner_table_name.to_ascii_lowercase();
5413
5414        enum DerivedProjection {
5415            All,
5416            Columns(Vec<(String, String)>),
5417        }
5418
5419        let resolve_compound_identifier = |parts: &[Ident]| -> SqlResult<String> {
5420            if parts.is_empty() {
5421                return Err(Error::InvalidArgumentError(
5422                    "Binder Error: empty identifier in derived table projection".into(),
5423                ));
5424            }
5425            if parts.len() == 1 {
5426                return Ok(parts[0].value.clone());
5427            }
5428            if parts.len() == 2 {
5429                let qualifier_lower = parts[0].value.to_ascii_lowercase();
5430                let qualifier_display = parts[0].value.clone();
5431                if let Some(ref alias_lower) = alias_lower {
5432                    if qualifier_lower != *alias_lower {
5433                        return Err(Error::InvalidArgumentError(format!(
5434                            "Binder Error: derived table qualifier '{}' does not match alias '{}'",
5435                            qualifier_display,
5436                            alias_name.as_deref().unwrap_or(""),
5437                        )));
5438                    }
5439                } else if qualifier_lower != inner_lower {
5440                    return Err(Error::InvalidArgumentError(format!(
5441                        "Binder Error: derived table qualifier '{}' does not match subquery name '{}'",
5442                        qualifier_display, inner_table_name
5443                    )));
5444                }
5445                return Ok(parts[1].value.clone());
5446            }
5447            Err(Error::InvalidArgumentError(
5448                "Binder Error: multi-part qualified identifiers are not supported for derived tables yet"
5449                    .into(),
5450            ))
5451        };
5452
5453        let build_projection_columns = |items: &[SelectItem]| -> SqlResult<Vec<(String, String)>> {
5454            let mut columns = Vec::with_capacity(items.len());
5455            for item in items {
5456                match item {
5457                    SelectItem::UnnamedExpr(SqlExpr::Identifier(ident)) => {
5458                        let name = ident.value.clone();
5459                        columns.push((name.clone(), name));
5460                    }
5461                    SelectItem::ExprWithAlias { expr, alias } => {
5462                        let source = match expr {
5463                            SqlExpr::Identifier(ident) => ident.value.clone(),
5464                            SqlExpr::CompoundIdentifier(parts) => {
5465                                resolve_compound_identifier(parts)?
5466                            }
5467                            _ => {
5468                                return Err(Error::InvalidArgumentError(
5469                                    "Binder Error: complex expressions in derived table projections are not supported yet"
5470                                        .into(),
5471                                ));
5472                            }
5473                        };
5474                        columns.push((source, alias.value.clone()))
5475                    }
5476                    SelectItem::UnnamedExpr(SqlExpr::CompoundIdentifier(parts)) => {
5477                        let column = resolve_compound_identifier(parts)?;
5478                        columns.push((column.clone(), column));
5479                    }
5480                    other => {
5481                        return Err(Error::InvalidArgumentError(format!(
5482                            "Binder Error: unsupported derived table projection {:?}",
5483                            other
5484                        )));
5485                    }
5486                }
5487            }
5488            Ok(columns)
5489        };
5490
5491        let projection = if select.projection.len() == 1 {
5492            match &select.projection[0] {
5493                SelectItem::Wildcard(_) => DerivedProjection::All,
5494                SelectItem::QualifiedWildcard(kind, _) => match kind {
5495                    SelectItemQualifiedWildcardKind::ObjectName(name) => {
5496                        let qualifier = Self::object_name_to_string(name)?;
5497                        let qualifier_lower = qualifier.to_ascii_lowercase();
5498                        if let Some(ref alias_lower) = alias_lower {
5499                            if qualifier_lower != *alias_lower {
5500                                return Err(Error::InvalidArgumentError(format!(
5501                                    "Binder Error: derived table qualifier '{}' does not match alias '{}'",
5502                                    qualifier,
5503                                    alias_name.as_deref().unwrap_or(""),
5504                                )));
5505                            }
5506                        } else if qualifier_lower != inner_lower {
5507                            return Err(Error::InvalidArgumentError(format!(
5508                                "Binder Error: derived table qualifier '{}' does not match subquery name '{}'",
5509                                qualifier, inner_table_name
5510                            )));
5511                        }
5512                        DerivedProjection::All
5513                    }
5514                    SelectItemQualifiedWildcardKind::Expr(_) => {
5515                        return Err(Error::InvalidArgumentError(
5516                                "Binder Error: expression-qualified wildcards are not supported for derived tables yet"
5517                                    .into(),
5518                            ));
5519                    }
5520                },
5521                _ => DerivedProjection::Columns(build_projection_columns(&select.projection)?),
5522            }
5523        } else {
5524            if select.projection.iter().any(|item| {
5525                matches!(
5526                    item,
5527                    SelectItem::Wildcard(_) | SelectItem::QualifiedWildcard(_, _)
5528                )
5529            }) {
5530                return Err(Error::InvalidArgumentError(
5531                    "Binder Error: derived table projections cannot mix wildcards with explicit columns"
5532                        .into(),
5533                ));
5534            }
5535            DerivedProjection::Columns(build_projection_columns(&select.projection)?)
5536        };
5537
5538        let mut batches = inner_exec.collect()?;
5539        let output_table_name = alias_name.clone().unwrap_or(inner_table_name.clone());
5540
5541        let mut name_to_index = FxHashMap::default();
5542        for (idx, field) in inner_schema.fields().iter().enumerate() {
5543            name_to_index.insert(field.name().to_ascii_lowercase(), idx);
5544        }
5545        if let Some(ref columns) = alias_columns {
5546            for (idx, alias_col) in columns.iter().enumerate() {
5547                name_to_index.insert(alias_col.to_ascii_lowercase(), idx);
5548            }
5549        }
5550
5551        let mut build_projected_result = |column_mappings: Vec<(String, String)>| -> SqlResult<_> {
5552            let mut column_indices = Vec::with_capacity(column_mappings.len());
5553            let mut projected_fields = Vec::with_capacity(column_mappings.len());
5554
5555            for (source, output) in column_mappings {
5556                let key = source.to_ascii_lowercase();
5557                let Some(&idx) = name_to_index.get(&key) else {
5558                    return Err(Error::InvalidArgumentError(format!(
5559                        "Binder Error: derived table does not provide a column named '{}'",
5560                        source
5561                    )));
5562                };
5563                column_indices.push(idx);
5564                let origin_field = inner_schema.field(idx).clone();
5565                let projected_field = Field::new(
5566                    &output,
5567                    origin_field.data_type().clone(),
5568                    origin_field.is_nullable(),
5569                )
5570                .with_metadata(origin_field.metadata().clone());
5571                projected_fields.push(projected_field);
5572            }
5573
5574            let projected_schema = Arc::new(Schema::new(projected_fields));
5575            let mut projected_batches = Vec::with_capacity(batches.len());
5576            for batch in batches.drain(..) {
5577                let mut arrays: Vec<ArrayRef> = Vec::with_capacity(column_indices.len());
5578                for idx in &column_indices {
5579                    arrays.push(Arc::clone(batch.column(*idx)));
5580                }
5581                let projected = RecordBatch::try_new(Arc::clone(&projected_schema), arrays)
5582                    .map_err(|err| {
5583                        Error::Internal(format!(
5584                            "failed to construct derived table projection batch: {err}"
5585                        ))
5586                    })?;
5587                projected_batches.push(projected);
5588            }
5589
5590            let combined_batch = if projected_batches.is_empty() {
5591                RecordBatch::new_empty(Arc::clone(&projected_schema))
5592            } else if projected_batches.len() == 1 {
5593                projected_batches.remove(0)
5594            } else {
5595                concat_batches(&projected_schema, projected_batches.iter()).map_err(|err| {
5596                    Error::Internal(format!(
5597                        "failed to concatenate derived table batches: {err}"
5598                    ))
5599                })?
5600            };
5601
5602            Ok((projected_schema, combined_batch))
5603        };
5604
5605        let (final_schema, combined_batch) = match projection {
5606            DerivedProjection::All => {
5607                if let Some(columns) = alias_columns {
5608                    let mappings = columns
5609                        .iter()
5610                        .map(|name| (name.clone(), name.clone()))
5611                        .collect::<Vec<_>>();
5612                    build_projected_result(mappings)?
5613                } else {
5614                    let schema = Arc::clone(&inner_schema);
5615                    let combined = if batches.is_empty() {
5616                        RecordBatch::new_empty(Arc::clone(&schema))
5617                    } else if batches.len() == 1 {
5618                        batches.remove(0)
5619                    } else {
5620                        concat_batches(&schema, batches.iter()).map_err(|err| {
5621                            Error::Internal(format!(
5622                                "failed to concatenate derived table batches: {err}"
5623                            ))
5624                        })?
5625                    };
5626                    (schema, combined)
5627                }
5628            }
5629            DerivedProjection::Columns(mappings) => build_projected_result(mappings)?,
5630        };
5631
5632        let execution = SelectExecution::from_batch(
5633            output_table_name.clone(),
5634            Arc::clone(&final_schema),
5635            combined_batch,
5636        );
5637
5638        Ok(Some(RuntimeStatementResult::Select {
5639            execution: Box::new(execution),
5640            table_name: output_table_name,
5641            schema: final_schema,
5642        }))
5643    }
5644
5645    fn try_execute_view_set_operation(
5646        &self,
5647        query: &Query,
5648        visited_views: &mut FxHashSet<String>,
5649    ) -> SqlResult<Option<RuntimeStatementResult<P>>> {
5650        if !matches!(query.body.as_ref(), SetExpr::SetOperation { .. }) {
5651            return Ok(None);
5652        }
5653
5654        if query.with.is_some()
5655            || query.order_by.is_some()
5656            || query.limit_clause.is_some()
5657            || query.fetch.is_some()
5658        {
5659            return Ok(None);
5660        }
5661
5662        if !self.set_expr_contains_view(query.body.as_ref())? {
5663            return Ok(None);
5664        }
5665
5666        let result = self.evaluate_set_expr(query.body.as_ref(), visited_views)?;
5667        Ok(Some(result))
5668    }
5669
5670    fn evaluate_set_expr(
5671        &self,
5672        expr: &SetExpr,
5673        visited_views: &mut FxHashSet<String>,
5674    ) -> SqlResult<RuntimeStatementResult<P>> {
5675        match expr {
5676            SetExpr::SetOperation {
5677                left,
5678                right,
5679                op,
5680                set_quantifier,
5681            } => {
5682                let left_result = self.evaluate_set_expr(left.as_ref(), visited_views)?;
5683                let right_result = self.evaluate_set_expr(right.as_ref(), visited_views)?;
5684                self.combine_set_results(left_result, right_result, *op, *set_quantifier)
5685            }
5686            SetExpr::Query(subquery) => {
5687                self.execute_query_with_view_support(*subquery.clone(), visited_views)
5688            }
5689            _ => self.execute_setexpr_query(expr, visited_views),
5690        }
5691    }
5692
5693    fn execute_setexpr_query(
5694        &self,
5695        expr: &SetExpr,
5696        visited_views: &mut FxHashSet<String>,
5697    ) -> SqlResult<RuntimeStatementResult<P>> {
5698        let sql = expr.to_string();
5699        let dialect = GenericDialect {};
5700        let statements = parse_sql_with_recursion_limit(&dialect, &sql).map_err(|err| {
5701            Error::InvalidArgumentError(format!(
5702                "failed to parse expanded view query '{sql}': {err}"
5703            ))
5704        })?;
5705
5706        let mut iter = statements.into_iter();
5707        let statement = iter.next().ok_or_else(|| {
5708            Error::InvalidArgumentError("expanded view query did not produce a statement".into())
5709        })?;
5710        if iter.next().is_some() {
5711            return Err(Error::InvalidArgumentError(
5712                "expanded view query produced multiple statements".into(),
5713            ));
5714        }
5715
5716        let query = match statement {
5717            Statement::Query(q) => *q,
5718            other => {
5719                return Err(Error::InvalidArgumentError(format!(
5720                    "expanded view query did not produce a SELECT statement: {other:?}"
5721                )));
5722            }
5723        };
5724
5725        self.execute_query_with_view_support(query, visited_views)
5726    }
5727
5728    fn combine_set_results(
5729        &self,
5730        left: RuntimeStatementResult<P>,
5731        right: RuntimeStatementResult<P>,
5732        op: SetOperator,
5733        quantifier: SetQuantifier,
5734    ) -> SqlResult<RuntimeStatementResult<P>> {
5735        match op {
5736            SetOperator::Union => self.union_select_results(left, right, quantifier),
5737            other => Err(Error::InvalidArgumentError(format!(
5738                "Binder Error: unsupported set operator {other:?} in view query"
5739            ))),
5740        }
5741    }
5742
5743    fn union_select_results(
5744        &self,
5745        left: RuntimeStatementResult<P>,
5746        right: RuntimeStatementResult<P>,
5747        quantifier: SetQuantifier,
5748    ) -> SqlResult<RuntimeStatementResult<P>> {
5749        let (left_exec, left_schema, left_name) = self.extract_select_result(left)?;
5750        let (right_exec, right_schema, _) = self.extract_select_result(right)?;
5751
5752        self.ensure_schemas_compatible(&left_schema, &right_schema)?;
5753
5754        let mut batches = Vec::new();
5755        batches.extend(left_exec.collect()?);
5756        batches.extend(right_exec.collect()?);
5757
5758        let mut combined_batch = if batches.is_empty() {
5759            RecordBatch::new_empty(Arc::clone(&left_schema))
5760        } else if batches.len() == 1 {
5761            batches.pop().expect("length checked above")
5762        } else {
5763            concat_batches(&left_schema, batches.iter()).map_err(|err| {
5764                Error::Internal(format!("failed to concatenate UNION batches: {err}"))
5765            })?
5766        };
5767
5768        if matches!(quantifier, SetQuantifier::Distinct) {
5769            combined_batch = self.distinct_batch(&left_schema, combined_batch)?;
5770        }
5771
5772        let execution = SelectExecution::from_batch(
5773            left_name.clone(),
5774            Arc::clone(&left_schema),
5775            combined_batch,
5776        );
5777
5778        Ok(RuntimeStatementResult::Select {
5779            execution: Box::new(execution),
5780            table_name: left_name,
5781            schema: left_schema,
5782        })
5783    }
5784
5785    fn extract_select_result(
5786        &self,
5787        result: RuntimeStatementResult<P>,
5788    ) -> SqlResult<(SelectExecution<P>, Arc<Schema>, String)> {
5789        match result {
5790            RuntimeStatementResult::Select {
5791                execution,
5792                schema,
5793                table_name,
5794            } => Ok((*execution, schema, table_name)),
5795            _ => Err(Error::InvalidArgumentError(
5796                "expected SELECT result while evaluating set operation".into(),
5797            )),
5798        }
5799    }
5800
5801    fn ensure_schemas_compatible(&self, left: &Arc<Schema>, right: &Arc<Schema>) -> SqlResult<()> {
5802        if left.fields().len() != right.fields().len() {
5803            return Err(Error::InvalidArgumentError(
5804                "Binder Error: UNION inputs project different column counts".into(),
5805            ));
5806        }
5807
5808        for (idx, (l_field, r_field)) in left.fields().iter().zip(right.fields().iter()).enumerate()
5809        {
5810            if l_field.data_type() != r_field.data_type() {
5811                return Err(Error::InvalidArgumentError(format!(
5812                    "Binder Error: UNION column {} type mismatch ({:?} vs {:?})",
5813                    idx + 1,
5814                    l_field.data_type(),
5815                    r_field.data_type()
5816                )));
5817            }
5818        }
5819
5820        Ok(())
5821    }
5822
5823    fn distinct_batch(&self, schema: &Arc<Schema>, batch: RecordBatch) -> SqlResult<RecordBatch> {
5824        if batch.num_rows() <= 1 {
5825            return Ok(batch);
5826        }
5827
5828        let sort_fields: Vec<SortField> = schema
5829            .fields()
5830            .iter()
5831            .map(|field| SortField::new(field.data_type().clone()))
5832            .collect();
5833
5834        let converter = RowConverter::new(sort_fields)
5835            .map_err(|err| Error::Internal(format!("failed to initialize row converter: {err}")))?;
5836        let rows = converter
5837            .convert_columns(batch.columns())
5838            .map_err(|err| Error::Internal(format!("failed to row-encode union result: {err}")))?;
5839
5840        let mut seen = FxHashSet::default();
5841        let mut indices = Vec::new();
5842        let mut has_duplicates = false;
5843        for (idx, row) in rows.iter().enumerate() {
5844            if seen.insert(row) {
5845                indices.push(idx as u32);
5846            } else {
5847                has_duplicates = true;
5848            }
5849        }
5850
5851        if !has_duplicates {
5852            return Ok(batch);
5853        }
5854
5855        let index_array = UInt32Array::from(indices);
5856        let mut columns = Vec::with_capacity(batch.num_columns());
5857        for column in batch.columns() {
5858            let taken = take(column.as_ref(), &index_array, None).map_err(|err| {
5859                Error::Internal(format!("failed to materialize DISTINCT rows: {err}"))
5860            })?;
5861            columns.push(taken);
5862        }
5863
5864        RecordBatch::try_new(Arc::clone(schema), columns)
5865            .map_err(|err| Error::Internal(format!("failed to build DISTINCT RecordBatch: {err}")))
5866    }
5867
5868    fn set_expr_contains_view(&self, expr: &SetExpr) -> SqlResult<bool> {
5869        match expr {
5870            SetExpr::Select(select) => self.select_contains_view(select.as_ref()),
5871            SetExpr::Query(query) => self.set_expr_contains_view(&query.body),
5872            SetExpr::SetOperation { left, right, .. } => Ok(self
5873                .set_expr_contains_view(left.as_ref())?
5874                || self.set_expr_contains_view(right.as_ref())?),
5875            _ => Ok(false),
5876        }
5877    }
5878
5879    fn select_contains_view(&self, select: &Select) -> SqlResult<bool> {
5880        for from_item in &select.from {
5881            if self.table_with_joins_contains_view(from_item)? {
5882                return Ok(true);
5883            }
5884        }
5885        Ok(false)
5886    }
5887
5888    fn table_with_joins_contains_view(&self, table: &TableWithJoins) -> SqlResult<bool> {
5889        if self.table_factor_contains_view(&table.relation)? {
5890            return Ok(true);
5891        }
5892
5893        for join in &table.joins {
5894            if self.table_factor_contains_view(&join.relation)? {
5895                return Ok(true);
5896            }
5897        }
5898
5899        Ok(false)
5900    }
5901
5902    fn table_factor_contains_view(&self, factor: &TableFactor) -> SqlResult<bool> {
5903        match factor {
5904            TableFactor::Table { name, .. } => {
5905                let (_, canonical) = canonical_object_name(name)?;
5906                let catalog = self.engine.context().table_catalog();
5907                let Some(table_id) = catalog.table_id(&canonical) else {
5908                    return Ok(false);
5909                };
5910                self.engine.context().is_view(table_id)
5911            }
5912            TableFactor::Derived { subquery, .. } => self.set_expr_contains_view(&subquery.body),
5913            TableFactor::NestedJoin {
5914                table_with_joins, ..
5915            } => self.table_with_joins_contains_view(table_with_joins),
5916            _ => Ok(false),
5917        }
5918    }
5919
5920    fn build_select_plan(&self, query: Query) -> SqlResult<SelectPlan> {
5921        if self.engine.session().has_active_transaction() && self.engine.session().is_aborted() {
5922            return Err(Error::TransactionContextError(
5923                "TransactionContext Error: transaction is aborted".into(),
5924            ));
5925        }
5926
5927        validate_simple_query(&query)?;
5928        let catalog = self.engine.context().table_catalog();
5929        let resolver = catalog.identifier_resolver();
5930
5931        let (mut select_plan, select_context) =
5932            self.translate_query_body(query.body.as_ref(), &resolver)?;
5933        if let Some(order_by) = &query.order_by {
5934            if !select_plan.aggregates.is_empty() {
5935                return Err(Error::InvalidArgumentError(
5936                    "ORDER BY is not supported for aggregate queries".into(),
5937                ));
5938            }
5939            let order_plan = self.translate_order_by(&resolver, select_context, order_by)?;
5940            select_plan = select_plan.with_order_by(order_plan);
5941        }
5942
5943        if let Some(limit_clause) = &query.limit_clause {
5944            let (limit, offset) = extract_limit_offset(limit_clause)?;
5945            select_plan.limit = limit;
5946            select_plan.offset = offset;
5947        }
5948
5949        Ok(select_plan)
5950    }
5951
5952    /// Internal version of build_select_plan that supports correlated subqueries.
5953    ///
5954    /// # Parameters
5955    /// - `query`: The SQL query to translate
5956    /// - `resolver`: Identifier resolver for column lookups
5957    /// - `outer_scopes`: Stack of outer query contexts for correlated column resolution
5958    /// - `subqueries`: Accumulator for EXISTS subqueries encountered during translation
5959    /// - `correlated_tracker`: Optional tracker for recording correlated column references
5960    fn build_select_plan_internal(
5961        &self,
5962        query: Query,
5963        resolver: &IdentifierResolver<'_>,
5964        outer_scopes: &[IdentifierContext],
5965        subqueries: &mut Vec<llkv_plan::FilterSubquery>,
5966        correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
5967    ) -> SqlResult<SelectPlan> {
5968        if self.engine.session().has_active_transaction() && self.engine.session().is_aborted() {
5969            return Err(Error::TransactionContextError(
5970                "TransactionContext Error: transaction is aborted".into(),
5971            ));
5972        }
5973
5974        validate_simple_query(&query)?;
5975
5976        let (mut select_plan, select_context) = self.translate_query_body_internal(
5977            query.body.as_ref(),
5978            resolver,
5979            outer_scopes,
5980            subqueries,
5981            correlated_tracker,
5982        )?;
5983        if let Some(order_by) = &query.order_by {
5984            if !select_plan.aggregates.is_empty() {
5985                return Err(Error::InvalidArgumentError(
5986                    "ORDER BY is not supported for aggregate queries".into(),
5987                ));
5988            }
5989            let order_plan = self.translate_order_by(resolver, select_context, order_by)?;
5990            select_plan = select_plan.with_order_by(order_plan);
5991        }
5992
5993        if let Some(limit_clause) = &query.limit_clause {
5994            let (limit, offset) = extract_limit_offset(limit_clause)?;
5995            select_plan.limit = limit;
5996            select_plan.offset = offset;
5997        }
5998
5999        Ok(select_plan)
6000    }
6001
6002    fn translate_select(
6003        &self,
6004        select: &Select,
6005        resolver: &IdentifierResolver<'_>,
6006    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
6007        let mut subqueries = Vec::new();
6008        let result =
6009            self.translate_select_internal(select, resolver, &[], &mut subqueries, None)?;
6010        if !subqueries.is_empty() {
6011            return Err(Error::Internal(
6012                "translate_select: unexpected subqueries from non-correlated translation".into(),
6013            ));
6014        }
6015        Ok(result)
6016    }
6017
6018    fn translate_query_body(
6019        &self,
6020        body: &SetExpr,
6021        resolver: &IdentifierResolver<'_>,
6022    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
6023        let mut subqueries = Vec::new();
6024        let result =
6025            self.translate_query_body_internal(body, resolver, &[], &mut subqueries, None)?;
6026        if !subqueries.is_empty() {
6027            return Err(Error::Internal(
6028                "translate_query_body: unexpected subqueries from non-correlated translation"
6029                    .into(),
6030            ));
6031        }
6032        Ok(result)
6033    }
6034
6035    fn translate_query_body_internal(
6036        &self,
6037        body: &SetExpr,
6038        resolver: &IdentifierResolver<'_>,
6039        outer_scopes: &[IdentifierContext],
6040        subqueries: &mut Vec<llkv_plan::FilterSubquery>,
6041        mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
6042    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
6043        match body {
6044            SetExpr::Select(select) => self.translate_select_internal(
6045                select.as_ref(),
6046                resolver,
6047                outer_scopes,
6048                subqueries,
6049                correlated_tracker,
6050            ),
6051            SetExpr::Query(query) => self.translate_query_body_internal(
6052                &query.body,
6053                resolver,
6054                outer_scopes,
6055                subqueries,
6056                correlated_tracker,
6057            ),
6058            SetExpr::SetOperation {
6059                left,
6060                right,
6061                op,
6062                set_quantifier,
6063            } => {
6064                let left_tracker = correlated_tracker.reborrow();
6065                let (left_plan, left_context) = self.translate_query_body_internal(
6066                    left.as_ref(),
6067                    resolver,
6068                    outer_scopes,
6069                    subqueries,
6070                    left_tracker,
6071                )?;
6072
6073                let right_tracker = correlated_tracker.reborrow();
6074                let (right_plan, _) = self.translate_query_body_internal(
6075                    right.as_ref(),
6076                    resolver,
6077                    outer_scopes,
6078                    subqueries,
6079                    right_tracker,
6080                )?;
6081
6082                let operator = match op {
6083                    sqlparser::ast::SetOperator::Union => llkv_plan::CompoundOperator::Union,
6084                    sqlparser::ast::SetOperator::Intersect => {
6085                        llkv_plan::CompoundOperator::Intersect
6086                    }
6087                    sqlparser::ast::SetOperator::Except | sqlparser::ast::SetOperator::Minus => {
6088                        llkv_plan::CompoundOperator::Except
6089                    }
6090                };
6091
6092                let quantifier = match set_quantifier {
6093                    SetQuantifier::All => llkv_plan::CompoundQuantifier::All,
6094                    _ => llkv_plan::CompoundQuantifier::Distinct,
6095                };
6096
6097                let mut compound = if let Some(existing) = left_plan.compound {
6098                    existing
6099                } else {
6100                    llkv_plan::CompoundSelectPlan::new(left_plan)
6101                };
6102                compound.push_operation(operator, quantifier, right_plan);
6103
6104                let result_plan = SelectPlan::new("").with_compound(compound);
6105
6106                Ok((result_plan, left_context))
6107            }
6108            other => Err(Error::InvalidArgumentError(format!(
6109                "unsupported query expression: {other:?}"
6110            ))),
6111        }
6112    }
6113
6114    fn translate_select_internal(
6115        &self,
6116        select: &Select,
6117        resolver: &IdentifierResolver<'_>,
6118        outer_scopes: &[IdentifierContext],
6119        subqueries: &mut Vec<llkv_plan::FilterSubquery>,
6120        mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
6121    ) -> SqlResult<(SelectPlan, IdentifierContext)> {
6122        let mut distinct = match &select.distinct {
6123            None => false,
6124            Some(Distinct::Distinct) => true,
6125            Some(Distinct::On(_)) => {
6126                return Err(Error::InvalidArgumentError(
6127                    "SELECT DISTINCT ON is not supported".into(),
6128                ));
6129            }
6130        };
6131        if matches!(
6132            select.value_table_mode,
6133            Some(
6134                sqlparser::ast::ValueTableMode::DistinctAsStruct
6135                    | sqlparser::ast::ValueTableMode::DistinctAsValue
6136            )
6137        ) {
6138            distinct = true;
6139        }
6140        if select.top.is_some() {
6141            return Err(Error::InvalidArgumentError(
6142                "SELECT TOP is not supported".into(),
6143            ));
6144        }
6145        if select.exclude.is_some() {
6146            return Err(Error::InvalidArgumentError(
6147                "SELECT EXCLUDE is not supported".into(),
6148            ));
6149        }
6150        if select.into.is_some() {
6151            return Err(Error::InvalidArgumentError(
6152                "SELECT INTO is not supported".into(),
6153            ));
6154        }
6155        if !select.lateral_views.is_empty() {
6156            return Err(Error::InvalidArgumentError(
6157                "LATERAL VIEW is not supported".into(),
6158            ));
6159        }
6160        if select.prewhere.is_some() {
6161            return Err(Error::InvalidArgumentError(
6162                "PREWHERE is not supported".into(),
6163            ));
6164        }
6165        if !select.cluster_by.is_empty()
6166            || !select.distribute_by.is_empty()
6167            || !select.sort_by.is_empty()
6168        {
6169            return Err(Error::InvalidArgumentError(
6170                "CLUSTER/DISTRIBUTE/SORT BY clauses are not supported".into(),
6171            ));
6172        }
6173        if !select.named_window.is_empty()
6174            || select.qualify.is_some()
6175            || select.connect_by.is_some()
6176        {
6177            return Err(Error::InvalidArgumentError(
6178                "advanced SELECT clauses are not supported".into(),
6179            ));
6180        }
6181
6182        let table_alias = select
6183            .from
6184            .first()
6185            .and_then(|table_with_joins| match &table_with_joins.relation {
6186                TableFactor::Table { alias, .. } => alias.as_ref().map(|a| a.name.value.clone()),
6187                _ => None,
6188            });
6189
6190        let has_joins = select.from.iter().any(table_with_joins_has_join);
6191        let mut join_conditions: Vec<Option<SqlExpr>> = Vec::new();
6192        let mut scalar_subqueries: Vec<llkv_plan::ScalarSubquery> = Vec::new();
6193        // Handle different FROM clause scenarios
6194        let catalog = self.engine.context().table_catalog();
6195        let has_group_by = !group_by_is_empty(&select.group_by);
6196        let (mut plan, id_context) = if select.from.is_empty() {
6197            if has_group_by {
6198                return Err(Error::InvalidArgumentError(
6199                    "GROUP BY requires a FROM clause".into(),
6200                ));
6201            }
6202            // No FROM clause - use empty string for table context (e.g., SELECT 42, SELECT {'a': 1} AS x)
6203            let mut p = SelectPlan::new("");
6204            let projections = self.build_projection_list(
6205                resolver,
6206                IdentifierContext::new(None),
6207                &select.projection,
6208                outer_scopes,
6209                &mut scalar_subqueries,
6210                correlated_tracker.reborrow(),
6211            )?;
6212            p = p.with_projections(projections);
6213            (p, IdentifierContext::new(None))
6214        } else if select.from.len() == 1 && !has_joins {
6215            // Single table query
6216            let (display_name, canonical_name) = extract_single_table(&select.from)?;
6217            let table_id = catalog.table_id(&canonical_name);
6218            let mut p = SelectPlan::new(display_name.clone());
6219            let available_columns = self.collect_known_columns(&display_name, &canonical_name)?;
6220            let single_table_context = IdentifierContext::new(table_id)
6221                .with_table_alias(table_alias.clone())
6222                .with_available_columns(available_columns);
6223            if let Some(alias) = table_alias.as_ref() {
6224                validate_projection_alias_qualifiers(&select.projection, alias)?;
6225            }
6226            if !has_group_by
6227                && let Some(aggregates) = self.detect_simple_aggregates(&select.projection)?
6228            {
6229                p = p.with_aggregates(aggregates);
6230            } else {
6231                let projections = self.build_projection_list(
6232                    resolver,
6233                    single_table_context.clone(),
6234                    &select.projection,
6235                    outer_scopes,
6236                    &mut scalar_subqueries,
6237                    correlated_tracker.reborrow(),
6238                )?;
6239                p = p.with_projections(projections);
6240            }
6241            (p, single_table_context)
6242        } else {
6243            // Multiple tables or explicit joins - treat as cross product for now
6244            let (tables, join_metadata, extracted_filters) = extract_tables(&select.from)?;
6245            join_conditions = extracted_filters;
6246            let available_columns = self.collect_available_columns_for_tables(&tables)?;
6247            let mut p = SelectPlan::with_tables(tables).with_joins(join_metadata);
6248            // For multi-table queries, we'll build projections differently
6249            // For now, just handle simple column references
6250            let projections = self.build_projection_list(
6251                resolver,
6252                IdentifierContext::new(None),
6253                &select.projection,
6254                outer_scopes,
6255                &mut scalar_subqueries,
6256                correlated_tracker.reborrow(),
6257            )?;
6258            p = p.with_projections(projections);
6259            (
6260                p,
6261                IdentifierContext::new(None).with_available_columns(available_columns),
6262            )
6263        };
6264
6265        let mut filter_components: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
6266        let mut all_subqueries = Vec::new();
6267
6268        if let Some(expr) = &select.selection {
6269            let materialized_expr = self.materialize_in_subquery(
6270                expr.clone(),
6271                SubqueryMaterializationMode::PreserveScalarSubqueries,
6272            )?;
6273            filter_components.push(translate_condition_with_context(
6274                self,
6275                resolver,
6276                id_context.clone(),
6277                &materialized_expr,
6278                outer_scopes,
6279                &mut all_subqueries,
6280                &mut scalar_subqueries,
6281                correlated_tracker.reborrow(),
6282            )?);
6283        }
6284
6285        // Translate JOIN ON predicates and attach them to the join metadata. INNER joins can
6286        // safely push these predicates into the WHERE clause, while LEFT joins must retain
6287        // them on the join so that unmatched left rows are preserved.
6288        for (idx, join_expr_opt) in join_conditions.iter().enumerate() {
6289            let translated = if let Some(join_expr) = join_expr_opt {
6290                let materialized_expr = self.materialize_in_subquery(
6291                    join_expr.clone(),
6292                    SubqueryMaterializationMode::PreserveScalarSubqueries,
6293                )?;
6294                Some(translate_condition_with_context(
6295                    self,
6296                    resolver,
6297                    id_context.clone(),
6298                    &materialized_expr,
6299                    outer_scopes,
6300                    &mut all_subqueries,
6301                    &mut scalar_subqueries,
6302                    correlated_tracker.reborrow(),
6303                )?)
6304            } else {
6305                None
6306            };
6307
6308            let is_left_join = plan
6309                .joins
6310                .get(idx)
6311                .map(|j| j.join_type == llkv_plan::JoinPlan::Left)
6312                .unwrap_or(false);
6313
6314            if let Some(join_meta) = plan.joins.get_mut(idx) {
6315                let expr_for_join = translated
6316                    .clone()
6317                    .unwrap_or(llkv_expr::expr::Expr::Literal(true));
6318                join_meta.on_condition = Some(expr_for_join);
6319            }
6320
6321            if let (Some(actual_expr), false) = (translated, is_left_join) {
6322                filter_components.push(actual_expr);
6323            }
6324        }
6325
6326        let having_expr = if let Some(having) = &select.having {
6327            let materialized_expr = self.materialize_in_subquery(
6328                having.clone(),
6329                SubqueryMaterializationMode::PreserveScalarSubqueries,
6330            )?;
6331            let translated = translate_condition_with_context(
6332                self,
6333                resolver,
6334                id_context.clone(),
6335                &materialized_expr,
6336                outer_scopes,
6337                &mut all_subqueries,
6338                &mut scalar_subqueries,
6339                correlated_tracker.reborrow(),
6340            )?;
6341            Some(translated)
6342        } else {
6343            None
6344        };
6345
6346        subqueries.append(&mut all_subqueries);
6347
6348        let filter = match filter_components.len() {
6349            0 => None,
6350            1 if subqueries.is_empty() => Some(llkv_plan::SelectFilter {
6351                predicate: filter_components.into_iter().next().unwrap(),
6352                subqueries: Vec::new(),
6353            }),
6354            1 => Some(llkv_plan::SelectFilter {
6355                predicate: filter_components.into_iter().next().unwrap(),
6356                subqueries: std::mem::take(subqueries),
6357            }),
6358            _ => Some(llkv_plan::SelectFilter {
6359                predicate: llkv_expr::expr::Expr::And(filter_components),
6360                subqueries: std::mem::take(subqueries),
6361            }),
6362        };
6363        plan = plan.with_filter(filter);
6364        plan = plan.with_having(having_expr);
6365        plan = plan.with_scalar_subqueries(std::mem::take(&mut scalar_subqueries));
6366        plan = plan.with_distinct(distinct);
6367
6368        let group_by_columns = if has_group_by {
6369            self.translate_group_by_columns(resolver, id_context.clone(), &select.group_by)?
6370        } else {
6371            Vec::new()
6372        };
6373        plan = plan.with_group_by(group_by_columns);
6374
6375        let value_mode = select.value_table_mode.map(convert_value_table_mode);
6376        plan = plan.with_value_table_mode(value_mode);
6377        Ok((plan, id_context))
6378    }
6379
6380    fn translate_order_by(
6381        &self,
6382        resolver: &IdentifierResolver<'_>,
6383        id_context: IdentifierContext,
6384        order_by: &OrderBy,
6385    ) -> SqlResult<Vec<OrderByPlan>> {
6386        let exprs = match &order_by.kind {
6387            OrderByKind::Expressions(exprs) => exprs,
6388            _ => {
6389                return Err(Error::InvalidArgumentError(
6390                    "unsupported ORDER BY clause".into(),
6391                ));
6392            }
6393        };
6394
6395        let base_nulls_first = self.default_nulls_first.load(AtomicOrdering::Relaxed);
6396
6397        let mut plans = Vec::with_capacity(exprs.len());
6398        for order_expr in exprs {
6399            let ascending = order_expr.options.asc.unwrap_or(true);
6400            let default_nulls_first_for_direction = if ascending {
6401                base_nulls_first
6402            } else {
6403                !base_nulls_first
6404            };
6405            let nulls_first = order_expr
6406                .options
6407                .nulls_first
6408                .unwrap_or(default_nulls_first_for_direction);
6409
6410            if let SqlExpr::Identifier(ident) = &order_expr.expr
6411                && ident.value.eq_ignore_ascii_case("ALL")
6412                && ident.quote_style.is_none()
6413            {
6414                plans.push(OrderByPlan {
6415                    target: OrderTarget::All,
6416                    sort_type: OrderSortType::Native,
6417                    ascending,
6418                    nulls_first,
6419                });
6420                continue;
6421            }
6422
6423            let (target, sort_type) = match &order_expr.expr {
6424                SqlExpr::Identifier(_) | SqlExpr::CompoundIdentifier(_) => (
6425                    OrderTarget::Column(self.resolve_simple_column_expr(
6426                        resolver,
6427                        id_context.clone(),
6428                        &order_expr.expr,
6429                    )?),
6430                    OrderSortType::Native,
6431                ),
6432                SqlExpr::Cast {
6433                    expr,
6434                    data_type:
6435                        SqlDataType::Int(_)
6436                        | SqlDataType::Integer(_)
6437                        | SqlDataType::BigInt(_)
6438                        | SqlDataType::SmallInt(_)
6439                        | SqlDataType::TinyInt(_),
6440                    ..
6441                } => (
6442                    OrderTarget::Column(self.resolve_simple_column_expr(
6443                        resolver,
6444                        id_context.clone(),
6445                        expr,
6446                    )?),
6447                    OrderSortType::CastTextToInteger,
6448                ),
6449                SqlExpr::Cast { data_type, .. } => {
6450                    return Err(Error::InvalidArgumentError(format!(
6451                        "ORDER BY CAST target type {:?} is not supported",
6452                        data_type
6453                    )));
6454                }
6455                SqlExpr::Value(value_with_span) => match &value_with_span.value {
6456                    Value::Number(raw, _) => {
6457                        let position: usize = raw.parse().map_err(|_| {
6458                            Error::InvalidArgumentError(format!(
6459                                "ORDER BY position '{}' is not a valid positive integer",
6460                                raw
6461                            ))
6462                        })?;
6463                        if position == 0 {
6464                            return Err(Error::InvalidArgumentError(
6465                                "ORDER BY position must be at least 1".into(),
6466                            ));
6467                        }
6468                        (OrderTarget::Index(position - 1), OrderSortType::Native)
6469                    }
6470                    other => {
6471                        return Err(Error::InvalidArgumentError(format!(
6472                            "unsupported ORDER BY literal expression: {other:?}"
6473                        )));
6474                    }
6475                },
6476                other => {
6477                    return Err(Error::InvalidArgumentError(format!(
6478                        "unsupported ORDER BY expression: {other:?}"
6479                    )));
6480                }
6481            };
6482
6483            plans.push(OrderByPlan {
6484                target,
6485                sort_type,
6486                ascending,
6487                nulls_first,
6488            });
6489        }
6490
6491        Ok(plans)
6492    }
6493
6494    fn translate_group_by_columns(
6495        &self,
6496        resolver: &IdentifierResolver<'_>,
6497        id_context: IdentifierContext,
6498        group_by: &GroupByExpr,
6499    ) -> SqlResult<Vec<String>> {
6500        use sqlparser::ast::Expr as SqlExpr;
6501
6502        match group_by {
6503            GroupByExpr::All(_) => Err(Error::InvalidArgumentError(
6504                "GROUP BY ALL is not supported".into(),
6505            )),
6506            GroupByExpr::Expressions(exprs, modifiers) => {
6507                if !modifiers.is_empty() {
6508                    return Err(Error::InvalidArgumentError(
6509                        "GROUP BY modifiers are not supported".into(),
6510                    ));
6511                }
6512                let mut columns = Vec::with_capacity(exprs.len());
6513                for expr in exprs {
6514                    let parts: Vec<String> = match expr {
6515                        SqlExpr::Identifier(ident) => vec![ident.value.clone()],
6516                        SqlExpr::CompoundIdentifier(idents) => {
6517                            idents.iter().map(|id| id.value.clone()).collect()
6518                        }
6519                        _ => {
6520                            return Err(Error::InvalidArgumentError(
6521                                "GROUP BY expressions must be simple column references".into(),
6522                            ));
6523                        }
6524                    };
6525                    let resolution = resolver.resolve(&parts, id_context.clone())?;
6526                    if !resolution.is_simple() {
6527                        return Err(Error::InvalidArgumentError(
6528                            "GROUP BY nested field references are not supported".into(),
6529                        ));
6530                    }
6531                    columns.push(resolution.column().to_string());
6532                }
6533                Ok(columns)
6534            }
6535        }
6536    }
6537
6538    fn resolve_simple_column_expr(
6539        &self,
6540        resolver: &IdentifierResolver<'_>,
6541        context: IdentifierContext,
6542        expr: &SqlExpr,
6543    ) -> SqlResult<String> {
6544        let normalized_expr = self.materialize_in_subquery(
6545            expr.clone(),
6546            SubqueryMaterializationMode::ExecuteScalarSubqueries,
6547        )?;
6548        let scalar = translate_scalar_with_context(resolver, context, &normalized_expr)?;
6549        match scalar {
6550            llkv_expr::expr::ScalarExpr::Column(column) => Ok(column),
6551            other => Err(Error::InvalidArgumentError(format!(
6552                "ORDER BY expression must reference a simple column, found {other:?}"
6553            ))),
6554        }
6555    }
6556
6557    fn detect_simple_aggregates(
6558        &self,
6559        projection_items: &[SelectItem],
6560    ) -> SqlResult<Option<Vec<AggregateExpr>>> {
6561        if projection_items.is_empty() {
6562            return Ok(None);
6563        }
6564
6565        let mut specs: Vec<AggregateExpr> = Vec::with_capacity(projection_items.len());
6566        for (idx, item) in projection_items.iter().enumerate() {
6567            let (expr, alias_opt) = match item {
6568                SelectItem::UnnamedExpr(expr) => (expr, None),
6569                SelectItem::ExprWithAlias { expr, alias } => (expr, Some(alias.value.clone())),
6570                _ => return Ok(None),
6571            };
6572
6573            let alias = alias_opt.unwrap_or_else(|| format!("col{}", idx + 1));
6574            let SqlExpr::Function(func) = expr else {
6575                return Ok(None);
6576            };
6577
6578            if func.uses_odbc_syntax {
6579                return Err(Error::InvalidArgumentError(
6580                    "ODBC function syntax is not supported in aggregate queries".into(),
6581                ));
6582            }
6583            if !matches!(func.parameters, FunctionArguments::None) {
6584                return Err(Error::InvalidArgumentError(
6585                    "parameterized aggregate functions are not supported".into(),
6586                ));
6587            }
6588            if func.filter.is_some()
6589                || func.null_treatment.is_some()
6590                || func.over.is_some()
6591                || !func.within_group.is_empty()
6592            {
6593                return Err(Error::InvalidArgumentError(
6594                    "advanced aggregate clauses are not supported".into(),
6595                ));
6596            }
6597
6598            let mut is_distinct = false;
6599            let args_slice: &[FunctionArg] = match &func.args {
6600                FunctionArguments::List(list) => {
6601                    if let Some(dup) = &list.duplicate_treatment {
6602                        use sqlparser::ast::DuplicateTreatment;
6603                        match dup {
6604                            DuplicateTreatment::All => {}
6605                            DuplicateTreatment::Distinct => is_distinct = true,
6606                        }
6607                    }
6608                    if !list.clauses.is_empty() {
6609                        return Err(Error::InvalidArgumentError(
6610                            "aggregate argument clauses are not supported".into(),
6611                        ));
6612                    }
6613                    &list.args
6614                }
6615                FunctionArguments::None => &[],
6616                FunctionArguments::Subquery(_) => {
6617                    return Err(Error::InvalidArgumentError(
6618                        "aggregate subquery arguments are not supported".into(),
6619                    ));
6620                }
6621            };
6622
6623            let func_name = if func.name.0.len() == 1 {
6624                match &func.name.0[0] {
6625                    ObjectNamePart::Identifier(ident) => ident.value.to_ascii_lowercase(),
6626                    _ => {
6627                        return Err(Error::InvalidArgumentError(
6628                            "unsupported aggregate function name".into(),
6629                        ));
6630                    }
6631                }
6632            } else {
6633                return Err(Error::InvalidArgumentError(
6634                    "qualified aggregate function names are not supported".into(),
6635                ));
6636            };
6637
6638            let aggregate = match func_name.as_str() {
6639                "count" => {
6640                    if args_slice.len() != 1 {
6641                        return Err(Error::InvalidArgumentError(
6642                            "COUNT accepts exactly one argument".into(),
6643                        ));
6644                    }
6645                    match &args_slice[0] {
6646                        FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => {
6647                            if is_distinct {
6648                                return Err(Error::InvalidArgumentError(
6649                                    "DISTINCT aggregates must be applied to columns not *, e.g. table columns like: 1,0,2,2".into(),
6650                                ));
6651                            }
6652                            AggregateExpr::count_star(alias, false)
6653                        }
6654                        FunctionArg::Unnamed(FunctionArgExpr::Expr(arg_expr)) => {
6655                            if !is_simple_aggregate_column(arg_expr) {
6656                                return Ok(None);
6657                            }
6658                            let column = resolve_column_name(arg_expr)?;
6659                            AggregateExpr::count_column(column, alias, is_distinct)
6660                        }
6661                        FunctionArg::Named { .. } | FunctionArg::ExprNamed { .. } => {
6662                            return Err(Error::InvalidArgumentError(
6663                                "named COUNT arguments are not supported".into(),
6664                            ));
6665                        }
6666                        FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(_)) => {
6667                            return Err(Error::InvalidArgumentError(
6668                                "COUNT does not support qualified wildcards".into(),
6669                            ));
6670                        }
6671                    }
6672                }
6673                "sum" | "min" | "max" => {
6674                    if args_slice.len() != 1 {
6675                        return Err(Error::InvalidArgumentError(format!(
6676                            "{} accepts exactly one argument",
6677                            func_name.to_uppercase()
6678                        )));
6679                    }
6680                    let arg_expr = match &args_slice[0] {
6681                        FunctionArg::Unnamed(FunctionArgExpr::Expr(arg_expr)) => arg_expr,
6682                        FunctionArg::Unnamed(FunctionArgExpr::Wildcard)
6683                        | FunctionArg::Unnamed(FunctionArgExpr::QualifiedWildcard(_)) => {
6684                            return Err(Error::InvalidArgumentError(format!(
6685                                "{} does not support wildcard arguments",
6686                                func_name.to_uppercase()
6687                            )));
6688                        }
6689                        FunctionArg::Named { .. } | FunctionArg::ExprNamed { .. } => {
6690                            return Err(Error::InvalidArgumentError(format!(
6691                                "{} arguments must be column references",
6692                                func_name.to_uppercase()
6693                            )));
6694                        }
6695                    };
6696
6697                    if is_distinct {
6698                        return Ok(None);
6699                    }
6700
6701                    if func_name == "sum" {
6702                        if let Some(column) = parse_count_nulls_case(arg_expr)? {
6703                            AggregateExpr::count_nulls(column, alias)
6704                        } else {
6705                            if !is_simple_aggregate_column(arg_expr) {
6706                                return Ok(None);
6707                            }
6708                            let column = resolve_column_name(arg_expr)?;
6709                            AggregateExpr::sum_int64(column, alias)
6710                        }
6711                    } else {
6712                        if !is_simple_aggregate_column(arg_expr) {
6713                            return Ok(None);
6714                        }
6715                        let column = resolve_column_name(arg_expr)?;
6716                        if func_name == "min" {
6717                            AggregateExpr::min_int64(column, alias)
6718                        } else {
6719                            AggregateExpr::max_int64(column, alias)
6720                        }
6721                    }
6722                }
6723                _ => return Ok(None),
6724            };
6725
6726            specs.push(aggregate);
6727        }
6728
6729        if specs.is_empty() {
6730            return Ok(None);
6731        }
6732        Ok(Some(specs))
6733    }
6734
6735    fn build_projection_list(
6736        &self,
6737        resolver: &IdentifierResolver<'_>,
6738        id_context: IdentifierContext,
6739        projection_items: &[SelectItem],
6740        outer_scopes: &[IdentifierContext],
6741        scalar_subqueries: &mut Vec<llkv_plan::ScalarSubquery>,
6742        mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
6743    ) -> SqlResult<Vec<SelectProjection>> {
6744        if projection_items.is_empty() {
6745            return Err(Error::InvalidArgumentError(
6746                "SELECT projection must include at least one column".into(),
6747            ));
6748        }
6749
6750        let mut projections = Vec::with_capacity(projection_items.len());
6751        for (idx, item) in projection_items.iter().enumerate() {
6752            match item {
6753                SelectItem::Wildcard(options) => {
6754                    if let Some(exclude) = &options.opt_exclude {
6755                        use sqlparser::ast::ExcludeSelectItem;
6756                        let exclude_cols = match exclude {
6757                            ExcludeSelectItem::Single(ident) => vec![ident.value.clone()],
6758                            ExcludeSelectItem::Multiple(idents) => {
6759                                idents.iter().map(|id| id.value.clone()).collect()
6760                            }
6761                        };
6762                        projections.push(SelectProjection::AllColumnsExcept {
6763                            exclude: exclude_cols,
6764                        });
6765                    } else {
6766                        projections.push(SelectProjection::AllColumns);
6767                    }
6768                }
6769                SelectItem::QualifiedWildcard(kind, _) => match kind {
6770                    SelectItemQualifiedWildcardKind::ObjectName(name) => {
6771                        projections.push(SelectProjection::Column {
6772                            name: name.to_string(),
6773                            alias: None,
6774                        });
6775                    }
6776                    SelectItemQualifiedWildcardKind::Expr(_) => {
6777                        return Err(Error::InvalidArgumentError(
6778                            "expression-qualified wildcards are not supported".into(),
6779                        ));
6780                    }
6781                },
6782                SelectItem::UnnamedExpr(expr) => match expr {
6783                    SqlExpr::Identifier(ident) => {
6784                        let parts = vec![ident.value.clone()];
6785                        let resolution = resolver.resolve(&parts, id_context.clone())?;
6786                        if resolution.is_simple() {
6787                            projections.push(SelectProjection::Column {
6788                                name: resolution.column().to_string(),
6789                                alias: None,
6790                            });
6791                        } else {
6792                            let alias = format!("col{}", idx + 1);
6793                            projections.push(SelectProjection::Computed {
6794                                expr: resolution.into_scalar_expr(),
6795                                alias,
6796                            });
6797                        }
6798                    }
6799                    SqlExpr::CompoundIdentifier(parts) => {
6800                        let name_parts: Vec<String> =
6801                            parts.iter().map(|part| part.value.clone()).collect();
6802                        let resolution = resolver.resolve(&name_parts, id_context.clone())?;
6803                        if resolution.is_simple() {
6804                            projections.push(SelectProjection::Column {
6805                                name: resolution.column().to_string(),
6806                                alias: None,
6807                            });
6808                        } else {
6809                            let alias = format!("col{}", idx + 1);
6810                            projections.push(SelectProjection::Computed {
6811                                expr: resolution.into_scalar_expr(),
6812                                alias,
6813                            });
6814                        }
6815                    }
6816                    _ => {
6817                        // Use the original SQL expression string as the alias for complex expressions.
6818                        // This preserves operators like unary plus (e.g., "+ col2" rather than "col2").
6819                        let alias = expr.to_string();
6820                        let normalized_expr = if matches!(expr, SqlExpr::Subquery(_)) {
6821                            expr.clone()
6822                        } else {
6823                            self.materialize_in_subquery(
6824                                expr.clone(),
6825                                SubqueryMaterializationMode::PreserveScalarSubqueries,
6826                            )?
6827                        };
6828                        let scalar = {
6829                            let tracker_view = correlated_tracker.reborrow();
6830                            let mut builder = ScalarSubqueryPlanner {
6831                                engine: self,
6832                                scalar_subqueries,
6833                            };
6834                            let mut tracker_wrapper =
6835                                SubqueryCorrelatedTracker::from_option(tracker_view);
6836                            translate_scalar_internal(
6837                                &normalized_expr,
6838                                Some(resolver),
6839                                Some(&id_context),
6840                                outer_scopes,
6841                                &mut tracker_wrapper,
6842                                Some(&mut builder),
6843                            )?
6844                        };
6845                        projections.push(SelectProjection::Computed {
6846                            expr: scalar,
6847                            alias,
6848                        });
6849                    }
6850                },
6851                SelectItem::ExprWithAlias { expr, alias } => match expr {
6852                    SqlExpr::Identifier(ident) => {
6853                        let parts = vec![ident.value.clone()];
6854                        let resolution = resolver.resolve(&parts, id_context.clone())?;
6855                        if resolution.is_simple() {
6856                            projections.push(SelectProjection::Column {
6857                                name: resolution.column().to_string(),
6858                                alias: Some(alias.value.clone()),
6859                            });
6860                        } else {
6861                            projections.push(SelectProjection::Computed {
6862                                expr: resolution.into_scalar_expr(),
6863                                alias: alias.value.clone(),
6864                            });
6865                        }
6866                    }
6867                    SqlExpr::CompoundIdentifier(parts) => {
6868                        let name_parts: Vec<String> =
6869                            parts.iter().map(|part| part.value.clone()).collect();
6870                        let resolution = resolver.resolve(&name_parts, id_context.clone())?;
6871                        if resolution.is_simple() {
6872                            projections.push(SelectProjection::Column {
6873                                name: resolution.column().to_string(),
6874                                alias: Some(alias.value.clone()),
6875                            });
6876                        } else {
6877                            projections.push(SelectProjection::Computed {
6878                                expr: resolution.into_scalar_expr(),
6879                                alias: alias.value.clone(),
6880                            });
6881                        }
6882                    }
6883                    _ => {
6884                        let normalized_expr = if matches!(expr, SqlExpr::Subquery(_)) {
6885                            expr.clone()
6886                        } else {
6887                            self.materialize_in_subquery(
6888                                expr.clone(),
6889                                SubqueryMaterializationMode::PreserveScalarSubqueries,
6890                            )?
6891                        };
6892                        let scalar = {
6893                            let tracker_view = correlated_tracker.reborrow();
6894                            let mut builder = ScalarSubqueryPlanner {
6895                                engine: self,
6896                                scalar_subqueries,
6897                            };
6898                            let mut tracker_wrapper =
6899                                SubqueryCorrelatedTracker::from_option(tracker_view);
6900                            translate_scalar_internal(
6901                                &normalized_expr,
6902                                Some(resolver),
6903                                Some(&id_context),
6904                                outer_scopes,
6905                                &mut tracker_wrapper,
6906                                Some(&mut builder),
6907                            )?
6908                        };
6909                        projections.push(SelectProjection::Computed {
6910                            expr: scalar,
6911                            alias: alias.value.clone(),
6912                        });
6913                    }
6914                },
6915            }
6916        }
6917        Ok(projections)
6918    }
6919
6920    #[allow(clippy::too_many_arguments)] // NOTE: Keeps parity with SQL START TRANSACTION grammar; revisit when options expand.
6921    fn handle_start_transaction(
6922        &self,
6923        modes: Vec<TransactionMode>,
6924        begin: bool,
6925        transaction: Option<BeginTransactionKind>,
6926        modifier: Option<TransactionModifier>,
6927        statements: Vec<Statement>,
6928        exception: Option<Vec<ExceptionWhen>>,
6929        has_end_keyword: bool,
6930    ) -> SqlResult<RuntimeStatementResult<P>> {
6931        if !modes.is_empty() {
6932            return Err(Error::InvalidArgumentError(
6933                "transaction modes are not supported".into(),
6934            ));
6935        }
6936        if modifier.is_some() {
6937            return Err(Error::InvalidArgumentError(
6938                "transaction modifiers are not supported".into(),
6939            ));
6940        }
6941        if !statements.is_empty() || exception.is_some() || has_end_keyword {
6942            return Err(Error::InvalidArgumentError(
6943                "BEGIN blocks with inline statements or exceptions are not supported".into(),
6944            ));
6945        }
6946        if let Some(kind) = transaction {
6947            match kind {
6948                BeginTransactionKind::Transaction | BeginTransactionKind::Work => {}
6949            }
6950        }
6951        if !begin {
6952            // Currently treat START TRANSACTION same as BEGIN
6953            tracing::warn!("Currently treat `START TRANSACTION` same as `BEGIN`")
6954        }
6955
6956        self.execute_plan_statement(PlanStatement::BeginTransaction)
6957    }
6958
6959    fn handle_commit(
6960        &self,
6961        chain: bool,
6962        end: bool,
6963        modifier: Option<TransactionModifier>,
6964    ) -> SqlResult<RuntimeStatementResult<P>> {
6965        if chain {
6966            return Err(Error::InvalidArgumentError(
6967                "COMMIT AND [NO] CHAIN is not supported".into(),
6968            ));
6969        }
6970        if end {
6971            return Err(Error::InvalidArgumentError(
6972                "END blocks are not supported".into(),
6973            ));
6974        }
6975        if modifier.is_some() {
6976            return Err(Error::InvalidArgumentError(
6977                "transaction modifiers are not supported".into(),
6978            ));
6979        }
6980
6981        self.execute_plan_statement(PlanStatement::CommitTransaction)
6982    }
6983
6984    fn handle_rollback(
6985        &self,
6986        chain: bool,
6987        savepoint: Option<Ident>,
6988    ) -> SqlResult<RuntimeStatementResult<P>> {
6989        if chain {
6990            return Err(Error::InvalidArgumentError(
6991                "ROLLBACK AND [NO] CHAIN is not supported".into(),
6992            ));
6993        }
6994        if savepoint.is_some() {
6995            return Err(Error::InvalidArgumentError(
6996                "ROLLBACK TO SAVEPOINT is not supported".into(),
6997            ));
6998        }
6999
7000        self.execute_plan_statement(PlanStatement::RollbackTransaction)
7001    }
7002
7003    fn handle_set(&self, set_stmt: Set) -> SqlResult<RuntimeStatementResult<P>> {
7004        match set_stmt {
7005            Set::SingleAssignment {
7006                scope,
7007                hivevar,
7008                variable,
7009                values,
7010            } => {
7011                if scope.is_some() || hivevar {
7012                    return Err(Error::InvalidArgumentError(
7013                        "SET modifiers are not supported".into(),
7014                    ));
7015                }
7016
7017                let variable_name_raw = variable.to_string();
7018                let variable_name = variable_name_raw.to_ascii_lowercase();
7019
7020                match variable_name.as_str() {
7021                    "default_null_order" => {
7022                        if values.len() != 1 {
7023                            return Err(Error::InvalidArgumentError(
7024                                "SET default_null_order expects exactly one value".into(),
7025                            ));
7026                        }
7027
7028                        let value_expr = &values[0];
7029                        let normalized = match value_expr {
7030                            SqlExpr::Value(value_with_span) => value_with_span
7031                                .value
7032                                .clone()
7033                                .into_string()
7034                                .map(|s| s.to_ascii_lowercase()),
7035                            SqlExpr::Identifier(ident) => Some(ident.value.to_ascii_lowercase()),
7036                            _ => None,
7037                        };
7038
7039                        if !matches!(normalized.as_deref(), Some("nulls_first" | "nulls_last")) {
7040                            return Err(Error::InvalidArgumentError(format!(
7041                                "unsupported value for SET default_null_order: {value_expr:?}"
7042                            )));
7043                        }
7044
7045                        let use_nulls_first = matches!(normalized.as_deref(), Some("nulls_first"));
7046                        self.default_nulls_first
7047                            .store(use_nulls_first, AtomicOrdering::Relaxed);
7048
7049                        Ok(RuntimeStatementResult::NoOp)
7050                    }
7051                    "constraint_enforcement_mode" => {
7052                        if values.len() != 1 {
7053                            return Err(Error::InvalidArgumentError(
7054                                "SET constraint_enforcement_mode expects exactly one value".into(),
7055                            ));
7056                        }
7057
7058                        let normalized = match &values[0] {
7059                            SqlExpr::Value(value_with_span) => value_with_span
7060                                .value
7061                                .clone()
7062                                .into_string()
7063                                .map(|s| s.to_ascii_lowercase()),
7064                            SqlExpr::Identifier(ident) => Some(ident.value.to_ascii_lowercase()),
7065                            _ => None,
7066                        };
7067
7068                        let mode = match normalized.as_deref() {
7069                            Some("immediate") => ConstraintEnforcementMode::Immediate,
7070                            Some("deferred") => ConstraintEnforcementMode::Deferred,
7071                            _ => {
7072                                return Err(Error::InvalidArgumentError(format!(
7073                                    "unsupported value for SET constraint_enforcement_mode: {}",
7074                                    values[0]
7075                                )));
7076                            }
7077                        };
7078
7079                        self.engine.session().set_constraint_enforcement_mode(mode);
7080
7081                        Ok(RuntimeStatementResult::NoOp)
7082                    }
7083                    "immediate_transaction_mode" => {
7084                        if values.len() != 1 {
7085                            return Err(Error::InvalidArgumentError(
7086                                "SET immediate_transaction_mode expects exactly one value".into(),
7087                            ));
7088                        }
7089                        let normalized = values[0].to_string().to_ascii_lowercase();
7090                        let enabled = match normalized.as_str() {
7091                            "true" | "on" | "1" => true,
7092                            "false" | "off" | "0" => false,
7093                            _ => {
7094                                return Err(Error::InvalidArgumentError(format!(
7095                                    "unsupported value for SET immediate_transaction_mode: {}",
7096                                    values[0]
7097                                )));
7098                            }
7099                        };
7100                        if !enabled {
7101                            tracing::warn!(
7102                                "SET immediate_transaction_mode=false has no effect; continuing with auto mode"
7103                            );
7104                        }
7105                        Ok(RuntimeStatementResult::NoOp)
7106                    }
7107                    _ => Err(Error::InvalidArgumentError(format!(
7108                        "unsupported SET variable: {variable_name_raw}"
7109                    ))),
7110                }
7111            }
7112            other => Err(Error::InvalidArgumentError(format!(
7113                "unsupported SQL SET statement: {other:?}",
7114            ))),
7115        }
7116    }
7117
7118    fn handle_pragma(
7119        &self,
7120        name: ObjectName,
7121        value: Option<Value>,
7122        is_eq: bool,
7123    ) -> SqlResult<RuntimeStatementResult<P>> {
7124        let (display, canonical) = canonical_object_name(&name)?;
7125        if value.is_some() || is_eq {
7126            return Err(Error::InvalidArgumentError(format!(
7127                "PRAGMA '{display}' does not accept a value"
7128            )));
7129        }
7130
7131        match canonical.as_str() {
7132            "enable_verification" | "disable_verification" => Ok(RuntimeStatementResult::NoOp),
7133            _ => Err(Error::InvalidArgumentError(format!(
7134                "unsupported PRAGMA '{}'",
7135                display
7136            ))),
7137        }
7138    }
7139
7140    fn handle_vacuum(&self, vacuum: VacuumStatement) -> SqlResult<RuntimeStatementResult<P>> {
7141        // Only support REINDEX with a table name (which is treated as index name in LLKV)
7142        if vacuum.reindex {
7143            let index_name = vacuum.table_name.ok_or_else(|| {
7144                Error::InvalidArgumentError("REINDEX requires an index name".to_string())
7145            })?;
7146
7147            let (display_name, canonical_name) = canonical_object_name(&index_name)?;
7148
7149            let plan = ReindexPlan::new(display_name.clone()).with_canonical(canonical_name);
7150
7151            let statement = PlanStatement::Reindex(plan);
7152            self.engine.execute_statement(statement)
7153        } else {
7154            // Other VACUUM variants are not supported
7155            Err(Error::InvalidArgumentError(
7156                "Only REINDEX is supported; general VACUUM is not implemented".to_string(),
7157            ))
7158        }
7159    }
7160}
7161
7162fn bind_plan_parameters(
7163    plan: &mut PlanStatement,
7164    params: &[SqlParamValue],
7165    expected_count: usize,
7166) -> SqlResult<()> {
7167    if expected_count == 0 {
7168        return Ok(());
7169    }
7170
7171    match plan {
7172        PlanStatement::Update(update) => bind_update_plan_parameters(update, params),
7173        other => Err(Error::InvalidArgumentError(format!(
7174            "prepared execution is not yet supported for {:?}",
7175            other
7176        ))),
7177    }
7178}
7179
7180fn bind_update_plan_parameters(plan: &mut UpdatePlan, params: &[SqlParamValue]) -> SqlResult<()> {
7181    for assignment in &mut plan.assignments {
7182        match &mut assignment.value {
7183            AssignmentValue::Literal(value) => bind_plan_value(value, params)?,
7184            AssignmentValue::Expression(expr) => bind_scalar_expr(expr, params)?,
7185        }
7186    }
7187
7188    if let Some(filter) = &mut plan.filter {
7189        bind_predicate_expr(filter, params)?;
7190    }
7191
7192    Ok(())
7193}
7194
7195fn bind_plan_value(value: &mut PlanValue, params: &[SqlParamValue]) -> SqlResult<()> {
7196    match value {
7197        PlanValue::String(text) => {
7198            if let Some(index) = parse_placeholder_marker(text) {
7199                let param = params
7200                    .get(index.saturating_sub(1))
7201                    .ok_or_else(|| missing_parameter_error(index, params.len()))?;
7202                *value = param.as_plan_value();
7203            }
7204        }
7205        PlanValue::Struct(fields) => {
7206            for field in fields.values_mut() {
7207                bind_plan_value(field, params)?;
7208            }
7209        }
7210        PlanValue::Null
7211        | PlanValue::Integer(_)
7212        | PlanValue::Float(_)
7213        | PlanValue::Decimal(_)
7214        | PlanValue::Date32(_)
7215        | PlanValue::Interval(_) => {}
7216    }
7217    Ok(())
7218}
7219
7220fn bind_scalar_expr(
7221    expr: &mut llkv_expr::expr::ScalarExpr<String>,
7222    params: &[SqlParamValue],
7223) -> SqlResult<()> {
7224    use llkv_expr::expr::ScalarExpr;
7225
7226    match expr {
7227        ScalarExpr::Column(_) => {}
7228        ScalarExpr::Literal(lit) => bind_literal(lit, params)?,
7229        ScalarExpr::Binary { left, right, .. } => {
7230            bind_scalar_expr(left, params)?;
7231            bind_scalar_expr(right, params)?;
7232        }
7233        ScalarExpr::Not(inner) => bind_scalar_expr(inner, params)?,
7234        ScalarExpr::IsNull { expr: inner, .. } => bind_scalar_expr(inner, params)?,
7235        ScalarExpr::Aggregate(_) => {
7236            return Err(Error::InvalidArgumentError(
7237                "parameters inside aggregate expressions are not supported".into(),
7238            ));
7239        }
7240        ScalarExpr::GetField { base, .. } => bind_scalar_expr(base, params)?,
7241        ScalarExpr::Cast { expr: inner, .. } => bind_scalar_expr(inner, params)?,
7242        ScalarExpr::Compare { left, right, .. } => {
7243            bind_scalar_expr(left, params)?;
7244            bind_scalar_expr(right, params)?;
7245        }
7246        ScalarExpr::Coalesce(list) => {
7247            for item in list {
7248                bind_scalar_expr(item, params)?;
7249            }
7250        }
7251        ScalarExpr::ScalarSubquery(_) => {
7252            return Err(Error::InvalidArgumentError(
7253                "parameters inside scalar subqueries are not supported yet".into(),
7254            ));
7255        }
7256        ScalarExpr::Case {
7257            operand,
7258            branches,
7259            else_expr,
7260        } => {
7261            if let Some(op) = operand {
7262                bind_scalar_expr(op, params)?;
7263            }
7264            for (when, then) in branches {
7265                bind_scalar_expr(when, params)?;
7266                bind_scalar_expr(then, params)?;
7267            }
7268            if let Some(else_expr) = else_expr {
7269                bind_scalar_expr(else_expr, params)?;
7270            }
7271        }
7272        ScalarExpr::Random => {}
7273    }
7274
7275    Ok(())
7276}
7277
7278fn bind_predicate_expr(
7279    expr: &mut llkv_expr::expr::Expr<'static, String>,
7280    params: &[SqlParamValue],
7281) -> SqlResult<()> {
7282    use llkv_expr::expr::Expr;
7283
7284    match expr {
7285        Expr::And(list) | Expr::Or(list) => {
7286            for sub in list {
7287                bind_predicate_expr(sub, params)?;
7288            }
7289        }
7290        Expr::Not(inner) => bind_predicate_expr(inner, params)?,
7291        Expr::Pred(filter) => bind_filter_operator(&mut filter.op, params)?,
7292        Expr::Compare { left, right, .. } => {
7293            bind_scalar_expr(left, params)?;
7294            bind_scalar_expr(right, params)?;
7295        }
7296        Expr::InList {
7297            expr: inner, list, ..
7298        } => {
7299            bind_scalar_expr(inner, params)?;
7300            for item in list {
7301                bind_scalar_expr(item, params)?;
7302            }
7303        }
7304        Expr::IsNull { expr: inner, .. } => bind_scalar_expr(inner, params)?,
7305        Expr::Literal(_) => {}
7306        Expr::Exists(_) => {
7307            return Err(Error::InvalidArgumentError(
7308                "parameters inside EXISTS subqueries are not supported yet".into(),
7309            ));
7310        }
7311    }
7312    Ok(())
7313}
7314
7315fn bind_filter_operator(
7316    op: &mut llkv_expr::expr::Operator<'static>,
7317    params: &[SqlParamValue],
7318) -> SqlResult<()> {
7319    use llkv_expr::expr::Operator;
7320
7321    match op {
7322        Operator::Equals(lit)
7323        | Operator::GreaterThan(lit)
7324        | Operator::GreaterThanOrEquals(lit)
7325        | Operator::LessThan(lit)
7326        | Operator::LessThanOrEquals(lit) => bind_literal(lit, params),
7327        Operator::Range { lower, upper } => {
7328            bind_bound_literal(lower, params)?;
7329            bind_bound_literal(upper, params)
7330        }
7331        Operator::In(list) => {
7332            for lit in *list {
7333                if let Literal::String(text) = lit
7334                    && parse_placeholder_marker(text).is_some()
7335                {
7336                    return Err(Error::InvalidArgumentError(
7337                        "IN predicates do not yet support bound parameters".into(),
7338                    ));
7339                }
7340            }
7341            Ok(())
7342        }
7343        Operator::StartsWith { pattern, .. }
7344        | Operator::EndsWith { pattern, .. }
7345        | Operator::Contains { pattern, .. } => {
7346            if pattern.contains(PARAM_SENTINEL_PREFIX) {
7347                return Err(Error::InvalidArgumentError(
7348                    "LIKE-style predicates do not yet support bound parameters".into(),
7349                ));
7350            }
7351            Ok(())
7352        }
7353        Operator::IsNull | Operator::IsNotNull => Ok(()),
7354    }
7355}
7356
7357fn bind_bound_literal(bound: &mut Bound<Literal>, params: &[SqlParamValue]) -> SqlResult<()> {
7358    match bound {
7359        Bound::Included(lit) | Bound::Excluded(lit) => bind_literal(lit, params),
7360        Bound::Unbounded => Ok(()),
7361    }
7362}
7363
7364fn bind_literal(literal: &mut Literal, params: &[SqlParamValue]) -> SqlResult<()> {
7365    match literal {
7366        Literal::String(text) => {
7367            if let Some(index) = parse_placeholder_marker(text) {
7368                let param = params
7369                    .get(index.saturating_sub(1))
7370                    .ok_or_else(|| missing_parameter_error(index, params.len()))?;
7371                *literal = param.as_literal();
7372            }
7373            Ok(())
7374        }
7375        Literal::Struct(fields) => {
7376            for (_, value) in fields.iter_mut() {
7377                bind_literal(value, params)?;
7378            }
7379            Ok(())
7380        }
7381        Literal::Int128(_)
7382        | Literal::Float64(_)
7383        | Literal::Decimal128(_)
7384        | Literal::Boolean(_)
7385        | Literal::Null
7386        | Literal::Date32(_)
7387        | Literal::Interval(_) => Ok(()),
7388    }
7389}
7390
7391fn missing_parameter_error(index: usize, provided: usize) -> Error {
7392    Error::InvalidArgumentError(format!(
7393        "missing parameter value for placeholder {} ({} provided)",
7394        index, provided
7395    ))
7396}
7397
7398fn canonical_object_name(name: &ObjectName) -> SqlResult<(String, String)> {
7399    if name.0.is_empty() {
7400        return Err(Error::InvalidArgumentError(
7401            "object name must not be empty".into(),
7402        ));
7403    }
7404    let mut parts: Vec<String> = Vec::with_capacity(name.0.len());
7405    for part in &name.0 {
7406        let ident = match part {
7407            ObjectNamePart::Identifier(ident) => ident,
7408            _ => {
7409                return Err(Error::InvalidArgumentError(
7410                    "object names using functions are not supported".into(),
7411                ));
7412            }
7413        };
7414        parts.push(ident.value.clone());
7415    }
7416    let display = parts.join(".");
7417    let canonical = display.to_ascii_lowercase();
7418    Ok((display, canonical))
7419}
7420
7421/// Parse an object name into optional schema and table name components.
7422///
7423/// Returns (schema_name, table_name) where schema_name is None if not qualified.
7424///
7425/// Examples:
7426/// - "users" -> (None, "users")
7427/// - "test.users" -> (Some("test"), "users")
7428/// - "catalog.test.users" -> Error (too many parts)
7429fn parse_schema_qualified_name(name: &ObjectName) -> SqlResult<(Option<String>, String)> {
7430    if name.0.is_empty() {
7431        return Err(Error::InvalidArgumentError(
7432            "object name must not be empty".into(),
7433        ));
7434    }
7435
7436    let mut parts: Vec<String> = Vec::with_capacity(name.0.len());
7437    for part in &name.0 {
7438        let ident = match part {
7439            ObjectNamePart::Identifier(ident) => ident,
7440            _ => {
7441                return Err(Error::InvalidArgumentError(
7442                    "object names using functions are not supported".into(),
7443                ));
7444            }
7445        };
7446        parts.push(ident.value.clone());
7447    }
7448
7449    match parts.len() {
7450        1 => Ok((None, parts[0].clone())),
7451        2 => Ok((Some(parts[0].clone()), parts[1].clone())),
7452        _ => Err(Error::InvalidArgumentError(format!(
7453            "table name has too many parts: {}",
7454            name
7455        ))),
7456    }
7457}
7458
7459/// Extract column name from an index column specification (OrderBy expression).
7460///
7461/// This handles the common pattern of validating and extracting column names from
7462/// SQL index column definitions (PRIMARY KEY, UNIQUE, CREATE INDEX).
7463///
7464/// # Parameters
7465/// - `index_col`: The index column from sqlparser AST
7466/// - `context`: Description of where this column appears (e.g., "PRIMARY KEY", "UNIQUE constraint")
7467/// - `allow_sort_options`: If false, errors if any sort options are present; if true, validates them
7468/// - `allow_compound`: If true, allows compound identifiers and takes the last part; if false, only allows simple identifiers
7469///
7470/// # Returns
7471/// Column name as a String
7472fn extract_index_column_name(
7473    index_col: &sqlparser::ast::IndexColumn,
7474    context: &str,
7475    allow_sort_options: bool,
7476    allow_compound: bool,
7477) -> SqlResult<String> {
7478    use sqlparser::ast::Expr as SqlExpr;
7479
7480    // Check operator class
7481    if index_col.operator_class.is_some() {
7482        return Err(Error::InvalidArgumentError(format!(
7483            "{} operator classes are not supported",
7484            context
7485        )));
7486    }
7487
7488    let order_expr = &index_col.column;
7489
7490    // Validate sort options
7491    if allow_sort_options {
7492        // For CREATE INDEX: extract and validate sort options
7493        let _ascending = order_expr.options.asc.unwrap_or(true);
7494        let _nulls_first = order_expr.options.nulls_first.unwrap_or(false);
7495        // DESC and NULLS FIRST are now supported
7496    } else {
7497        // For constraints: no sort options allowed
7498        if order_expr.options.asc.is_some()
7499            || order_expr.options.nulls_first.is_some()
7500            || order_expr.with_fill.is_some()
7501        {
7502            return Err(Error::InvalidArgumentError(format!(
7503                "{} columns must be simple identifiers",
7504                context
7505            )));
7506        }
7507    }
7508
7509    // Extract column name from expression
7510    let column_name = match &order_expr.expr {
7511        SqlExpr::Identifier(ident) => ident.value.clone(),
7512        SqlExpr::CompoundIdentifier(parts) => {
7513            if allow_compound {
7514                // For CREATE INDEX: allow qualified names, take last part
7515                parts
7516                    .last()
7517                    .map(|ident| ident.value.clone())
7518                    .ok_or_else(|| {
7519                        Error::InvalidArgumentError(format!(
7520                            "invalid column reference in {}",
7521                            context
7522                        ))
7523                    })?
7524            } else if parts.len() == 1 {
7525                // For constraints: only allow single-part compound identifiers
7526                parts[0].value.clone()
7527            } else {
7528                return Err(Error::InvalidArgumentError(format!(
7529                    "{} columns must be column identifiers",
7530                    context
7531                )));
7532            }
7533        }
7534        other => {
7535            return Err(Error::InvalidArgumentError(format!(
7536                "{} only supports column references, found {:?}",
7537                context, other
7538            )));
7539        }
7540    };
7541
7542    Ok(column_name)
7543}
7544
7545fn validate_create_table_common(stmt: &sqlparser::ast::CreateTable) -> SqlResult<()> {
7546    if stmt.clone.is_some() || stmt.like.is_some() {
7547        return Err(Error::InvalidArgumentError(
7548            "CREATE TABLE LIKE/CLONE is not supported".into(),
7549        ));
7550    }
7551    if stmt.or_replace && stmt.if_not_exists {
7552        return Err(Error::InvalidArgumentError(
7553            "CREATE TABLE cannot combine OR REPLACE with IF NOT EXISTS".into(),
7554        ));
7555    }
7556    use sqlparser::ast::TableConstraint;
7557
7558    let mut seen_primary_key = false;
7559    for constraint in &stmt.constraints {
7560        match constraint {
7561            TableConstraint::PrimaryKey { .. } => {
7562                if seen_primary_key {
7563                    return Err(Error::InvalidArgumentError(
7564                        "multiple PRIMARY KEY constraints are not supported".into(),
7565                    ));
7566                }
7567                seen_primary_key = true;
7568            }
7569            TableConstraint::Unique { .. } => {
7570                // Detailed validation is performed later during plan construction.
7571            }
7572            TableConstraint::ForeignKey { .. } => {
7573                // Detailed validation is performed later during plan construction.
7574            }
7575            other => {
7576                return Err(Error::InvalidArgumentError(format!(
7577                    "table-level constraint {:?} is not supported",
7578                    other
7579                )));
7580            }
7581        }
7582    }
7583    Ok(())
7584}
7585
7586fn validate_check_constraint(
7587    check_expr: &sqlparser::ast::Expr,
7588    table_name: &str,
7589    column_names: &[&str],
7590) -> SqlResult<()> {
7591    use sqlparser::ast::Expr as SqlExpr;
7592
7593    let column_names_lower: FxHashSet<String> = column_names
7594        .iter()
7595        .map(|name| name.to_ascii_lowercase())
7596        .collect();
7597
7598    let mut stack: Vec<&SqlExpr> = vec![check_expr];
7599
7600    while let Some(expr) = stack.pop() {
7601        match expr {
7602            SqlExpr::Subquery(_) => {
7603                return Err(Error::InvalidArgumentError(
7604                    "Subqueries are not allowed in CHECK constraints".into(),
7605                ));
7606            }
7607            SqlExpr::Function(func) => {
7608                let func_name = func.name.to_string().to_uppercase();
7609                if matches!(func_name.as_str(), "SUM" | "AVG" | "COUNT" | "MIN" | "MAX") {
7610                    return Err(Error::InvalidArgumentError(
7611                        "Aggregate functions are not allowed in CHECK constraints".into(),
7612                    ));
7613                }
7614
7615                if let sqlparser::ast::FunctionArguments::List(list) = &func.args {
7616                    for arg in &list.args {
7617                        if let sqlparser::ast::FunctionArg::Unnamed(
7618                            sqlparser::ast::FunctionArgExpr::Expr(expr),
7619                        ) = arg
7620                        {
7621                            stack.push(expr);
7622                        }
7623                    }
7624                }
7625            }
7626            SqlExpr::Identifier(ident) => {
7627                if !column_names_lower.contains(&ident.value.to_ascii_lowercase()) {
7628                    return Err(Error::InvalidArgumentError(format!(
7629                        "Column '{}' referenced in CHECK constraint does not exist",
7630                        ident.value
7631                    )));
7632                }
7633            }
7634            SqlExpr::CompoundIdentifier(idents) => {
7635                if idents.len() == 2 {
7636                    let first = idents[0].value.as_str();
7637                    let second = &idents[1].value;
7638
7639                    if column_names_lower.contains(&first.to_ascii_lowercase()) {
7640                        continue;
7641                    }
7642
7643                    if !first.eq_ignore_ascii_case(table_name) {
7644                        return Err(Error::InvalidArgumentError(format!(
7645                            "CHECK constraint references column from different table '{}'",
7646                            first
7647                        )));
7648                    }
7649
7650                    if !column_names_lower.contains(&second.to_ascii_lowercase()) {
7651                        return Err(Error::InvalidArgumentError(format!(
7652                            "Column '{}' referenced in CHECK constraint does not exist",
7653                            second
7654                        )));
7655                    }
7656                } else if idents.len() == 3 {
7657                    let first = &idents[0].value;
7658                    let second = &idents[1].value;
7659                    let third = &idents[2].value;
7660
7661                    if first.eq_ignore_ascii_case(table_name) {
7662                        if !column_names_lower.contains(&second.to_ascii_lowercase()) {
7663                            return Err(Error::InvalidArgumentError(format!(
7664                                "Column '{}' referenced in CHECK constraint does not exist",
7665                                second
7666                            )));
7667                        }
7668                    } else if second.eq_ignore_ascii_case(table_name) {
7669                        if !column_names_lower.contains(&third.to_ascii_lowercase()) {
7670                            return Err(Error::InvalidArgumentError(format!(
7671                                "Column '{}' referenced in CHECK constraint does not exist",
7672                                third
7673                            )));
7674                        }
7675                    } else {
7676                        return Err(Error::InvalidArgumentError(format!(
7677                            "CHECK constraint references column from different table '{}'",
7678                            second
7679                        )));
7680                    }
7681                }
7682            }
7683            SqlExpr::BinaryOp { left, right, .. } => {
7684                stack.push(left);
7685                stack.push(right);
7686            }
7687            SqlExpr::UnaryOp { expr, .. } | SqlExpr::Nested(expr) => {
7688                stack.push(expr);
7689            }
7690            SqlExpr::Value(_) | SqlExpr::TypedString { .. } => {}
7691            _ => {}
7692        }
7693    }
7694
7695    Ok(())
7696}
7697
7698fn validate_create_table_definition(stmt: &sqlparser::ast::CreateTable) -> SqlResult<()> {
7699    for column in &stmt.columns {
7700        for ColumnOptionDef { option, .. } in &column.options {
7701            match option {
7702                ColumnOption::Null
7703                | ColumnOption::NotNull
7704                | ColumnOption::Unique { .. }
7705                | ColumnOption::Check(_)
7706                | ColumnOption::ForeignKey { .. } => {}
7707                ColumnOption::Default(_) => {
7708                    return Err(Error::InvalidArgumentError(format!(
7709                        "DEFAULT values are not supported for column '{}'",
7710                        column.name
7711                    )));
7712                }
7713                other => {
7714                    return Err(Error::InvalidArgumentError(format!(
7715                        "unsupported column option {:?} on '{}'",
7716                        other, column.name
7717                    )));
7718                }
7719            }
7720        }
7721    }
7722    Ok(())
7723}
7724
7725/// Validate that a table name does not conflict with reserved schema names.
7726///
7727/// SQL databases reserve certain schema names (like `information_schema`) that cannot
7728/// be used as unqualified table names in the default namespace. This prevents user tables
7729/// from shadowing system tables.
7730///
7731/// # Examples
7732/// - ❌ `CREATE TABLE information_schema (...)` - conflicts with system schema
7733/// - ✅ `CREATE TABLE my_table (...)` - allowed
7734/// - ✅ `CREATE TABLE myschema.information_schema (...)` - allowed (different namespace)
7735fn validate_reserved_table_name(schema: Option<&str>, table_name: &str) -> SqlResult<()> {
7736    // If schema is explicitly provided, we're not in the default namespace
7737    if schema.is_some() {
7738        return Ok(());
7739    }
7740
7741    // Check if the unqualified table name conflicts with a reserved schema
7742    let canonical = table_name.to_ascii_lowercase();
7743    if canonical == "information_schema" {
7744        return Err(Error::InvalidArgumentError(
7745            "table name 'information_schema' is reserved (conflicts with system schema)".into(),
7746        ));
7747    }
7748
7749    Ok(())
7750}
7751
7752fn validate_create_table_as(stmt: &sqlparser::ast::CreateTable) -> SqlResult<()> {
7753    if !stmt.columns.is_empty() {
7754        return Err(Error::InvalidArgumentError(
7755            "CREATE TABLE AS SELECT does not support column definitions yet".into(),
7756        ));
7757    }
7758    Ok(())
7759}
7760
7761#[derive(Clone)]
7762struct InlineColumn {
7763    name: &'static str,
7764    data_type: DataType,
7765    nullable: bool,
7766}
7767
7768impl InlineColumn {
7769    fn utf8(name: &'static str, nullable: bool) -> Self {
7770        Self {
7771            name,
7772            data_type: DataType::Utf8,
7773            nullable,
7774        }
7775    }
7776
7777    fn bool(name: &'static str, nullable: bool) -> Self {
7778        Self {
7779            name,
7780            data_type: DataType::Boolean,
7781            nullable,
7782        }
7783    }
7784
7785    fn int32(name: &'static str, nullable: bool) -> Self {
7786        Self {
7787            name,
7788            data_type: DataType::Int32,
7789            nullable,
7790        }
7791    }
7792}
7793
7794#[derive(Clone)]
7795struct InlineRow {
7796    values: Vec<InlineValue>,
7797}
7798
7799#[derive(Clone, Debug)]
7800enum InlineValue {
7801    String(Option<String>),
7802    Int32(Option<i32>),
7803    Bool(Option<bool>),
7804    Null,
7805}
7806
7807struct InlineView {
7808    columns: Vec<InlineColumn>,
7809    rows: Vec<InlineRow>,
7810}
7811
7812impl InlineView {
7813    fn new(columns: Vec<InlineColumn>) -> Self {
7814        Self {
7815            columns,
7816            rows: Vec::new(),
7817        }
7818    }
7819
7820    fn add_row(&mut self, values: Vec<InlineValue>) -> SqlResult<()> {
7821        if values.len() != self.columns.len() {
7822            return Err(Error::Internal(
7823                "inline row does not match column layout".into(),
7824            ));
7825        }
7826        for (value, column) in values.iter().zip(self.columns.iter()) {
7827            if !value.matches_type(&column.data_type) {
7828                return Err(Error::Internal(format!(
7829                    "inline value type mismatch for column {}",
7830                    column.name
7831                )));
7832            }
7833        }
7834        self.rows.push(InlineRow { values });
7835        Ok(())
7836    }
7837
7838    fn apply_selection(&mut self, expr: &SqlExpr, alias: Option<&str>) -> SqlResult<()> {
7839        let mut filtered = Vec::with_capacity(self.rows.len());
7840        for row in self.rows.drain(..) {
7841            if evaluate_predicate(expr, &row, &self.columns, alias)? {
7842                filtered.push(row);
7843            }
7844        }
7845        self.rows = filtered;
7846        Ok(())
7847    }
7848
7849    fn into_record_batch(self) -> SqlResult<(Arc<Schema>, RecordBatch)> {
7850        let mut fields = Vec::with_capacity(self.columns.len());
7851        for column in &self.columns {
7852            fields.push(Field::new(
7853                column.name,
7854                column.data_type.clone(),
7855                column.nullable,
7856            ));
7857        }
7858        let schema = Arc::new(Schema::new(fields));
7859
7860        let mut arrays: Vec<ArrayRef> = Vec::with_capacity(self.columns.len());
7861        for (idx, column) in self.columns.iter().enumerate() {
7862            match &column.data_type {
7863                DataType::Utf8 => {
7864                    let mut values = Vec::with_capacity(self.rows.len());
7865                    for row in &self.rows {
7866                        match &row.values[idx] {
7867                            InlineValue::String(val) => values.push(val.clone()),
7868                            InlineValue::Null => values.push(None),
7869                            other => {
7870                                return Err(Error::Internal(format!(
7871                                    "unexpected string value for column {}: {:?}",
7872                                    column.name, other
7873                                )));
7874                            }
7875                        }
7876                    }
7877                    arrays.push(Arc::new(StringArray::from(values)) as ArrayRef);
7878                }
7879                DataType::Boolean => {
7880                    let mut values = Vec::with_capacity(self.rows.len());
7881                    for row in &self.rows {
7882                        match &row.values[idx] {
7883                            InlineValue::Bool(val) => values.push(*val),
7884                            InlineValue::Null => values.push(None),
7885                            other => {
7886                                return Err(Error::Internal(format!(
7887                                    "unexpected boolean value for column {}: {:?}",
7888                                    column.name, other
7889                                )));
7890                            }
7891                        }
7892                    }
7893                    arrays.push(Arc::new(BooleanArray::from(values)) as ArrayRef);
7894                }
7895                DataType::Int32 => {
7896                    let mut values = Vec::with_capacity(self.rows.len());
7897                    for row in &self.rows {
7898                        match &row.values[idx] {
7899                            InlineValue::Int32(val) => values.push(*val),
7900                            InlineValue::Null => values.push(None),
7901                            other => {
7902                                return Err(Error::Internal(format!(
7903                                    "unexpected integer value for column {}: {:?}",
7904                                    column.name, other
7905                                )));
7906                            }
7907                        }
7908                    }
7909                    arrays.push(Arc::new(Int32Array::from(values)) as ArrayRef);
7910                }
7911                other => {
7912                    return Err(Error::Internal(format!(
7913                        "unsupported inline column type: {:?}",
7914                        other
7915                    )));
7916                }
7917            }
7918        }
7919
7920        let batch = RecordBatch::try_new(Arc::clone(&schema), arrays)
7921            .map_err(|e| Error::Internal(format!("failed to build inline batch: {}", e)))?;
7922        Ok((schema, batch))
7923    }
7924}
7925
7926impl InlineValue {
7927    fn is_null(&self) -> bool {
7928        matches!(
7929            self,
7930            InlineValue::Null
7931                | InlineValue::String(None)
7932                | InlineValue::Int32(None)
7933                | InlineValue::Bool(None)
7934        )
7935    }
7936
7937    fn matches_type(&self, data_type: &DataType) -> bool {
7938        if matches!(self, InlineValue::Null) {
7939            return true;
7940        }
7941        match data_type {
7942            DataType::Utf8 => matches!(self, InlineValue::String(_) | InlineValue::Null),
7943            DataType::Boolean => matches!(self, InlineValue::Bool(_) | InlineValue::Null),
7944            DataType::Int32 => matches!(self, InlineValue::Int32(_) | InlineValue::Null),
7945            _ => false,
7946        }
7947    }
7948
7949    fn as_bool(&self) -> Option<bool> {
7950        match self {
7951            InlineValue::Bool(value) => *value,
7952            _ => None,
7953        }
7954    }
7955}
7956
7957fn ensure_inline_select_supported(select: &Select, query: &Query) -> SqlResult<()> {
7958    if select.distinct.is_some() {
7959        return Err(Error::InvalidArgumentError(
7960            "DISTINCT is not supported for information_schema or pragma queries".into(),
7961        ));
7962    }
7963    if select.top.is_some() {
7964        return Err(Error::InvalidArgumentError(
7965            "TOP clauses are not supported for information_schema or pragma queries".into(),
7966        ));
7967    }
7968    if select.exclude.is_some()
7969        || select.into.is_some()
7970        || !select.lateral_views.is_empty()
7971        || select.prewhere.is_some()
7972    {
7973        return Err(Error::InvalidArgumentError(
7974            "the requested SELECT features are not supported for information_schema or pragma queries"
7975                .into(),
7976        ));
7977    }
7978    if !group_by_is_empty(&select.group_by) {
7979        return Err(Error::InvalidArgumentError(
7980            "GROUP BY is not supported for information_schema or pragma queries".into(),
7981        ));
7982    }
7983    if select.having.is_some() {
7984        return Err(Error::InvalidArgumentError(
7985            "HAVING clauses are not supported for information_schema or pragma queries".into(),
7986        ));
7987    }
7988    if !select.cluster_by.is_empty()
7989        || !select.distribute_by.is_empty()
7990        || !select.sort_by.is_empty()
7991        || select.value_table_mode.is_some()
7992        || select.connect_by.is_some()
7993        || !select.named_window.is_empty()
7994        || select.qualify.is_some()
7995    {
7996        return Err(Error::InvalidArgumentError(
7997            "the requested SELECT modifiers are not supported for information_schema or pragma queries"
7998                .into(),
7999        ));
8000    }
8001    if query.with.is_some() {
8002        return Err(Error::InvalidArgumentError(
8003            "WITH clauses are not supported for information_schema or pragma queries".into(),
8004        ));
8005    }
8006    if query.fetch.is_some() {
8007        return Err(Error::InvalidArgumentError(
8008            "FETCH clauses are not supported for information_schema or pragma queries".into(),
8009        ));
8010    }
8011    Ok(())
8012}
8013
8014fn extract_limit_count(query: &Query) -> SqlResult<Option<usize>> {
8015    use sqlparser::ast::{Expr, LimitClause};
8016
8017    let Some(limit_clause) = &query.limit_clause else {
8018        return Ok(None);
8019    };
8020
8021    match limit_clause {
8022        LimitClause::LimitOffset {
8023            limit,
8024            offset,
8025            limit_by,
8026        } => {
8027            if offset.is_some() || !limit_by.is_empty() {
8028                return Err(Error::InvalidArgumentError(
8029                    "OFFSET/LIMIT BY are not supported for information_schema or pragma queries"
8030                        .into(),
8031                ));
8032            }
8033            match limit {
8034                None => Ok(None),
8035                Some(Expr::Value(value)) => match &value.value {
8036                    sqlparser::ast::Value::Number(text, _) => {
8037                        let parsed = text.parse::<i64>().map_err(|_| {
8038                            Error::InvalidArgumentError("LIMIT must be a positive integer".into())
8039                        })?;
8040                        if parsed < 0 {
8041                            return Err(Error::InvalidArgumentError(
8042                                "LIMIT must be non-negative".into(),
8043                            ));
8044                        }
8045                        Ok(Some(parsed as usize))
8046                    }
8047                    sqlparser::ast::Value::SingleQuotedString(text) => {
8048                        let parsed = text.parse::<i64>().map_err(|_| {
8049                            Error::InvalidArgumentError("LIMIT must be a positive integer".into())
8050                        })?;
8051                        if parsed < 0 {
8052                            return Err(Error::InvalidArgumentError(
8053                                "LIMIT must be non-negative".into(),
8054                            ));
8055                        }
8056                        Ok(Some(parsed as usize))
8057                    }
8058                    _ => Err(Error::InvalidArgumentError(
8059                        "LIMIT must be a numeric literal for information_schema or pragma queries"
8060                            .into(),
8061                    )),
8062                },
8063                Some(_) => Err(Error::InvalidArgumentError(
8064                    "LIMIT must be a literal for information_schema or pragma queries".into(),
8065                )),
8066            }
8067        }
8068        LimitClause::OffsetCommaLimit { .. } => Err(Error::InvalidArgumentError(
8069            "LIMIT with comma offset is not supported for information_schema or pragma queries"
8070                .into(),
8071        )),
8072    }
8073}
8074
8075fn evaluate_predicate(
8076    expr: &SqlExpr,
8077    row: &InlineRow,
8078    columns: &[InlineColumn],
8079    alias: Option<&str>,
8080) -> SqlResult<bool> {
8081    match expr {
8082        SqlExpr::BinaryOp { left, op, right } => match op {
8083            BinaryOperator::And => Ok(evaluate_predicate(left, row, columns, alias)?
8084                && evaluate_predicate(right, row, columns, alias)?),
8085            BinaryOperator::Or => Ok(evaluate_predicate(left, row, columns, alias)?
8086                || evaluate_predicate(right, row, columns, alias)?),
8087            BinaryOperator::Eq
8088            | BinaryOperator::NotEq
8089            | BinaryOperator::Gt
8090            | BinaryOperator::Lt
8091            | BinaryOperator::GtEq
8092            | BinaryOperator::LtEq => {
8093                let left_value = evaluate_scalar(left, row, columns, alias)?;
8094                let right_value = evaluate_scalar(right, row, columns, alias)?;
8095                compare_values(&left_value, &right_value, op)
8096            }
8097            _ => Err(Error::InvalidArgumentError(format!(
8098                "unsupported operator '{}' in inline predicate",
8099                op
8100            ))),
8101        },
8102        SqlExpr::IsNull(inner) => Ok(evaluate_scalar(inner, row, columns, alias)?.is_null()),
8103        SqlExpr::IsNotNull(inner) => Ok(!evaluate_scalar(inner, row, columns, alias)?.is_null()),
8104        SqlExpr::Nested(inner) => evaluate_predicate(inner, row, columns, alias),
8105        SqlExpr::UnaryOp {
8106            op: UnaryOperator::Not,
8107            expr,
8108        } => Ok(!evaluate_predicate(expr, row, columns, alias)?),
8109        SqlExpr::Identifier(_) | SqlExpr::CompoundIdentifier(_) | SqlExpr::Value(_) => {
8110            let scalar = evaluate_scalar(expr, row, columns, alias)?;
8111            scalar
8112                .as_bool()
8113                .ok_or_else(|| Error::InvalidArgumentError("expression is not boolean".into()))
8114        }
8115        _ => Err(Error::InvalidArgumentError(
8116            "unsupported predicate in inline query".into(),
8117        )),
8118    }
8119}
8120
8121fn evaluate_scalar(
8122    expr: &SqlExpr,
8123    row: &InlineRow,
8124    columns: &[InlineColumn],
8125    alias: Option<&str>,
8126) -> SqlResult<InlineValue> {
8127    match expr {
8128        SqlExpr::Identifier(ident) => {
8129            let idx =
8130                column_index(columns, &ident.value).ok_or_else(|| invalid_column(&ident.value))?;
8131            Ok(row.values[idx].clone())
8132        }
8133        SqlExpr::CompoundIdentifier(parts) => {
8134            let column = resolve_identifier_from_parts(parts, alias).ok_or_else(|| {
8135                invalid_column(
8136                    &parts
8137                        .last()
8138                        .map(|ident| ident.value.clone())
8139                        .unwrap_or_else(|| "<unknown>".into()),
8140                )
8141            })?;
8142            let idx = column_index(columns, &column).ok_or_else(|| invalid_column(&column))?;
8143            Ok(row.values[idx].clone())
8144        }
8145        SqlExpr::Value(value) => literal_to_inline_value(value),
8146        SqlExpr::Nested(inner) => evaluate_scalar(inner, row, columns, alias),
8147        SqlExpr::UnaryOp {
8148            op: UnaryOperator::Plus,
8149            expr,
8150        } => evaluate_scalar(expr, row, columns, alias),
8151        SqlExpr::UnaryOp {
8152            op: UnaryOperator::Minus,
8153            expr,
8154        } => {
8155            let value = evaluate_scalar(expr, row, columns, alias)?;
8156            match value {
8157                InlineValue::Int32(Some(v)) => Ok(InlineValue::Int32(Some(-v))),
8158                InlineValue::Int32(None) => Ok(InlineValue::Int32(None)),
8159                InlineValue::Null => Ok(InlineValue::Null),
8160                _ => Err(Error::InvalidArgumentError(
8161                    "UNARY - is only supported for numeric expressions in inline queries".into(),
8162                )),
8163            }
8164        }
8165        _ => Err(Error::InvalidArgumentError(format!(
8166            "unsupported expression '{}' in inline query",
8167            expr
8168        ))),
8169    }
8170}
8171
8172fn resolve_identifier_from_parts(parts: &[Ident], alias: Option<&str>) -> Option<String> {
8173    if parts.is_empty() {
8174        return None;
8175    }
8176    if parts.len() == 1 {
8177        return Some(parts[0].value.clone());
8178    }
8179    if parts.len() == 2
8180        && let Some(alias_name) = alias
8181        && alias_name.eq_ignore_ascii_case(&parts[0].value)
8182    {
8183        return Some(parts[1].value.clone());
8184    }
8185    parts.last().map(|ident| ident.value.clone())
8186}
8187
8188fn column_index(columns: &[InlineColumn], name: &str) -> Option<usize> {
8189    columns
8190        .iter()
8191        .position(|column| column.name.eq_ignore_ascii_case(name))
8192}
8193
8194fn compare_values(left: &InlineValue, right: &InlineValue, op: &BinaryOperator) -> SqlResult<bool> {
8195    if left.is_null() || right.is_null() {
8196        return Ok(false);
8197    }
8198    match op {
8199        BinaryOperator::Eq => match (left, right) {
8200            (InlineValue::String(Some(a)), InlineValue::String(Some(b))) => Ok(a == b),
8201            (InlineValue::Int32(Some(a)), InlineValue::Int32(Some(b))) => Ok(a == b),
8202            (InlineValue::Bool(Some(a)), InlineValue::Bool(Some(b))) => Ok(a == b),
8203            _ => Err(Error::InvalidArgumentError(
8204                "type mismatch in comparison".into(),
8205            )),
8206        },
8207        BinaryOperator::NotEq => {
8208            compare_values(left, right, &BinaryOperator::Eq).map(|result| !result)
8209        }
8210        BinaryOperator::Gt | BinaryOperator::Lt | BinaryOperator::GtEq | BinaryOperator::LtEq => {
8211            match (left, right) {
8212                (InlineValue::String(Some(a)), InlineValue::String(Some(b))) => match op {
8213                    BinaryOperator::Gt => Ok(a > b),
8214                    BinaryOperator::Lt => Ok(a < b),
8215                    BinaryOperator::GtEq => Ok(a >= b),
8216                    BinaryOperator::LtEq => Ok(a <= b),
8217                    _ => unreachable!(),
8218                },
8219                (InlineValue::Int32(Some(a)), InlineValue::Int32(Some(b))) => match op {
8220                    BinaryOperator::Gt => Ok(a > b),
8221                    BinaryOperator::Lt => Ok(a < b),
8222                    BinaryOperator::GtEq => Ok(a >= b),
8223                    BinaryOperator::LtEq => Ok(a <= b),
8224                    _ => unreachable!(),
8225                },
8226                _ => Err(Error::InvalidArgumentError(
8227                    "type mismatch in comparison".into(),
8228                )),
8229            }
8230        }
8231        _ => Err(Error::InvalidArgumentError(
8232            "unsupported comparison operator".into(),
8233        )),
8234    }
8235}
8236
8237fn literal_to_inline_value(value: &ValueWithSpan) -> SqlResult<InlineValue> {
8238    if let Some(text) = value.clone().value.into_string() {
8239        return Ok(InlineValue::String(Some(text)));
8240    }
8241
8242    match &value.value {
8243        sqlparser::ast::Value::Number(text, _) => {
8244            let parsed = text
8245                .parse::<i64>()
8246                .map_err(|_| Error::InvalidArgumentError("numeric literal is too large".into()))?;
8247            if parsed < i64::from(i32::MIN) || parsed > i64::from(i32::MAX) {
8248                return Err(Error::InvalidArgumentError(
8249                    "numeric literal is out of range".into(),
8250                ));
8251            }
8252            Ok(InlineValue::Int32(Some(parsed as i32)))
8253        }
8254        sqlparser::ast::Value::Boolean(flag) => Ok(InlineValue::Bool(Some(*flag))),
8255        sqlparser::ast::Value::Null => Ok(InlineValue::Null),
8256        other => Err(Error::InvalidArgumentError(format!(
8257            "unsupported literal '{other}' in inline query"
8258        ))),
8259    }
8260}
8261
8262fn invalid_column(name: &str) -> Error {
8263    Error::InvalidArgumentError(format!("column '{name}' does not exist"))
8264}
8265
8266fn validate_simple_query(query: &Query) -> SqlResult<()> {
8267    if query.with.is_some() {
8268        return Err(Error::InvalidArgumentError(
8269            "WITH clauses are not supported".into(),
8270        ));
8271    }
8272    if let Some(limit_clause) = &query.limit_clause {
8273        match limit_clause {
8274            LimitClause::LimitOffset {
8275                offset: Some(_), ..
8276            }
8277            | LimitClause::OffsetCommaLimit { .. } => {
8278                return Err(Error::InvalidArgumentError(
8279                    "OFFSET clauses are not supported".into(),
8280                ));
8281            }
8282            LimitClause::LimitOffset { limit_by, .. } if !limit_by.is_empty() => {
8283                return Err(Error::InvalidArgumentError(
8284                    "LIMIT BY clauses are not supported".into(),
8285                ));
8286            }
8287            _ => {}
8288        }
8289    }
8290    if query.fetch.is_some() {
8291        return Err(Error::InvalidArgumentError(
8292            "FETCH clauses are not supported".into(),
8293        ));
8294    }
8295    Ok(())
8296}
8297
8298fn resolve_column_name(expr: &SqlExpr) -> SqlResult<String> {
8299    match expr {
8300        SqlExpr::Identifier(ident) => Ok(ident.value.clone()),
8301        SqlExpr::CompoundIdentifier(parts) => {
8302            if let Some(last) = parts.last() {
8303                Ok(last.value.clone())
8304            } else {
8305                Err(Error::InvalidArgumentError(
8306                    "empty column identifier".into(),
8307                ))
8308            }
8309        }
8310        SqlExpr::Nested(inner) => resolve_column_name(inner),
8311        // Handle unary +/- by recursively resolving the inner expression
8312        SqlExpr::UnaryOp {
8313            op: UnaryOperator::Plus | UnaryOperator::Minus,
8314            expr,
8315        } => resolve_column_name(expr),
8316        _ => Err(Error::InvalidArgumentError(
8317            "aggregate arguments must be plain column identifiers".into(),
8318        )),
8319    }
8320}
8321
8322fn is_simple_aggregate_column(expr: &SqlExpr) -> bool {
8323    match expr {
8324        SqlExpr::Identifier(_) | SqlExpr::CompoundIdentifier(_) => true,
8325        SqlExpr::Nested(inner) => is_simple_aggregate_column(inner),
8326        SqlExpr::UnaryOp {
8327            op: UnaryOperator::Plus,
8328            expr,
8329        } => is_simple_aggregate_column(expr),
8330        _ => false,
8331    }
8332}
8333
8334fn validate_projection_alias_qualifiers(
8335    projection_items: &[SelectItem],
8336    alias: &str,
8337) -> SqlResult<()> {
8338    let alias_lower = alias.to_ascii_lowercase();
8339    for item in projection_items {
8340        match item {
8341            SelectItem::UnnamedExpr(expr) | SelectItem::ExprWithAlias { expr, .. } => {
8342                if let SqlExpr::CompoundIdentifier(parts) = expr
8343                    && parts.len() >= 2
8344                    && let Some(first) = parts.first()
8345                    && !first.value.eq_ignore_ascii_case(&alias_lower)
8346                {
8347                    return Err(Error::InvalidArgumentError(format!(
8348                        "Binder Error: table '{}' not found",
8349                        first.value
8350                    )));
8351                }
8352            }
8353            _ => {}
8354        }
8355    }
8356    Ok(())
8357}
8358
8359/// Try to parse a function as an aggregate call for use in scalar expressions
8360/// Check if a scalar expression contains any aggregate functions
8361#[allow(dead_code)] // Utility function for future use
8362fn expr_contains_aggregate(expr: &llkv_expr::expr::ScalarExpr<String>) -> bool {
8363    match expr {
8364        llkv_expr::expr::ScalarExpr::Aggregate(_) => true,
8365        llkv_expr::expr::ScalarExpr::Binary { left, right, .. } => {
8366            expr_contains_aggregate(left) || expr_contains_aggregate(right)
8367        }
8368        llkv_expr::expr::ScalarExpr::Compare { left, right, .. } => {
8369            expr_contains_aggregate(left) || expr_contains_aggregate(right)
8370        }
8371        llkv_expr::expr::ScalarExpr::Not(inner) => expr_contains_aggregate(inner),
8372        llkv_expr::expr::ScalarExpr::IsNull { expr, .. } => expr_contains_aggregate(expr),
8373        llkv_expr::expr::ScalarExpr::GetField { base, .. } => expr_contains_aggregate(base),
8374        llkv_expr::expr::ScalarExpr::Cast { expr, .. } => expr_contains_aggregate(expr),
8375        llkv_expr::expr::ScalarExpr::Case {
8376            operand,
8377            branches,
8378            else_expr,
8379        } => {
8380            operand
8381                .as_deref()
8382                .map(expr_contains_aggregate)
8383                .unwrap_or(false)
8384                || branches.iter().any(|(when_expr, then_expr)| {
8385                    expr_contains_aggregate(when_expr) || expr_contains_aggregate(then_expr)
8386                })
8387                || else_expr
8388                    .as_deref()
8389                    .map(expr_contains_aggregate)
8390                    .unwrap_or(false)
8391        }
8392        llkv_expr::expr::ScalarExpr::Coalesce(items) => items.iter().any(expr_contains_aggregate),
8393        llkv_expr::expr::ScalarExpr::Column(_)
8394        | llkv_expr::expr::ScalarExpr::Literal(_)
8395        | llkv_expr::expr::ScalarExpr::Random => false,
8396        llkv_expr::expr::ScalarExpr::ScalarSubquery(_) => false,
8397    }
8398}
8399
8400fn try_parse_aggregate_function(
8401    func: &sqlparser::ast::Function,
8402    resolver: Option<&IdentifierResolver<'_>>,
8403    context: Option<&IdentifierContext>,
8404    outer_scopes: &[IdentifierContext],
8405    tracker: &mut SubqueryCorrelatedTracker<'_>,
8406) -> SqlResult<Option<llkv_expr::expr::AggregateCall<String>>> {
8407    use sqlparser::ast::{
8408        DuplicateTreatment, FunctionArg, FunctionArgExpr, FunctionArguments, ObjectNamePart,
8409    };
8410
8411    if func.uses_odbc_syntax {
8412        return Ok(None);
8413    }
8414    if !matches!(func.parameters, FunctionArguments::None) {
8415        return Ok(None);
8416    }
8417    if func.filter.is_some()
8418        || func.null_treatment.is_some()
8419        || func.over.is_some()
8420        || !func.within_group.is_empty()
8421    {
8422        return Ok(None);
8423    }
8424
8425    let func_name = if func.name.0.len() == 1 {
8426        match &func.name.0[0] {
8427            ObjectNamePart::Identifier(ident) => ident.value.to_ascii_lowercase(),
8428            _ => return Ok(None),
8429        }
8430    } else {
8431        return Ok(None);
8432    };
8433
8434    // Check for DISTINCT modifier
8435    let distinct = match &func.args {
8436        FunctionArguments::List(list) => {
8437            if !list.clauses.is_empty() {
8438                return Ok(None);
8439            }
8440            matches!(list.duplicate_treatment, Some(DuplicateTreatment::Distinct))
8441        }
8442        _ => false,
8443    };
8444
8445    let args_slice: &[FunctionArg] = match &func.args {
8446        FunctionArguments::List(list) => &list.args,
8447        FunctionArguments::None => &[],
8448        FunctionArguments::Subquery(_) => return Ok(None),
8449    };
8450
8451    let agg_call = match func_name.as_str() {
8452        "count" => {
8453            if args_slice.len() != 1 {
8454                return Err(Error::InvalidArgumentError(
8455                    "COUNT accepts exactly one argument".into(),
8456                ));
8457            }
8458            match &args_slice[0] {
8459                FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => {
8460                    if distinct {
8461                        return Err(Error::InvalidArgumentError(
8462                            "COUNT(DISTINCT *) is not supported".into(),
8463                        ));
8464                    }
8465                    llkv_expr::expr::AggregateCall::CountStar
8466                }
8467                FunctionArg::Unnamed(FunctionArgExpr::Expr(arg_expr)) => {
8468                    let expr = translate_scalar_internal(
8469                        arg_expr,
8470                        resolver,
8471                        context,
8472                        outer_scopes,
8473                        tracker,
8474                        None,
8475                    )?;
8476                    llkv_expr::expr::AggregateCall::Count {
8477                        expr: Box::new(expr),
8478                        distinct,
8479                    }
8480                }
8481                _ => {
8482                    return Err(Error::InvalidArgumentError(
8483                        "unsupported COUNT argument".into(),
8484                    ));
8485                }
8486            }
8487        }
8488        "sum" => {
8489            if args_slice.len() != 1 {
8490                return Err(Error::InvalidArgumentError(
8491                    "SUM accepts exactly one argument".into(),
8492                ));
8493            }
8494            let arg_expr = match &args_slice[0] {
8495                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8496                _ => {
8497                    return Err(Error::InvalidArgumentError(
8498                        "SUM requires a column argument".into(),
8499                    ));
8500                }
8501            };
8502
8503            // Check for COUNT(CASE ...) pattern
8504            if let Some(column) = parse_count_nulls_case(arg_expr)? {
8505                if distinct {
8506                    return Err(Error::InvalidArgumentError(
8507                        "DISTINCT not supported for COUNT(CASE ...) pattern".into(),
8508                    ));
8509                }
8510                llkv_expr::expr::AggregateCall::CountNulls(Box::new(
8511                    llkv_expr::expr::ScalarExpr::column(column),
8512                ))
8513            } else {
8514                let expr = translate_scalar_internal(
8515                    arg_expr,
8516                    resolver,
8517                    context,
8518                    outer_scopes,
8519                    tracker,
8520                    None,
8521                )?;
8522                llkv_expr::expr::AggregateCall::Sum {
8523                    expr: Box::new(expr),
8524                    distinct,
8525                }
8526            }
8527        }
8528        "total" => {
8529            if args_slice.len() != 1 {
8530                return Err(Error::InvalidArgumentError(
8531                    "TOTAL accepts exactly one argument".into(),
8532                ));
8533            }
8534            let arg_expr = match &args_slice[0] {
8535                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8536                _ => {
8537                    return Err(Error::InvalidArgumentError(
8538                        "TOTAL requires a column argument".into(),
8539                    ));
8540                }
8541            };
8542
8543            let expr = translate_scalar_internal(
8544                arg_expr,
8545                resolver,
8546                context,
8547                outer_scopes,
8548                tracker,
8549                None,
8550            )?;
8551            llkv_expr::expr::AggregateCall::Total {
8552                expr: Box::new(expr),
8553                distinct,
8554            }
8555        }
8556        "min" => {
8557            if args_slice.len() != 1 {
8558                return Err(Error::InvalidArgumentError(
8559                    "MIN accepts exactly one argument".into(),
8560                ));
8561            }
8562            let arg_expr = match &args_slice[0] {
8563                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8564                _ => {
8565                    return Err(Error::InvalidArgumentError(
8566                        "MIN requires a column argument".into(),
8567                    ));
8568                }
8569            };
8570            let expr = translate_scalar_internal(
8571                arg_expr,
8572                resolver,
8573                context,
8574                outer_scopes,
8575                tracker,
8576                None,
8577            )?;
8578            llkv_expr::expr::AggregateCall::Min(Box::new(expr))
8579        }
8580        "max" => {
8581            if args_slice.len() != 1 {
8582                return Err(Error::InvalidArgumentError(
8583                    "MAX accepts exactly one argument".into(),
8584                ));
8585            }
8586            let arg_expr = match &args_slice[0] {
8587                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8588                _ => {
8589                    return Err(Error::InvalidArgumentError(
8590                        "MAX requires a column argument".into(),
8591                    ));
8592                }
8593            };
8594            let expr = translate_scalar_internal(
8595                arg_expr,
8596                resolver,
8597                context,
8598                outer_scopes,
8599                tracker,
8600                None,
8601            )?;
8602            llkv_expr::expr::AggregateCall::Max(Box::new(expr))
8603        }
8604        "avg" => {
8605            if args_slice.len() != 1 {
8606                return Err(Error::InvalidArgumentError(
8607                    "AVG accepts exactly one argument".into(),
8608                ));
8609            }
8610            let arg_expr = match &args_slice[0] {
8611                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8612                _ => {
8613                    return Err(Error::InvalidArgumentError(
8614                        "AVG requires a column argument".into(),
8615                    ));
8616                }
8617            };
8618            let expr = translate_scalar_internal(
8619                arg_expr,
8620                resolver,
8621                context,
8622                outer_scopes,
8623                tracker,
8624                None,
8625            )?;
8626            llkv_expr::expr::AggregateCall::Avg {
8627                expr: Box::new(expr),
8628                distinct,
8629            }
8630        }
8631        "group_concat" => {
8632            if args_slice.is_empty() || args_slice.len() > 2 {
8633                return Err(Error::InvalidArgumentError(
8634                    "GROUP_CONCAT accepts one or two arguments".into(),
8635                ));
8636            }
8637
8638            // First argument is the column/expression
8639            let arg_expr = match &args_slice[0] {
8640                FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
8641                _ => {
8642                    return Err(Error::InvalidArgumentError(
8643                        "GROUP_CONCAT requires a column argument".into(),
8644                    ));
8645                }
8646            };
8647
8648            let expr = translate_scalar_internal(
8649                arg_expr,
8650                resolver,
8651                context,
8652                outer_scopes,
8653                tracker,
8654                None,
8655            )?;
8656
8657            // Second argument (optional) is the separator
8658            let separator = if args_slice.len() == 2 {
8659                match &args_slice[1] {
8660                    FunctionArg::Unnamed(FunctionArgExpr::Expr(SqlExpr::Value(
8661                        ValueWithSpan {
8662                            value: sqlparser::ast::Value::SingleQuotedString(s),
8663                            ..
8664                        },
8665                    ))) => Some(s.clone()),
8666                    _ => {
8667                        return Err(Error::InvalidArgumentError(
8668                            "GROUP_CONCAT separator must be a string literal".into(),
8669                        ));
8670                    }
8671                }
8672            } else {
8673                None
8674            };
8675
8676            // SQLite doesn't support DISTINCT with a custom separator
8677            if distinct && separator.is_some() {
8678                return Err(Error::InvalidArgumentError(
8679                    "GROUP_CONCAT does not support DISTINCT with a custom separator".into(),
8680                ));
8681            }
8682
8683            llkv_expr::expr::AggregateCall::GroupConcat {
8684                expr: Box::new(expr),
8685                distinct,
8686                separator,
8687            }
8688        }
8689        _ => return Ok(None),
8690    };
8691
8692    Ok(Some(agg_call))
8693}
8694
8695fn parse_count_nulls_case(expr: &SqlExpr) -> SqlResult<Option<String>> {
8696    let SqlExpr::Case {
8697        operand,
8698        conditions,
8699        else_result,
8700        ..
8701    } = expr
8702    else {
8703        return Ok(None);
8704    };
8705
8706    if operand.is_some() || conditions.len() != 1 {
8707        return Ok(None);
8708    }
8709
8710    let case_when = &conditions[0];
8711    if !is_integer_literal(&case_when.result, 1) {
8712        return Ok(None);
8713    }
8714
8715    let else_expr = match else_result {
8716        Some(expr) => expr.as_ref(),
8717        None => return Ok(None),
8718    };
8719    if !is_integer_literal(else_expr, 0) {
8720        return Ok(None);
8721    }
8722
8723    let inner = match &case_when.condition {
8724        SqlExpr::IsNull(inner) => inner.as_ref(),
8725        _ => return Ok(None),
8726    };
8727
8728    resolve_column_name(inner).map(Some)
8729}
8730
8731fn is_integer_literal(expr: &SqlExpr, expected: i64) -> bool {
8732    match expr {
8733        SqlExpr::Value(ValueWithSpan {
8734            value: Value::Number(text, _),
8735            ..
8736        }) => text.parse::<i64>() == Ok(expected),
8737        _ => false,
8738    }
8739}
8740
8741fn strip_sql_expr_nesting(expr: &SqlExpr) -> &SqlExpr {
8742    match expr {
8743        SqlExpr::Nested(inner) => strip_sql_expr_nesting(inner),
8744        other => other,
8745    }
8746}
8747
8748fn normalize_between_operand(operand: &SqlExpr, negated: bool) -> (bool, &SqlExpr) {
8749    let mut effective_negated = negated;
8750    let mut current = operand;
8751    loop {
8752        current = strip_sql_expr_nesting(current);
8753        match current {
8754            SqlExpr::UnaryOp {
8755                op: UnaryOperator::Not,
8756                expr,
8757            } => {
8758                effective_negated = !effective_negated;
8759                current = expr;
8760            }
8761            _ => return (effective_negated, current),
8762        }
8763    }
8764}
8765
8766fn peel_not_chain(expr: &SqlExpr) -> (usize, &SqlExpr) {
8767    let mut count = 0;
8768    let mut current = expr;
8769    loop {
8770        current = strip_sql_expr_nesting(current);
8771        match current {
8772            SqlExpr::UnaryOp {
8773                op: UnaryOperator::Not,
8774                expr,
8775            } => {
8776                count += 1;
8777                current = expr;
8778            }
8779            _ => return (count, current),
8780        }
8781    }
8782}
8783
8784struct BetweenBounds<'a> {
8785    lower: &'a SqlExpr,
8786    upper: &'a SqlExpr,
8787}
8788
8789#[allow(clippy::too_many_arguments)] // TODO: Refactor args list
8790fn translate_between_expr(
8791    engine: &SqlEngine,
8792    resolver: &IdentifierResolver<'_>,
8793    context: IdentifierContext,
8794    between_expr: &SqlExpr,
8795    bounds: BetweenBounds<'_>,
8796    negated: bool,
8797    outer_scopes: &[IdentifierContext],
8798    scalar_subqueries: &mut Vec<llkv_plan::ScalarSubquery>,
8799    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
8800) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
8801    let lower_op = if negated {
8802        BinaryOperator::Lt
8803    } else {
8804        BinaryOperator::GtEq
8805    };
8806    let upper_op = if negated {
8807        BinaryOperator::Gt
8808    } else {
8809        BinaryOperator::LtEq
8810    };
8811
8812    let lower_bound = translate_comparison_with_context(
8813        engine,
8814        resolver,
8815        context.clone(),
8816        between_expr,
8817        lower_op,
8818        bounds.lower,
8819        outer_scopes,
8820        scalar_subqueries,
8821        correlated_tracker.reborrow(),
8822    )?;
8823    let upper_bound = translate_comparison_with_context(
8824        engine,
8825        resolver,
8826        context,
8827        between_expr,
8828        upper_op,
8829        bounds.upper,
8830        outer_scopes,
8831        scalar_subqueries,
8832        correlated_tracker,
8833    )?;
8834
8835    if negated {
8836        Ok(llkv_expr::expr::Expr::Or(vec![lower_bound, upper_bound]))
8837    } else {
8838        Ok(llkv_expr::expr::Expr::And(vec![lower_bound, upper_bound]))
8839    }
8840}
8841
8842#[allow(clippy::too_many_arguments)] // TODO: Refactor args list
8843fn translate_like_expr(
8844    engine: &SqlEngine,
8845    resolver: &IdentifierResolver<'_>,
8846    context: IdentifierContext,
8847    like_expr: &SqlExpr,
8848    pattern_expr: &SqlExpr,
8849    negated: bool,
8850    outer_scopes: &[IdentifierContext],
8851    scalar_subqueries: &mut Vec<llkv_plan::ScalarSubquery>,
8852    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
8853) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
8854    // Translate the target expression (must be a column reference)
8855    let target_scalar = translate_scalar_with_context_scoped(
8856        engine,
8857        resolver,
8858        context,
8859        like_expr,
8860        outer_scopes,
8861        correlated_tracker.reborrow(),
8862        Some(&mut *scalar_subqueries),
8863    )?;
8864
8865    let llkv_expr::expr::ScalarExpr::Column(column) = target_scalar else {
8866        return Err(Error::InvalidArgumentError(
8867            "LIKE expression target must be a column reference".into(),
8868        ));
8869    };
8870
8871    // Extract the pattern string literal
8872    let SqlExpr::Value(pattern_value) = pattern_expr else {
8873        return Err(Error::InvalidArgumentError(
8874            "LIKE pattern must be a string literal".into(),
8875        ));
8876    };
8877
8878    let (Value::SingleQuotedString(pattern_str) | Value::DoubleQuotedString(pattern_str)) =
8879        &pattern_value.value
8880    else {
8881        return Err(Error::InvalidArgumentError(
8882            "LIKE pattern must be a quoted string".into(),
8883        ));
8884    };
8885
8886    // Optimize common LIKE patterns to use specialized operators
8887    let operator = if let Some(optimized_op) = try_optimize_like_pattern(pattern_str) {
8888        optimized_op
8889    } else {
8890        // TODO: Implement full pattern matching with % and _ wildcards
8891        return Err(Error::InvalidArgumentError(format!(
8892            "LIKE pattern '{}' requires full wildcard support (not yet implemented)",
8893            pattern_str
8894        )));
8895    };
8896
8897    let filter = llkv_expr::expr::Filter {
8898        field_id: column,
8899        op: operator,
8900    };
8901
8902    let result = llkv_expr::expr::Expr::Pred(filter);
8903
8904    if negated {
8905        Ok(llkv_expr::expr::Expr::not(result))
8906    } else {
8907        Ok(result)
8908    }
8909}
8910
8911/// Try to optimize SQL LIKE patterns to specialized operators.
8912///
8913/// Returns `Some(operator)` if the pattern can be optimized, `None` otherwise.
8914///
8915/// Supported optimizations:
8916/// - `pattern%` → `StartsWith(pattern)`
8917/// - `%pattern` → `EndsWith(pattern)`
8918/// - `%pattern%` → `Contains(pattern)`
8919/// - Exact match (no wildcards) → `Equals(pattern)`
8920fn try_optimize_like_pattern(pattern: &str) -> Option<llkv_expr::expr::Operator<'static>> {
8921    let bytes = pattern.as_bytes();
8922
8923    // Check if pattern has any underscore wildcards (can't optimize those)
8924    if pattern.contains('_') {
8925        return None;
8926    }
8927
8928    // Count percent signs
8929    let percent_count = pattern.chars().filter(|&c| c == '%').count();
8930
8931    match percent_count {
8932        0 => {
8933            // No wildcards - exact match
8934            Some(llkv_expr::expr::Operator::Equals(Literal::from(pattern)))
8935        }
8936        1 => {
8937            if bytes.first() == Some(&b'%') {
8938                // %pattern - ends with
8939                let suffix = pattern[1..].to_string();
8940                Some(llkv_expr::expr::Operator::EndsWith {
8941                    pattern: suffix,
8942                    case_sensitive: true,
8943                })
8944            } else if bytes.last() == Some(&b'%') {
8945                // pattern% - starts with
8946                let prefix = pattern[..pattern.len() - 1].to_string();
8947                Some(llkv_expr::expr::Operator::StartsWith {
8948                    pattern: prefix,
8949                    case_sensitive: true,
8950                })
8951            } else {
8952                // % in the middle - can't optimize
8953                None
8954            }
8955        }
8956        2 => {
8957            if bytes.first() == Some(&b'%') && bytes.last() == Some(&b'%') {
8958                // %pattern% - contains
8959                let substring = pattern[1..pattern.len() - 1].to_string();
8960                Some(llkv_expr::expr::Operator::Contains {
8961                    pattern: substring,
8962                    case_sensitive: true,
8963                })
8964            } else {
8965                // Multiple % in other positions - can't optimize
8966                None
8967            }
8968        }
8969        _ => {
8970            // Multiple % signs in complex positions - can't optimize
8971            None
8972        }
8973    }
8974}
8975
8976fn correlated_scalar_from_resolution(
8977    placeholder: String,
8978    resolution: &ColumnResolution,
8979) -> llkv_expr::expr::ScalarExpr<String> {
8980    let mut expr = llkv_expr::expr::ScalarExpr::column(placeholder);
8981    for field in resolution.field_path() {
8982        expr = llkv_expr::expr::ScalarExpr::get_field(expr, field.clone());
8983    }
8984    expr
8985}
8986
8987fn resolve_correlated_identifier(
8988    resolver: &IdentifierResolver<'_>,
8989    parts: &[String],
8990    outer_scopes: &[IdentifierContext],
8991    mut tracker: SubqueryCorrelatedTracker<'_>,
8992) -> SqlResult<Option<llkv_expr::expr::ScalarExpr<String>>> {
8993    if !tracker.is_active() {
8994        return Ok(None);
8995    }
8996
8997    for scope in outer_scopes.iter().rev() {
8998        match resolver.resolve(parts, scope.clone()) {
8999            Ok(resolution) => {
9000                if let Some(placeholder) = tracker.placeholder_for_resolution(&resolution) {
9001                    let expr = correlated_scalar_from_resolution(placeholder, &resolution);
9002                    return Ok(Some(expr));
9003                }
9004            }
9005            Err(_) => continue,
9006        }
9007    }
9008
9009    Ok(None)
9010}
9011
9012fn resolve_identifier_expr(
9013    resolver: &IdentifierResolver<'_>,
9014    context: &IdentifierContext,
9015    parts: Vec<String>,
9016    outer_scopes: &[IdentifierContext],
9017    tracker: SubqueryCorrelatedTracker<'_>,
9018) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
9019    match resolver.resolve(&parts, context.clone()) {
9020        Ok(resolution) => {
9021            let use_scope_check =
9022                context.default_table_id().is_none() && context.tracks_available_columns();
9023            if use_scope_check && !context.has_available_column_for(&parts) {
9024                if let Some(expr) =
9025                    resolve_correlated_identifier(resolver, &parts, outer_scopes, tracker)?
9026                {
9027                    return Ok(expr);
9028                } else {
9029                    return Err(Error::InvalidArgumentError(format!(
9030                        "Binder Error: does not have a column named '{}'",
9031                        parts.join(".")
9032                    )));
9033                }
9034            }
9035            Ok(resolution.into_scalar_expr())
9036        }
9037        Err(err) => {
9038            if let Some(expr) =
9039                resolve_correlated_identifier(resolver, &parts, outer_scopes, tracker)?
9040            {
9041                Ok(expr)
9042            } else {
9043                Err(err)
9044            }
9045        }
9046    }
9047}
9048
9049#[allow(clippy::too_many_arguments)] // TODO: Refactor args list
9050fn translate_condition_with_context(
9051    engine: &SqlEngine,
9052    resolver: &IdentifierResolver<'_>,
9053    context: IdentifierContext,
9054    expr: &SqlExpr,
9055    outer_scopes: &[IdentifierContext],
9056    subqueries: &mut Vec<llkv_plan::FilterSubquery>,
9057    scalar_subqueries: &mut Vec<llkv_plan::ScalarSubquery>,
9058    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
9059) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
9060    // Iterative postorder traversal using the TransformFrame pattern.
9061    // See llkv-plan::TransformFrame documentation for pattern details.
9062    //
9063    // This avoids stack overflow on deeply nested expressions (50k+ nodes) by using
9064    // explicit work_stack and result_stack instead of recursion.
9065
9066    enum ConditionExitContext {
9067        And,
9068        Or,
9069        Not,
9070        Nested,
9071    }
9072
9073    type ConditionFrame<'a> = llkv_plan::TransformFrame<
9074        'a,
9075        SqlExpr,
9076        llkv_expr::expr::Expr<'static, String>,
9077        ConditionExitContext,
9078    >;
9079
9080    let mut work_stack: Vec<ConditionFrame> = vec![ConditionFrame::Enter(expr)];
9081    let mut result_stack: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
9082
9083    while let Some(frame) = work_stack.pop() {
9084        match frame {
9085            ConditionFrame::Enter(node) => match node {
9086                SqlExpr::BinaryOp { left, op, right } => match op {
9087                    BinaryOperator::And => {
9088                        work_stack.push(ConditionFrame::Exit(ConditionExitContext::And));
9089                        work_stack.push(ConditionFrame::Enter(right));
9090                        work_stack.push(ConditionFrame::Enter(left));
9091                    }
9092                    BinaryOperator::Or => {
9093                        work_stack.push(ConditionFrame::Exit(ConditionExitContext::Or));
9094                        work_stack.push(ConditionFrame::Enter(right));
9095                        work_stack.push(ConditionFrame::Enter(left));
9096                    }
9097                    BinaryOperator::Eq
9098                    | BinaryOperator::NotEq
9099                    | BinaryOperator::Lt
9100                    | BinaryOperator::LtEq
9101                    | BinaryOperator::Gt
9102                    | BinaryOperator::GtEq => {
9103                        let result = translate_comparison_with_context(
9104                            engine,
9105                            resolver,
9106                            context.clone(),
9107                            left,
9108                            op.clone(),
9109                            right,
9110                            outer_scopes,
9111                            scalar_subqueries,
9112                            correlated_tracker.reborrow(),
9113                        )?;
9114                        work_stack.push(ConditionFrame::Leaf(result));
9115                    }
9116                    other => {
9117                        return Err(Error::InvalidArgumentError(format!(
9118                            "unsupported binary operator in WHERE clause: {other:?}"
9119                        )));
9120                    }
9121                },
9122                SqlExpr::UnaryOp {
9123                    op: UnaryOperator::Not,
9124                    expr: inner,
9125                } => {
9126                    let (nested_not_count, core_expr) = peel_not_chain(inner);
9127                    let total_not_count = nested_not_count + 1;
9128                    if let SqlExpr::Between {
9129                        expr: between_expr,
9130                        negated,
9131                        low,
9132                        high,
9133                    } = core_expr
9134                    {
9135                        let mut negated_mode = *negated;
9136                        if total_not_count % 2 == 1 {
9137                            negated_mode = !negated_mode;
9138                        }
9139                        let (effective_negated, normalized_expr) =
9140                            normalize_between_operand(between_expr, negated_mode);
9141                        let between_expr_result = translate_between_expr(
9142                            engine,
9143                            resolver,
9144                            context.clone(),
9145                            normalized_expr,
9146                            BetweenBounds {
9147                                lower: low,
9148                                upper: high,
9149                            },
9150                            effective_negated,
9151                            outer_scopes,
9152                            scalar_subqueries,
9153                            correlated_tracker.reborrow(),
9154                        )?;
9155                        work_stack.push(ConditionFrame::Leaf(between_expr_result));
9156                        continue;
9157                    }
9158                    // Note: Do not short-circuit NOT on NULL comparisons.
9159                    // NULL comparisons evaluate to NULL, and NOT NULL should also be NULL,
9160                    // not FALSE. Let the normal evaluation handle NULL propagation.
9161                    for _ in 0..total_not_count {
9162                        work_stack.push(ConditionFrame::Exit(ConditionExitContext::Not));
9163                    }
9164                    work_stack.push(ConditionFrame::Enter(core_expr));
9165                }
9166                SqlExpr::Nested(inner) => {
9167                    work_stack.push(ConditionFrame::Exit(ConditionExitContext::Nested));
9168                    work_stack.push(ConditionFrame::Enter(inner));
9169                }
9170                SqlExpr::IsNull(inner) => {
9171                    let scalar = translate_scalar_with_context_scoped(
9172                        engine,
9173                        resolver,
9174                        context.clone(),
9175                        inner,
9176                        outer_scopes,
9177                        correlated_tracker.reborrow(),
9178                        Some(&mut *scalar_subqueries),
9179                    )?;
9180                    match scalar {
9181                        llkv_expr::expr::ScalarExpr::Column(column) => {
9182                            // Optimize simple column checks to use Filter
9183                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::Pred(
9184                                llkv_expr::expr::Filter {
9185                                    field_id: column,
9186                                    op: llkv_expr::expr::Operator::IsNull,
9187                                },
9188                            )));
9189                        }
9190                        // NOTE: Do NOT constant-fold IsNull(Literal(Null)) to Literal(true).
9191                        // While technically correct (NULL IS NULL = TRUE), it breaks NULL
9192                        // propagation in boolean expressions like NOT (NOT NULL = NULL).
9193                        // The executor's evaluate_having_expr handles these correctly.
9194                        other => {
9195                            // For all expressions including literals, use the IsNull variant
9196                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::IsNull {
9197                                expr: other,
9198                                negated: false,
9199                            }));
9200                        }
9201                    }
9202                }
9203                SqlExpr::IsNotNull(inner) => {
9204                    let scalar = translate_scalar_with_context_scoped(
9205                        engine,
9206                        resolver,
9207                        context.clone(),
9208                        inner,
9209                        outer_scopes,
9210                        correlated_tracker.reborrow(),
9211                        Some(&mut *scalar_subqueries),
9212                    )?;
9213                    match scalar {
9214                        llkv_expr::expr::ScalarExpr::Column(column) => {
9215                            // Optimize simple column checks to use Filter
9216                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::Pred(
9217                                llkv_expr::expr::Filter {
9218                                    field_id: column,
9219                                    op: llkv_expr::expr::Operator::IsNotNull,
9220                                },
9221                            )));
9222                        }
9223                        // NOTE: Do NOT constant-fold IsNotNull(Literal(Null)) to Literal(false).
9224                        // While technically correct (NULL IS NOT NULL = FALSE), it breaks NULL
9225                        // propagation in boolean expressions like NOT (NOT NULL = NULL).
9226                        // The executor's evaluate_having_expr handles these correctly.
9227                        other => {
9228                            // For all expressions including literals, use the IsNull variant with negation
9229                            work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::IsNull {
9230                                expr: other,
9231                                negated: true,
9232                            }));
9233                        }
9234                    }
9235                }
9236                SqlExpr::InList {
9237                    expr: in_expr,
9238                    list,
9239                    negated,
9240                } => {
9241                    if list.is_empty() {
9242                        let result = if *negated {
9243                            llkv_expr::expr::Expr::Literal(true)
9244                        } else {
9245                            llkv_expr::expr::Expr::Literal(false)
9246                        };
9247                        work_stack.push(ConditionFrame::Leaf(result));
9248                    } else {
9249                        let target = translate_scalar_with_context_scoped(
9250                            engine,
9251                            resolver,
9252                            context.clone(),
9253                            in_expr,
9254                            outer_scopes,
9255                            correlated_tracker.reborrow(),
9256                            Some(&mut *scalar_subqueries),
9257                        )?;
9258                        let mut values = Vec::with_capacity(list.len());
9259                        for value_expr in list {
9260                            let scalar = translate_scalar_with_context_scoped(
9261                                engine,
9262                                resolver,
9263                                context.clone(),
9264                                value_expr,
9265                                outer_scopes,
9266                                correlated_tracker.reborrow(),
9267                                Some(&mut *scalar_subqueries),
9268                            )?;
9269                            values.push(scalar);
9270                        }
9271
9272                        work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::InList {
9273                            expr: target,
9274                            list: values,
9275                            negated: *negated,
9276                        }));
9277                    }
9278                }
9279                SqlExpr::InSubquery { .. } => {
9280                    return Err(Error::InvalidArgumentError(
9281                        "IN (SELECT ...) subqueries must be materialized before translation".into(),
9282                    ));
9283                }
9284                SqlExpr::Between {
9285                    expr: between_expr,
9286                    negated,
9287                    low,
9288                    high,
9289                } => {
9290                    let (effective_negated, normalized_expr) =
9291                        normalize_between_operand(between_expr, *negated);
9292                    let between_expr_result = translate_between_expr(
9293                        engine,
9294                        resolver,
9295                        context.clone(),
9296                        normalized_expr,
9297                        BetweenBounds {
9298                            lower: low,
9299                            upper: high,
9300                        },
9301                        effective_negated,
9302                        outer_scopes,
9303                        scalar_subqueries,
9304                        correlated_tracker.reborrow(),
9305                    )?;
9306                    work_stack.push(ConditionFrame::Leaf(between_expr_result));
9307                }
9308                SqlExpr::Exists { subquery, negated } => {
9309                    // Build nested select plan for the subquery
9310                    let mut nested_scopes = outer_scopes.to_vec();
9311                    nested_scopes.push(context.clone());
9312
9313                    let mut tracker = SubqueryCorrelatedColumnTracker::new();
9314                    let mut nested_subqueries = Vec::new();
9315
9316                    // Translate the subquery in an extended scope
9317                    let subquery_plan = engine.build_select_plan_internal(
9318                        (**subquery).clone(),
9319                        resolver,
9320                        &nested_scopes,
9321                        &mut nested_subqueries,
9322                        Some(&mut tracker),
9323                    )?;
9324
9325                    let subquery_id = llkv_expr::SubqueryId(subqueries.len() as u32);
9326                    let filter_subquery = llkv_plan::FilterSubquery {
9327                        id: subquery_id,
9328                        plan: Box::new(subquery_plan),
9329                        correlated_columns: tracker.into_columns(),
9330                    };
9331                    subqueries.push(filter_subquery);
9332
9333                    work_stack.push(ConditionFrame::Leaf(llkv_expr::expr::Expr::Exists(
9334                        llkv_expr::SubqueryExpr {
9335                            id: subquery_id,
9336                            negated: *negated,
9337                        },
9338                    )));
9339                }
9340                SqlExpr::Like {
9341                    negated,
9342                    expr: like_expr,
9343                    pattern,
9344                    escape_char,
9345                    any: _,
9346                } => {
9347                    if escape_char.is_some() {
9348                        return Err(Error::InvalidArgumentError(
9349                            "LIKE with ESCAPE clause is not supported".into(),
9350                        ));
9351                    }
9352
9353                    let result = translate_like_expr(
9354                        engine,
9355                        resolver,
9356                        context.clone(),
9357                        like_expr,
9358                        pattern,
9359                        *negated,
9360                        outer_scopes,
9361                        scalar_subqueries,
9362                        correlated_tracker.reborrow(),
9363                    )?;
9364                    work_stack.push(ConditionFrame::Leaf(result));
9365                }
9366                other => {
9367                    return Err(Error::InvalidArgumentError(format!(
9368                        "unsupported WHERE clause: {other:?}"
9369                    )));
9370                }
9371            },
9372            ConditionFrame::Leaf(translated) => {
9373                result_stack.push(translated);
9374            }
9375            ConditionFrame::Exit(exit_context) => match exit_context {
9376                ConditionExitContext::And => {
9377                    let right = result_stack.pop().ok_or_else(|| {
9378                        Error::Internal(
9379                            "translate_condition: result stack underflow for And right".into(),
9380                        )
9381                    })?;
9382                    let left = result_stack.pop().ok_or_else(|| {
9383                        Error::Internal(
9384                            "translate_condition: result stack underflow for And left".into(),
9385                        )
9386                    })?;
9387                    result_stack.push(flatten_and(left, right));
9388                }
9389                ConditionExitContext::Or => {
9390                    let right = result_stack.pop().ok_or_else(|| {
9391                        Error::Internal(
9392                            "translate_condition: result stack underflow for Or right".into(),
9393                        )
9394                    })?;
9395                    let left = result_stack.pop().ok_or_else(|| {
9396                        Error::Internal(
9397                            "translate_condition: result stack underflow for Or left".into(),
9398                        )
9399                    })?;
9400                    result_stack.push(flatten_or(left, right));
9401                }
9402                ConditionExitContext::Not => {
9403                    let inner = result_stack.pop().ok_or_else(|| {
9404                        Error::Internal(
9405                            "translate_condition: result stack underflow for Not".into(),
9406                        )
9407                    })?;
9408                    // Optimize: NOT (expr IS NULL) -> expr IS NOT NULL by flipping negation
9409                    match inner {
9410                        llkv_expr::expr::Expr::IsNull { expr, negated } => {
9411                            result_stack.push(llkv_expr::expr::Expr::IsNull {
9412                                expr,
9413                                negated: !negated,
9414                            });
9415                        }
9416                        other => {
9417                            result_stack.push(llkv_expr::expr::Expr::not(other));
9418                        }
9419                    }
9420                }
9421                ConditionExitContext::Nested => {
9422                    // Nested is a no-op - just pass through the inner expression
9423                }
9424            },
9425        }
9426    }
9427
9428    result_stack.pop().ok_or_else(|| {
9429        Error::Internal("translate_condition_with_context: empty result stack".into())
9430    })
9431}
9432
9433fn flatten_and(
9434    left: llkv_expr::expr::Expr<'static, String>,
9435    right: llkv_expr::expr::Expr<'static, String>,
9436) -> llkv_expr::expr::Expr<'static, String> {
9437    let mut children: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
9438    match left {
9439        llkv_expr::expr::Expr::And(mut left_children) => children.append(&mut left_children),
9440        other => children.push(other),
9441    }
9442    match right {
9443        llkv_expr::expr::Expr::And(mut right_children) => children.append(&mut right_children),
9444        other => children.push(other),
9445    }
9446    if children.len() == 1 {
9447        children.into_iter().next().unwrap()
9448    } else {
9449        llkv_expr::expr::Expr::And(children)
9450    }
9451}
9452
9453fn flatten_or(
9454    left: llkv_expr::expr::Expr<'static, String>,
9455    right: llkv_expr::expr::Expr<'static, String>,
9456) -> llkv_expr::expr::Expr<'static, String> {
9457    let mut children: Vec<llkv_expr::expr::Expr<'static, String>> = Vec::new();
9458    match left {
9459        llkv_expr::expr::Expr::Or(mut left_children) => children.append(&mut left_children),
9460        other => children.push(other),
9461    }
9462    match right {
9463        llkv_expr::expr::Expr::Or(mut right_children) => children.append(&mut right_children),
9464        other => children.push(other),
9465    }
9466    if children.len() == 1 {
9467        children.into_iter().next().unwrap()
9468    } else {
9469        llkv_expr::expr::Expr::Or(children)
9470    }
9471}
9472
9473fn peel_unparenthesized_not_chain(expr: &SqlExpr) -> (usize, &SqlExpr) {
9474    let mut count: usize = 0;
9475    let mut current = expr;
9476    while let SqlExpr::UnaryOp {
9477        op: UnaryOperator::Not,
9478        expr: inner,
9479    } = current
9480    {
9481        if matches!(inner.as_ref(), SqlExpr::Nested(_)) {
9482            break;
9483        }
9484        count += 1;
9485        current = inner.as_ref();
9486    }
9487    (count, current)
9488}
9489#[allow(clippy::too_many_arguments)] // TODO: Refactor args list
9490fn translate_comparison_with_context(
9491    engine: &SqlEngine,
9492    resolver: &IdentifierResolver<'_>,
9493    context: IdentifierContext,
9494    left: &SqlExpr,
9495    op: BinaryOperator,
9496    right: &SqlExpr,
9497    outer_scopes: &[IdentifierContext],
9498    scalar_subqueries: &mut Vec<llkv_plan::ScalarSubquery>,
9499    mut correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
9500) -> SqlResult<llkv_expr::expr::Expr<'static, String>> {
9501    let (not_count, comparison_left) = peel_unparenthesized_not_chain(left);
9502
9503    let left_scalar = {
9504        let tracker = correlated_tracker.reborrow();
9505        translate_scalar_with_context_scoped(
9506            engine,
9507            resolver,
9508            context.clone(),
9509            comparison_left,
9510            outer_scopes,
9511            tracker,
9512            Some(scalar_subqueries),
9513        )?
9514    };
9515    let right_scalar = {
9516        let tracker = correlated_tracker.reborrow();
9517        translate_scalar_with_context_scoped(
9518            engine,
9519            resolver,
9520            context,
9521            right,
9522            outer_scopes,
9523            tracker,
9524            Some(scalar_subqueries),
9525        )?
9526    };
9527    let compare_op = match op {
9528        BinaryOperator::Eq => llkv_expr::expr::CompareOp::Eq,
9529        BinaryOperator::NotEq => llkv_expr::expr::CompareOp::NotEq,
9530        BinaryOperator::Lt => llkv_expr::expr::CompareOp::Lt,
9531        BinaryOperator::LtEq => llkv_expr::expr::CompareOp::LtEq,
9532        BinaryOperator::Gt => llkv_expr::expr::CompareOp::Gt,
9533        BinaryOperator::GtEq => llkv_expr::expr::CompareOp::GtEq,
9534        other => {
9535            return Err(Error::InvalidArgumentError(format!(
9536                "unsupported comparison operator: {other:?}"
9537            )));
9538        }
9539    };
9540
9541    let mut expr = llkv_expr::expr::Expr::Compare {
9542        left: left_scalar.clone(),
9543        op: compare_op,
9544        right: right_scalar.clone(),
9545    };
9546
9547    if let (
9548        llkv_expr::expr::ScalarExpr::Column(column),
9549        llkv_expr::expr::ScalarExpr::Literal(literal),
9550    ) = (&left_scalar, &right_scalar)
9551        && let Some(op) = compare_op_to_filter_operator(compare_op, literal)
9552    {
9553        tracing::debug!(
9554            column = ?column,
9555            literal = ?literal,
9556            ?compare_op,
9557            "translate_comparison direct"
9558        );
9559        expr = llkv_expr::expr::Expr::Pred(llkv_expr::expr::Filter {
9560            field_id: column.clone(),
9561            op,
9562        });
9563    } else if let (
9564        llkv_expr::expr::ScalarExpr::Literal(literal),
9565        llkv_expr::expr::ScalarExpr::Column(column),
9566    ) = (&left_scalar, &right_scalar)
9567        && let Some(flipped) = flip_compare_op(compare_op)
9568        && let Some(op) = compare_op_to_filter_operator(flipped, literal)
9569    {
9570        tracing::debug!(
9571            column = ?column,
9572            literal = ?literal,
9573            original_op = ?compare_op,
9574            flipped_op = ?flipped,
9575            "translate_comparison flipped"
9576        );
9577        expr = llkv_expr::expr::Expr::Pred(llkv_expr::expr::Filter {
9578            field_id: column.clone(),
9579            op,
9580        });
9581    }
9582
9583    let mut wrapped = expr;
9584    for _ in 0..not_count {
9585        wrapped = llkv_expr::expr::Expr::Not(Box::new(wrapped));
9586    }
9587
9588    Ok(wrapped)
9589}
9590
9591fn compare_op_to_filter_operator(
9592    op: llkv_expr::expr::CompareOp,
9593    literal: &Literal,
9594) -> Option<llkv_expr::expr::Operator<'static>> {
9595    if matches!(literal, Literal::Null) {
9596        return None;
9597    }
9598    let lit = literal.clone();
9599    tracing::debug!(?op, literal = ?literal, "compare_op_to_filter_operator input");
9600    match op {
9601        llkv_expr::expr::CompareOp::Eq => Some(llkv_expr::expr::Operator::Equals(lit)),
9602        llkv_expr::expr::CompareOp::Lt => Some(llkv_expr::expr::Operator::LessThan(lit)),
9603        llkv_expr::expr::CompareOp::LtEq => Some(llkv_expr::expr::Operator::LessThanOrEquals(lit)),
9604        llkv_expr::expr::CompareOp::Gt => Some(llkv_expr::expr::Operator::GreaterThan(lit)),
9605        llkv_expr::expr::CompareOp::GtEq => {
9606            Some(llkv_expr::expr::Operator::GreaterThanOrEquals(lit))
9607        }
9608        llkv_expr::expr::CompareOp::NotEq => None,
9609    }
9610}
9611
9612fn flip_compare_op(op: llkv_expr::expr::CompareOp) -> Option<llkv_expr::expr::CompareOp> {
9613    match op {
9614        llkv_expr::expr::CompareOp::Eq => Some(llkv_expr::expr::CompareOp::Eq),
9615        llkv_expr::expr::CompareOp::Lt => Some(llkv_expr::expr::CompareOp::Gt),
9616        llkv_expr::expr::CompareOp::LtEq => Some(llkv_expr::expr::CompareOp::GtEq),
9617        llkv_expr::expr::CompareOp::Gt => Some(llkv_expr::expr::CompareOp::Lt),
9618        llkv_expr::expr::CompareOp::GtEq => Some(llkv_expr::expr::CompareOp::LtEq),
9619        llkv_expr::expr::CompareOp::NotEq => None,
9620    }
9621}
9622/// Translate scalar expression with knowledge of the FROM table context.
9623/// This allows us to properly distinguish schema.table.column from column.field.field.
9624fn translate_scalar_with_context(
9625    resolver: &IdentifierResolver<'_>,
9626    context: IdentifierContext,
9627    expr: &SqlExpr,
9628) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
9629    let mut tracker = SubqueryCorrelatedTracker::from_option(None);
9630    translate_scalar_internal(
9631        expr,
9632        Some(resolver),
9633        Some(&context),
9634        &[],
9635        &mut tracker,
9636        None,
9637    )
9638}
9639
9640fn translate_scalar_with_context_scoped(
9641    engine: &SqlEngine,
9642    resolver: &IdentifierResolver<'_>,
9643    context: IdentifierContext,
9644    expr: &SqlExpr,
9645    outer_scopes: &[IdentifierContext],
9646    correlated_tracker: Option<&mut SubqueryCorrelatedColumnTracker>,
9647    scalar_subqueries: Option<&mut Vec<llkv_plan::ScalarSubquery>>,
9648) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
9649    let mut tracker = SubqueryCorrelatedTracker::from_option(correlated_tracker);
9650    let mut planner = scalar_subqueries.map(|storage| ScalarSubqueryPlanner {
9651        engine,
9652        scalar_subqueries: storage,
9653    });
9654    let resolver_opt = planner
9655        .as_mut()
9656        .map(|builder| builder as &mut dyn ScalarSubqueryResolver);
9657    translate_scalar_internal(
9658        expr,
9659        Some(resolver),
9660        Some(&context),
9661        outer_scopes,
9662        &mut tracker,
9663        resolver_opt,
9664    )
9665}
9666
9667#[allow(dead_code)]
9668fn translate_scalar(expr: &SqlExpr) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
9669    let mut tracker = SubqueryCorrelatedTracker::from_option(None);
9670    translate_scalar_internal(expr, None, None, &[], &mut tracker, None)
9671}
9672
9673fn translate_scalar_internal(
9674    expr: &SqlExpr,
9675    resolver: Option<&IdentifierResolver<'_>>,
9676    context: Option<&IdentifierContext>,
9677    outer_scopes: &[IdentifierContext],
9678    tracker: &mut SubqueryCorrelatedTracker<'_>,
9679    mut subquery_resolver: Option<&mut dyn ScalarSubqueryResolver>,
9680) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
9681    // Iterative postorder traversal using the TransformFrame pattern.
9682    // See llkv-plan::traversal module documentation for pattern details.
9683    //
9684    // This avoids stack overflow on deeply nested expressions (50k+ nodes) by using
9685    // explicit work_stack and result_stack instead of recursion.
9686
9687    /// Context passed through Exit frames during scalar expression translation
9688    enum ScalarExitContext {
9689        BinaryOp {
9690            op: BinaryOperator,
9691        },
9692        Compare {
9693            op: llkv_expr::expr::CompareOp,
9694        },
9695        UnaryNot,
9696        UnaryMinus,
9697        UnaryPlus,
9698        Nested,
9699        Cast(DataType),
9700        IsNull {
9701            negated: bool,
9702        },
9703        Between {
9704            negated: bool,
9705        },
9706        InList {
9707            list_len: usize,
9708            negated: bool,
9709        },
9710        Case {
9711            branch_count: usize,
9712            has_operand: bool,
9713            has_else: bool,
9714        },
9715        BuiltinFunction {
9716            func: BuiltinScalarFunction,
9717            arg_count: usize,
9718        },
9719    }
9720
9721    #[derive(Clone, Copy)]
9722    enum BuiltinScalarFunction {
9723        Abs,
9724        Coalesce,
9725        NullIf,
9726        Floor,
9727    }
9728
9729    type ScalarFrame<'a> =
9730        TransformFrame<'a, SqlExpr, llkv_expr::expr::ScalarExpr<String>, ScalarExitContext>;
9731
9732    let mut work_stack: Vec<ScalarFrame> = vec![ScalarFrame::Enter(expr)];
9733    let mut result_stack: Vec<llkv_expr::expr::ScalarExpr<String>> = Vec::new();
9734
9735    while let Some(frame) = work_stack.pop() {
9736        match frame {
9737            ScalarFrame::Enter(node) => match node {
9738                SqlExpr::Identifier(ident) => {
9739                    if let (Some(resolver), Some(ctx)) = (resolver, context) {
9740                        let parts = vec![ident.value.clone()];
9741                        let tracker_view = tracker.reborrow();
9742                        let expr = resolve_identifier_expr(
9743                            resolver,
9744                            ctx,
9745                            parts,
9746                            outer_scopes,
9747                            tracker_view,
9748                        )?;
9749                        work_stack.push(ScalarFrame::Leaf(expr));
9750                    } else {
9751                        work_stack.push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::column(
9752                            ident.value.clone(),
9753                        )));
9754                    }
9755                }
9756                SqlExpr::CompoundIdentifier(idents) => {
9757                    if idents.is_empty() {
9758                        return Err(Error::InvalidArgumentError(
9759                            "invalid compound identifier".into(),
9760                        ));
9761                    }
9762
9763                    if let (Some(resolver), Some(ctx)) = (resolver, context) {
9764                        let parts: Vec<String> =
9765                            idents.iter().map(|ident| ident.value.clone()).collect();
9766                        let tracker_view = tracker.reborrow();
9767                        let expr = resolve_identifier_expr(
9768                            resolver,
9769                            ctx,
9770                            parts,
9771                            outer_scopes,
9772                            tracker_view,
9773                        )?;
9774                        work_stack.push(ScalarFrame::Leaf(expr));
9775                    } else {
9776                        let column_name = idents[0].value.clone();
9777                        let mut result = llkv_expr::expr::ScalarExpr::column(column_name);
9778
9779                        for part in &idents[1..] {
9780                            let field_name = part.value.clone();
9781                            result = llkv_expr::expr::ScalarExpr::get_field(result, field_name);
9782                        }
9783
9784                        work_stack.push(ScalarFrame::Leaf(result));
9785                    }
9786                }
9787                SqlExpr::Value(value) => {
9788                    let result = literal_from_value(value)?;
9789                    work_stack.push(ScalarFrame::Leaf(result));
9790                }
9791                SqlExpr::Interval(interval) => {
9792                    let parsed = parse_interval_literal(interval)?;
9793                    let literal = llkv_expr::expr::ScalarExpr::literal(Literal::Interval(parsed));
9794                    work_stack.push(ScalarFrame::Leaf(literal));
9795                }
9796                SqlExpr::BinaryOp { left, op, right } => match op {
9797                    BinaryOperator::Plus
9798                    | BinaryOperator::Minus
9799                    | BinaryOperator::Multiply
9800                    | BinaryOperator::Divide
9801                    | BinaryOperator::Modulo
9802                    | BinaryOperator::And
9803                    | BinaryOperator::Or
9804                    | BinaryOperator::PGBitwiseShiftLeft
9805                    | BinaryOperator::PGBitwiseShiftRight => {
9806                        work_stack.push(ScalarFrame::Exit(ScalarExitContext::BinaryOp {
9807                            op: op.clone(),
9808                        }));
9809                        work_stack.push(ScalarFrame::Enter(right));
9810                        work_stack.push(ScalarFrame::Enter(left));
9811                    }
9812                    BinaryOperator::Eq
9813                    | BinaryOperator::NotEq
9814                    | BinaryOperator::Lt
9815                    | BinaryOperator::LtEq
9816                    | BinaryOperator::Gt
9817                    | BinaryOperator::GtEq => {
9818                        let compare_op = match op {
9819                            BinaryOperator::Eq => llkv_expr::expr::CompareOp::Eq,
9820                            BinaryOperator::NotEq => llkv_expr::expr::CompareOp::NotEq,
9821                            BinaryOperator::Lt => llkv_expr::expr::CompareOp::Lt,
9822                            BinaryOperator::LtEq => llkv_expr::expr::CompareOp::LtEq,
9823                            BinaryOperator::Gt => llkv_expr::expr::CompareOp::Gt,
9824                            BinaryOperator::GtEq => llkv_expr::expr::CompareOp::GtEq,
9825                            _ => unreachable!(),
9826                        };
9827                        work_stack.push(ScalarFrame::Exit(ScalarExitContext::Compare {
9828                            op: compare_op,
9829                        }));
9830                        work_stack.push(ScalarFrame::Enter(right));
9831                        work_stack.push(ScalarFrame::Enter(left));
9832                    }
9833                    other => {
9834                        return Err(Error::InvalidArgumentError(format!(
9835                            "unsupported scalar binary operator: {other:?}"
9836                        )));
9837                    }
9838                },
9839                SqlExpr::UnaryOp {
9840                    op: UnaryOperator::Not,
9841                    expr: inner,
9842                } => {
9843                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::UnaryNot));
9844                    work_stack.push(ScalarFrame::Enter(inner));
9845                }
9846                SqlExpr::UnaryOp {
9847                    op: UnaryOperator::Minus,
9848                    expr: inner,
9849                } => {
9850                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::UnaryMinus));
9851                    work_stack.push(ScalarFrame::Enter(inner));
9852                }
9853                SqlExpr::UnaryOp {
9854                    op: UnaryOperator::Plus,
9855                    expr: inner,
9856                } => {
9857                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::UnaryPlus));
9858                    work_stack.push(ScalarFrame::Enter(inner));
9859                }
9860                SqlExpr::Nested(inner) => {
9861                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Nested));
9862                    work_stack.push(ScalarFrame::Enter(inner));
9863                }
9864                SqlExpr::Cast {
9865                    expr: inner,
9866                    data_type,
9867                    ..
9868                } => {
9869                    let target_type = arrow_type_from_sql(data_type)?;
9870                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Cast(target_type)));
9871                    work_stack.push(ScalarFrame::Enter(inner));
9872                }
9873                SqlExpr::Case {
9874                    operand,
9875                    conditions,
9876                    else_result,
9877                    ..
9878                } => {
9879                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Case {
9880                        branch_count: conditions.len(),
9881                        has_operand: operand.is_some(),
9882                        has_else: else_result.is_some(),
9883                    }));
9884                    if let Some(else_expr) = else_result.as_deref() {
9885                        work_stack.push(ScalarFrame::Enter(else_expr));
9886                    }
9887                    for case_when in conditions.iter().rev() {
9888                        work_stack.push(ScalarFrame::Enter(&case_when.result));
9889                        work_stack.push(ScalarFrame::Enter(&case_when.condition));
9890                    }
9891                    if let Some(opnd) = operand.as_deref() {
9892                        work_stack.push(ScalarFrame::Enter(opnd));
9893                    }
9894                }
9895                SqlExpr::InList {
9896                    expr: in_expr,
9897                    list,
9898                    negated,
9899                } => {
9900                    if list.is_empty() {
9901                        let literal_value = if *negated {
9902                            llkv_expr::expr::ScalarExpr::literal(Literal::Int128(1))
9903                        } else {
9904                            llkv_expr::expr::ScalarExpr::literal(Literal::Int128(0))
9905                        };
9906                        work_stack.push(ScalarFrame::Leaf(literal_value));
9907                    } else {
9908                        work_stack.push(ScalarFrame::Exit(ScalarExitContext::InList {
9909                            list_len: list.len(),
9910                            negated: *negated,
9911                        }));
9912                        for value_expr in list.iter().rev() {
9913                            work_stack.push(ScalarFrame::Enter(value_expr));
9914                        }
9915                        work_stack.push(ScalarFrame::Enter(in_expr));
9916                    }
9917                }
9918                SqlExpr::IsNull(inner) => {
9919                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::IsNull {
9920                        negated: false,
9921                    }));
9922                    work_stack.push(ScalarFrame::Enter(inner));
9923                }
9924                SqlExpr::IsNotNull(inner) => {
9925                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::IsNull {
9926                        negated: true,
9927                    }));
9928                    work_stack.push(ScalarFrame::Enter(inner));
9929                }
9930                SqlExpr::Between {
9931                    expr: between_expr,
9932                    negated,
9933                    low,
9934                    high,
9935                } => {
9936                    let (effective_negated, normalized_expr) =
9937                        normalize_between_operand(between_expr, *negated);
9938                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Between {
9939                        negated: effective_negated,
9940                    }));
9941                    work_stack.push(ScalarFrame::Enter(high));
9942                    work_stack.push(ScalarFrame::Enter(low));
9943                    work_stack.push(ScalarFrame::Enter(normalized_expr));
9944                }
9945                SqlExpr::Function(func) => {
9946                    if let Some(agg_call) = try_parse_aggregate_function(
9947                        func,
9948                        resolver,
9949                        context,
9950                        outer_scopes,
9951                        tracker,
9952                    )? {
9953                        work_stack.push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::aggregate(
9954                            agg_call,
9955                        )));
9956                    } else {
9957                        use sqlparser::ast::{
9958                            FunctionArg, FunctionArgExpr, FunctionArguments, ObjectNamePart,
9959                        };
9960
9961                        if func.uses_odbc_syntax
9962                            || !matches!(func.parameters, FunctionArguments::None)
9963                            || func.filter.is_some()
9964                            || func.null_treatment.is_some()
9965                            || func.over.is_some()
9966                            || !func.within_group.is_empty()
9967                        {
9968                            return Err(Error::InvalidArgumentError(format!(
9969                                "unsupported function in scalar expression: {:?}",
9970                                func.name
9971                            )));
9972                        }
9973
9974                        let func_name = if func.name.0.len() == 1 {
9975                            match &func.name.0[0] {
9976                                ObjectNamePart::Identifier(ident) => {
9977                                    ident.value.to_ascii_lowercase()
9978                                }
9979                                _ => {
9980                                    return Err(Error::InvalidArgumentError(format!(
9981                                        "unsupported function in scalar expression: {:?}",
9982                                        func.name
9983                                    )));
9984                                }
9985                            }
9986                        } else {
9987                            return Err(Error::InvalidArgumentError(format!(
9988                                "unsupported function in scalar expression: {:?}",
9989                                func.name
9990                            )));
9991                        };
9992
9993                        match func_name.as_str() {
9994                            "abs" => {
9995                                let args_slice: &[FunctionArg] = match &func.args {
9996                                    FunctionArguments::List(list) => {
9997                                        if list.duplicate_treatment.is_some()
9998                                            || !list.clauses.is_empty()
9999                                        {
10000                                            return Err(Error::InvalidArgumentError(
10001                                                "ABS does not support qualifiers".into(),
10002                                            ));
10003                                        }
10004                                        &list.args
10005                                    }
10006                                    _ => {
10007                                        return Err(Error::InvalidArgumentError(
10008                                            "ABS requires exactly one argument".into(),
10009                                        ));
10010                                    }
10011                                };
10012
10013                                if args_slice.len() != 1 {
10014                                    return Err(Error::InvalidArgumentError(
10015                                        "ABS requires exactly one argument".into(),
10016                                    ));
10017                                }
10018
10019                                let arg_expr = match &args_slice[0] {
10020                                    FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
10021                                    _ => {
10022                                        return Err(Error::InvalidArgumentError(
10023                                            "ABS argument must be an expression".into(),
10024                                        ));
10025                                    }
10026                                };
10027
10028                                work_stack.push(ScalarFrame::Exit(
10029                                    ScalarExitContext::BuiltinFunction {
10030                                        func: BuiltinScalarFunction::Abs,
10031                                        arg_count: 1,
10032                                    },
10033                                ));
10034                                work_stack.push(ScalarFrame::Enter(arg_expr));
10035                                continue;
10036                            }
10037                            "floor" => {
10038                                let args_slice: &[FunctionArg] = match &func.args {
10039                                    FunctionArguments::List(list) => {
10040                                        if list.duplicate_treatment.is_some()
10041                                            || !list.clauses.is_empty()
10042                                        {
10043                                            return Err(Error::InvalidArgumentError(
10044                                                "FLOOR does not support qualifiers".into(),
10045                                            ));
10046                                        }
10047                                        &list.args
10048                                    }
10049                                    _ => {
10050                                        return Err(Error::InvalidArgumentError(
10051                                            "FLOOR requires exactly one argument".into(),
10052                                        ));
10053                                    }
10054                                };
10055
10056                                if args_slice.len() != 1 {
10057                                    return Err(Error::InvalidArgumentError(
10058                                        "FLOOR requires exactly one argument".into(),
10059                                    ));
10060                                }
10061
10062                                let arg_expr = match &args_slice[0] {
10063                                    FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
10064                                    _ => {
10065                                        return Err(Error::InvalidArgumentError(
10066                                            "FLOOR argument must be an expression".into(),
10067                                        ));
10068                                    }
10069                                };
10070
10071                                work_stack.push(ScalarFrame::Exit(
10072                                    ScalarExitContext::BuiltinFunction {
10073                                        func: BuiltinScalarFunction::Floor,
10074                                        arg_count: 1,
10075                                    },
10076                                ));
10077                                work_stack.push(ScalarFrame::Enter(arg_expr));
10078                                continue;
10079                            }
10080                            "coalesce" => {
10081                                let args_slice: &[FunctionArg] = match &func.args {
10082                                    FunctionArguments::List(list) => {
10083                                        if list.duplicate_treatment.is_some()
10084                                            || !list.clauses.is_empty()
10085                                        {
10086                                            return Err(Error::InvalidArgumentError(
10087                                                "COALESCE does not support qualifiers".into(),
10088                                            ));
10089                                        }
10090                                        &list.args
10091                                    }
10092                                    _ => {
10093                                        return Err(Error::InvalidArgumentError(
10094                                            "COALESCE requires at least one argument".into(),
10095                                        ));
10096                                    }
10097                                };
10098
10099                                if args_slice.is_empty() {
10100                                    return Err(Error::InvalidArgumentError(
10101                                        "COALESCE requires at least one argument".into(),
10102                                    ));
10103                                }
10104
10105                                work_stack.push(ScalarFrame::Exit(
10106                                    ScalarExitContext::BuiltinFunction {
10107                                        func: BuiltinScalarFunction::Coalesce,
10108                                        arg_count: args_slice.len(),
10109                                    },
10110                                ));
10111
10112                                for arg in args_slice.iter().rev() {
10113                                    let arg_expr = match arg {
10114                                        FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
10115                                        _ => {
10116                                            return Err(Error::InvalidArgumentError(
10117                                                "COALESCE arguments must be expressions".into(),
10118                                            ));
10119                                        }
10120                                    };
10121                                    work_stack.push(ScalarFrame::Enter(arg_expr));
10122                                }
10123                                continue;
10124                            }
10125                            "nullif" => {
10126                                let args_slice: &[FunctionArg] = match &func.args {
10127                                    FunctionArguments::List(list) => {
10128                                        if list.duplicate_treatment.is_some()
10129                                            || !list.clauses.is_empty()
10130                                        {
10131                                            return Err(Error::InvalidArgumentError(
10132                                                "NULLIF does not support qualifiers".into(),
10133                                            ));
10134                                        }
10135                                        &list.args
10136                                    }
10137                                    _ => {
10138                                        return Err(Error::InvalidArgumentError(
10139                                            "NULLIF requires exactly two arguments".into(),
10140                                        ));
10141                                    }
10142                                };
10143
10144                                if args_slice.len() != 2 {
10145                                    return Err(Error::InvalidArgumentError(
10146                                        "NULLIF requires exactly two arguments".into(),
10147                                    ));
10148                                }
10149
10150                                work_stack.push(ScalarFrame::Exit(
10151                                    ScalarExitContext::BuiltinFunction {
10152                                        func: BuiltinScalarFunction::NullIf,
10153                                        arg_count: 2,
10154                                    },
10155                                ));
10156
10157                                for arg in args_slice.iter().rev() {
10158                                    let arg_expr = match arg {
10159                                        FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => expr,
10160                                        _ => {
10161                                            return Err(Error::InvalidArgumentError(
10162                                                "NULLIF arguments must be expressions".into(),
10163                                            ));
10164                                        }
10165                                    };
10166                                    work_stack.push(ScalarFrame::Enter(arg_expr));
10167                                }
10168                                continue;
10169                            }
10170                            "random" | "rand" => {
10171                                let args_slice: &[FunctionArg] = match &func.args {
10172                                    FunctionArguments::List(list) => {
10173                                        if list.duplicate_treatment.is_some()
10174                                            || !list.clauses.is_empty()
10175                                        {
10176                                            return Err(Error::InvalidArgumentError(
10177                                                "RANDOM does not support qualifiers".into(),
10178                                            ));
10179                                        }
10180                                        &list.args
10181                                    }
10182                                    FunctionArguments::None => &[],
10183                                    _ => {
10184                                        return Err(Error::InvalidArgumentError(
10185                                            "RANDOM does not accept arguments".into(),
10186                                        ));
10187                                    }
10188                                };
10189
10190                                if !args_slice.is_empty() {
10191                                    return Err(Error::InvalidArgumentError(
10192                                        "RANDOM does not accept arguments".into(),
10193                                    ));
10194                                }
10195
10196                                work_stack
10197                                    .push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::random()));
10198                                continue;
10199                            }
10200                            _ => {
10201                                return Err(Error::InvalidArgumentError(format!(
10202                                    "unsupported function in scalar expression: {:?}",
10203                                    func.name
10204                                )));
10205                            }
10206                        }
10207                    }
10208                }
10209                SqlExpr::Dictionary(fields) => {
10210                    // Process dictionary fields iteratively to avoid recursion
10211                    let mut struct_fields = Vec::new();
10212                    for entry in fields {
10213                        let key = entry.key.value.clone();
10214                        // Reuse scalar translation for nested values while honoring identifier context.
10215                        // Dictionaries rarely nest deeply, so recursion here is acceptable.
10216                        let mut tracker_view = tracker.reborrow();
10217                        let value_expr = translate_scalar_internal(
10218                            &entry.value,
10219                            resolver,
10220                            context,
10221                            outer_scopes,
10222                            &mut tracker_view,
10223                            None,
10224                        )?;
10225                        match value_expr {
10226                            llkv_expr::expr::ScalarExpr::Literal(lit) => {
10227                                struct_fields.push((key, Box::new(lit)));
10228                            }
10229                            _ => {
10230                                return Err(Error::InvalidArgumentError(
10231                                    "Dictionary values must be literals".to_string(),
10232                                ));
10233                            }
10234                        }
10235                    }
10236                    work_stack.push(ScalarFrame::Leaf(llkv_expr::expr::ScalarExpr::literal(
10237                        Literal::Struct(struct_fields),
10238                    )));
10239                }
10240                SqlExpr::Subquery(subquery) => {
10241                    let handler = subquery_resolver.as_mut().ok_or_else(|| {
10242                        Error::InvalidArgumentError(
10243                            "Correlated scalar subqueries not yet fully implemented - requires plan-level support".
10244                                to_string(),
10245                        )
10246                    })?;
10247                    let resolver_ref = resolver.ok_or_else(|| {
10248                        Error::InvalidArgumentError(
10249                            "scalar subquery translation requires identifier resolver".into(),
10250                        )
10251                    })?;
10252                    let context_ref = context.ok_or_else(|| {
10253                        Error::InvalidArgumentError(
10254                            "scalar subquery translation requires identifier context".into(),
10255                        )
10256                    })?;
10257                    let translated = handler.handle_scalar_subquery(
10258                        subquery.as_ref(),
10259                        resolver_ref,
10260                        context_ref,
10261                        outer_scopes,
10262                    )?;
10263                    work_stack.push(ScalarFrame::Leaf(translated));
10264                }
10265                SqlExpr::Floor { expr, field } => {
10266                    // sqlparser treats FLOOR as datetime extraction when field is specified
10267                    // We only support simple FLOOR (NoDateTime field)
10268                    use sqlparser::ast::{CeilFloorKind, DateTimeField};
10269                    if !matches!(
10270                        field,
10271                        CeilFloorKind::DateTimeField(DateTimeField::NoDateTime)
10272                    ) {
10273                        return Err(Error::InvalidArgumentError(format!(
10274                            "FLOOR with datetime field or scale not supported: {field:?}"
10275                        )));
10276                    }
10277
10278                    // Treat FLOOR as a unary function: CAST(expr AS INT64)
10279                    // Note: CAST truncates towards zero, not true floor (towards -infinity)
10280                    work_stack.push(ScalarFrame::Exit(ScalarExitContext::Cast(DataType::Int64)));
10281                    work_stack.push(ScalarFrame::Enter(expr.as_ref()));
10282                }
10283                other => {
10284                    return Err(Error::InvalidArgumentError(format!(
10285                        "unsupported scalar expression: {other:?}"
10286                    )));
10287                }
10288            },
10289            ScalarFrame::Leaf(translated) => {
10290                result_stack.push(translated);
10291            }
10292            ScalarFrame::Exit(exit_context) => match exit_context {
10293                ScalarExitContext::BinaryOp { op } => {
10294                    let right_expr = result_stack.pop().ok_or_else(|| {
10295                        Error::Internal(
10296                            "translate_scalar: result stack underflow for BinaryOp right".into(),
10297                        )
10298                    })?;
10299                    let left_expr = result_stack.pop().ok_or_else(|| {
10300                        Error::Internal(
10301                            "translate_scalar: result stack underflow for BinaryOp left".into(),
10302                        )
10303                    })?;
10304                    match op {
10305                        BinaryOperator::Plus => {
10306                            let expr = llkv_expr::expr::ScalarExpr::binary(
10307                                left_expr,
10308                                llkv_expr::expr::BinaryOp::Add,
10309                                right_expr,
10310                            );
10311                            result_stack.push(expr);
10312                        }
10313                        BinaryOperator::Minus => {
10314                            let expr = llkv_expr::expr::ScalarExpr::binary(
10315                                left_expr,
10316                                llkv_expr::expr::BinaryOp::Subtract,
10317                                right_expr,
10318                            );
10319                            result_stack.push(expr);
10320                        }
10321                        BinaryOperator::Multiply => {
10322                            let expr = llkv_expr::expr::ScalarExpr::binary(
10323                                left_expr,
10324                                llkv_expr::expr::BinaryOp::Multiply,
10325                                right_expr,
10326                            );
10327                            result_stack.push(expr);
10328                        }
10329                        BinaryOperator::Divide => {
10330                            let expr = llkv_expr::expr::ScalarExpr::binary(
10331                                left_expr,
10332                                llkv_expr::expr::BinaryOp::Divide,
10333                                right_expr,
10334                            );
10335                            result_stack.push(expr);
10336                        }
10337                        BinaryOperator::Modulo => {
10338                            let expr = llkv_expr::expr::ScalarExpr::binary(
10339                                left_expr,
10340                                llkv_expr::expr::BinaryOp::Modulo,
10341                                right_expr,
10342                            );
10343                            result_stack.push(expr);
10344                        }
10345                        BinaryOperator::And => {
10346                            let expr = llkv_expr::expr::ScalarExpr::binary(
10347                                left_expr,
10348                                llkv_expr::expr::BinaryOp::And,
10349                                right_expr,
10350                            );
10351                            result_stack.push(expr);
10352                        }
10353                        BinaryOperator::Or => {
10354                            let expr = llkv_expr::expr::ScalarExpr::binary(
10355                                left_expr,
10356                                llkv_expr::expr::BinaryOp::Or,
10357                                right_expr,
10358                            );
10359                            result_stack.push(expr);
10360                        }
10361                        BinaryOperator::PGBitwiseShiftLeft => {
10362                            let expr = llkv_expr::expr::ScalarExpr::binary(
10363                                left_expr,
10364                                llkv_expr::expr::BinaryOp::BitwiseShiftLeft,
10365                                right_expr,
10366                            );
10367                            result_stack.push(expr);
10368                        }
10369                        BinaryOperator::PGBitwiseShiftRight => {
10370                            let expr = llkv_expr::expr::ScalarExpr::binary(
10371                                left_expr,
10372                                llkv_expr::expr::BinaryOp::BitwiseShiftRight,
10373                                right_expr,
10374                            );
10375                            result_stack.push(expr);
10376                        }
10377                        other => {
10378                            return Err(Error::InvalidArgumentError(format!(
10379                                "unsupported scalar binary operator: {other:?}"
10380                            )));
10381                        }
10382                    }
10383                }
10384                ScalarExitContext::Compare { op } => {
10385                    let right_expr = result_stack.pop().ok_or_else(|| {
10386                        Error::Internal(
10387                            "translate_scalar: result stack underflow for Compare right".into(),
10388                        )
10389                    })?;
10390                    let left_expr = result_stack.pop().ok_or_else(|| {
10391                        Error::Internal(
10392                            "translate_scalar: result stack underflow for Compare left".into(),
10393                        )
10394                    })?;
10395                    result_stack.push(llkv_expr::expr::ScalarExpr::compare(
10396                        left_expr, op, right_expr,
10397                    ));
10398                }
10399                ScalarExitContext::BuiltinFunction { func, arg_count } => {
10400                    if result_stack.len() < arg_count {
10401                        return Err(Error::Internal(
10402                            "translate_scalar: result stack underflow for builtin function".into(),
10403                        ));
10404                    }
10405
10406                    let mut args: Vec<llkv_expr::expr::ScalarExpr<String>> =
10407                        Vec::with_capacity(arg_count);
10408                    for _ in 0..arg_count {
10409                        if let Some(expr) = result_stack.pop() {
10410                            args.push(expr);
10411                        }
10412                    }
10413                    args.reverse();
10414
10415                    let result_expr = match func {
10416                        BuiltinScalarFunction::Abs => {
10417                            debug_assert_eq!(args.len(), 1);
10418                            build_abs_case_expr(args.pop().expect("ABS expects one argument"))
10419                        }
10420                        BuiltinScalarFunction::Coalesce => {
10421                            llkv_expr::expr::ScalarExpr::coalesce(args)
10422                        }
10423                        BuiltinScalarFunction::NullIf => {
10424                            debug_assert_eq!(args.len(), 2);
10425                            let left = args.remove(0);
10426                            let right = args.remove(0);
10427                            let condition = llkv_expr::expr::ScalarExpr::compare(
10428                                left.clone(),
10429                                llkv_expr::expr::CompareOp::Eq,
10430                                right,
10431                            );
10432                            llkv_expr::expr::ScalarExpr::Case {
10433                                operand: None,
10434                                branches: vec![(
10435                                    condition,
10436                                    llkv_expr::expr::ScalarExpr::literal(Literal::Null),
10437                                )],
10438                                else_expr: Some(Box::new(left)),
10439                            }
10440                        }
10441                        BuiltinScalarFunction::Floor => {
10442                            debug_assert_eq!(args.len(), 1);
10443                            let arg = args.pop().expect("FLOOR expects one argument");
10444                            // Implement FLOOR as CAST to INT64 which truncates towards zero
10445                            // Note: This is not mathematically correct for negative numbers
10446                            // (should round towards negative infinity), but works for positive values
10447                            llkv_expr::expr::ScalarExpr::cast(arg, DataType::Int64)
10448                        }
10449                    };
10450
10451                    result_stack.push(result_expr);
10452                }
10453                ScalarExitContext::UnaryMinus => {
10454                    let inner = result_stack.pop().ok_or_else(|| {
10455                        Error::Internal(
10456                            "translate_scalar: result stack underflow for UnaryMinus".into(),
10457                        )
10458                    })?;
10459                    match inner {
10460                        llkv_expr::expr::ScalarExpr::Literal(lit) => match lit {
10461                            Literal::Int128(v) => {
10462                                result_stack.push(llkv_expr::expr::ScalarExpr::literal(
10463                                    Literal::Int128(-v),
10464                                ));
10465                            }
10466                            Literal::Float64(v) => {
10467                                result_stack.push(llkv_expr::expr::ScalarExpr::literal(
10468                                    Literal::Float64(-v),
10469                                ));
10470                            }
10471                            Literal::Boolean(_) => {
10472                                return Err(Error::InvalidArgumentError(
10473                                    "cannot negate boolean literal".into(),
10474                                ));
10475                            }
10476                            Literal::String(_) => {
10477                                return Err(Error::InvalidArgumentError(
10478                                    "cannot negate string literal".into(),
10479                                ));
10480                            }
10481                            Literal::Struct(_) => {
10482                                return Err(Error::InvalidArgumentError(
10483                                    "cannot negate struct literal".into(),
10484                                ));
10485                            }
10486                            Literal::Date32(_) => {
10487                                return Err(Error::InvalidArgumentError(
10488                                    "cannot negate date literal".into(),
10489                                ));
10490                            }
10491                            Literal::Interval(interval) => {
10492                                let negated = interval.checked_neg().ok_or_else(|| {
10493                                    Error::InvalidArgumentError("interval overflow".into())
10494                                })?;
10495                                result_stack.push(llkv_expr::expr::ScalarExpr::literal(
10496                                    Literal::Interval(negated),
10497                                ));
10498                            }
10499                            Literal::Null => {
10500                                result_stack
10501                                    .push(llkv_expr::expr::ScalarExpr::literal(Literal::Null));
10502                            }
10503                            Literal::Decimal128(value) => {
10504                                let negated_raw =
10505                                    value.raw_value().checked_neg().ok_or_else(|| {
10506                                        Error::InvalidArgumentError(
10507                                            "decimal overflow when applying unary minus".into(),
10508                                        )
10509                                    })?;
10510                                let negated = DecimalValue::new(negated_raw, value.scale())
10511                                    .map_err(|err| {
10512                                        Error::InvalidArgumentError(format!(
10513                                            "failed to negate decimal literal: {err}"
10514                                        ))
10515                                    })?;
10516                                result_stack.push(llkv_expr::expr::ScalarExpr::literal(
10517                                    Literal::Decimal128(negated),
10518                                ));
10519                            }
10520                        },
10521                        other => {
10522                            let zero = llkv_expr::expr::ScalarExpr::literal(Literal::Int128(0));
10523                            result_stack.push(llkv_expr::expr::ScalarExpr::binary(
10524                                zero,
10525                                llkv_expr::expr::BinaryOp::Subtract,
10526                                other,
10527                            ));
10528                        }
10529                    }
10530                }
10531                ScalarExitContext::UnaryNot => {
10532                    let inner = result_stack.pop().ok_or_else(|| {
10533                        Error::Internal(
10534                            "translate_scalar: result stack underflow for UnaryNot".into(),
10535                        )
10536                    })?;
10537                    result_stack.push(llkv_expr::expr::ScalarExpr::logical_not(inner));
10538                }
10539                ScalarExitContext::UnaryPlus => {
10540                    // Unary plus is an identity operation in SQL - it returns the value unchanged.
10541                    // Unlike unary minus, it does NOT force numeric conversion; it's purely syntactic.
10542                    // SQLite treats `+col` identically to `col` in all contexts.
10543                    let inner = result_stack.pop().ok_or_else(|| {
10544                        Error::Internal(
10545                            "translate_scalar: result stack underflow for UnaryPlus".into(),
10546                        )
10547                    })?;
10548                    result_stack.push(inner);
10549                }
10550                ScalarExitContext::Nested => {
10551                    // Nested is a no-op - just pass through
10552                }
10553                ScalarExitContext::Cast(target_type) => {
10554                    let inner = result_stack.pop().ok_or_else(|| {
10555                        Error::Internal("translate_scalar: result stack underflow for CAST".into())
10556                    })?;
10557                    result_stack.push(llkv_expr::expr::ScalarExpr::cast(inner, target_type));
10558                }
10559                ScalarExitContext::InList { list_len, negated } => {
10560                    let mut list_exprs = Vec::with_capacity(list_len);
10561                    for _ in 0..list_len {
10562                        let value_expr = result_stack.pop().ok_or_else(|| {
10563                            Error::Internal(
10564                                "translate_scalar: result stack underflow for IN list value".into(),
10565                            )
10566                        })?;
10567                        list_exprs.push(value_expr);
10568                    }
10569                    list_exprs.reverse();
10570
10571                    let target_expr = result_stack.pop().ok_or_else(|| {
10572                        Error::Internal(
10573                            "translate_scalar: result stack underflow for IN list target".into(),
10574                        )
10575                    })?;
10576
10577                    let mut comparisons: Vec<llkv_expr::expr::ScalarExpr<String>> =
10578                        Vec::with_capacity(list_len);
10579                    for value in &list_exprs {
10580                        comparisons.push(llkv_expr::expr::ScalarExpr::compare(
10581                            target_expr.clone(),
10582                            llkv_expr::expr::CompareOp::Eq,
10583                            value.clone(),
10584                        ));
10585                    }
10586
10587                    let mut branches: Vec<(
10588                        llkv_expr::expr::ScalarExpr<String>,
10589                        llkv_expr::expr::ScalarExpr<String>,
10590                    )> = Vec::with_capacity(list_len.saturating_mul(2));
10591
10592                    for comparison in &comparisons {
10593                        branches.push((
10594                            comparison.clone(),
10595                            llkv_expr::expr::ScalarExpr::literal(Literal::Int128(1)),
10596                        ));
10597                    }
10598
10599                    for comparison in comparisons {
10600                        let comparison_is_null =
10601                            llkv_expr::expr::ScalarExpr::is_null(comparison, false);
10602                        branches.push((
10603                            comparison_is_null,
10604                            llkv_expr::expr::ScalarExpr::literal(Literal::Null),
10605                        ));
10606                    }
10607
10608                    let else_expr = Some(llkv_expr::expr::ScalarExpr::literal(Literal::Int128(0)));
10609                    let in_result = llkv_expr::expr::ScalarExpr::case(None, branches, else_expr);
10610                    let final_expr = if negated {
10611                        llkv_expr::expr::ScalarExpr::logical_not(in_result)
10612                    } else {
10613                        in_result
10614                    };
10615
10616                    result_stack.push(final_expr);
10617                }
10618                ScalarExitContext::Case {
10619                    branch_count,
10620                    has_operand,
10621                    has_else,
10622                } => {
10623                    let else_expr = if has_else {
10624                        Some(result_stack.pop().ok_or_else(|| {
10625                            Error::Internal(
10626                                "translate_scalar: result stack underflow for CASE ELSE".into(),
10627                            )
10628                        })?)
10629                    } else {
10630                        None
10631                    };
10632
10633                    let mut branches_rev = Vec::with_capacity(branch_count);
10634                    for _ in 0..branch_count {
10635                        let then_expr = result_stack.pop().ok_or_else(|| {
10636                            Error::Internal(
10637                                "translate_scalar: result stack underflow for CASE THEN".into(),
10638                            )
10639                        })?;
10640                        let when_expr = result_stack.pop().ok_or_else(|| {
10641                            Error::Internal(
10642                                "translate_scalar: result stack underflow for CASE WHEN".into(),
10643                            )
10644                        })?;
10645                        branches_rev.push((when_expr, then_expr));
10646                    }
10647                    branches_rev.reverse();
10648
10649                    let operand_expr = if has_operand {
10650                        Some(result_stack.pop().ok_or_else(|| {
10651                            Error::Internal(
10652                                "translate_scalar: result stack underflow for CASE operand".into(),
10653                            )
10654                        })?)
10655                    } else {
10656                        None
10657                    };
10658
10659                    let case_expr =
10660                        llkv_expr::expr::ScalarExpr::case(operand_expr, branches_rev, else_expr);
10661                    result_stack.push(case_expr);
10662                }
10663                ScalarExitContext::IsNull { negated } => {
10664                    let inner = result_stack.pop().ok_or_else(|| {
10665                        Error::Internal(
10666                            "translate_scalar: result stack underflow for IS NULL operand".into(),
10667                        )
10668                    })?;
10669                    result_stack.push(llkv_expr::expr::ScalarExpr::is_null(inner, negated));
10670                }
10671                ScalarExitContext::Between { negated } => {
10672                    let high = result_stack.pop().ok_or_else(|| {
10673                        Error::Internal(
10674                            "translate_scalar: result stack underflow for BETWEEN upper".into(),
10675                        )
10676                    })?;
10677                    let low = result_stack.pop().ok_or_else(|| {
10678                        Error::Internal(
10679                            "translate_scalar: result stack underflow for BETWEEN lower".into(),
10680                        )
10681                    })?;
10682                    let expr_value = result_stack.pop().ok_or_else(|| {
10683                        Error::Internal(
10684                            "translate_scalar: result stack underflow for BETWEEN operand".into(),
10685                        )
10686                    })?;
10687
10688                    let between_expr = if negated {
10689                        let less_than = llkv_expr::expr::ScalarExpr::compare(
10690                            expr_value.clone(),
10691                            llkv_expr::expr::CompareOp::Lt,
10692                            low.clone(),
10693                        );
10694                        let greater_than = llkv_expr::expr::ScalarExpr::compare(
10695                            expr_value,
10696                            llkv_expr::expr::CompareOp::Gt,
10697                            high,
10698                        );
10699                        llkv_expr::expr::ScalarExpr::binary(
10700                            less_than,
10701                            llkv_expr::expr::BinaryOp::Or,
10702                            greater_than,
10703                        )
10704                    } else {
10705                        let greater_or_equal = llkv_expr::expr::ScalarExpr::compare(
10706                            expr_value.clone(),
10707                            llkv_expr::expr::CompareOp::GtEq,
10708                            low,
10709                        );
10710                        let less_or_equal = llkv_expr::expr::ScalarExpr::compare(
10711                            expr_value,
10712                            llkv_expr::expr::CompareOp::LtEq,
10713                            high,
10714                        );
10715                        llkv_expr::expr::ScalarExpr::binary(
10716                            greater_or_equal,
10717                            llkv_expr::expr::BinaryOp::And,
10718                            less_or_equal,
10719                        )
10720                    };
10721                    result_stack.push(between_expr);
10722                }
10723            },
10724        }
10725    }
10726
10727    result_stack
10728        .pop()
10729        .ok_or_else(|| Error::Internal("translate_scalar: empty result stack".into()))
10730}
10731
10732fn infer_query_output_type(
10733    engine: &SqlEngine,
10734    plan: &llkv_plan::SelectPlan,
10735) -> SqlResult<DataType> {
10736    if !plan.aggregates.is_empty() {
10737        if plan.aggregates.len() != 1 {
10738            return Err(Error::InvalidArgumentError(
10739                "Scalar subquery must return exactly one column".into(),
10740            ));
10741        }
10742        let agg = &plan.aggregates[0];
10743        return match agg {
10744            llkv_plan::AggregateExpr::CountStar { .. } => Ok(DataType::Int64),
10745            llkv_plan::AggregateExpr::Column { function, .. } => match function {
10746                llkv_plan::AggregateFunction::Count
10747                | llkv_plan::AggregateFunction::SumInt64
10748                | llkv_plan::AggregateFunction::TotalInt64
10749                | llkv_plan::AggregateFunction::MinInt64
10750                | llkv_plan::AggregateFunction::MaxInt64
10751                | llkv_plan::AggregateFunction::CountNulls => Ok(DataType::Int64),
10752                llkv_plan::AggregateFunction::GroupConcat => Ok(DataType::Utf8),
10753            },
10754        };
10755    }
10756
10757    if plan.projections.len() != 1 {
10758        return Err(Error::InvalidArgumentError(
10759            "Scalar subquery must return exactly one column".into(),
10760        ));
10761    }
10762    match &plan.projections[0] {
10763        llkv_plan::SelectProjection::Computed { expr, .. } => infer_expr_type(engine, expr, plan),
10764        llkv_plan::SelectProjection::Column { name, .. } => {
10765            resolve_column_type_in_plan(engine, name, plan)
10766        }
10767        _ => Ok(DataType::Int64),
10768    }
10769}
10770
10771fn infer_expr_type(
10772    engine: &SqlEngine,
10773    expr: &llkv_expr::expr::ScalarExpr<String>,
10774    plan: &llkv_plan::SelectPlan,
10775) -> SqlResult<DataType> {
10776    use llkv_expr::expr::ScalarExpr;
10777    match expr {
10778        ScalarExpr::Literal(lit) => Ok(match lit {
10779            Literal::Int128(_) => DataType::Int64,
10780            Literal::Float64(_) => DataType::Float64,
10781            Literal::Decimal128(v) => DataType::Decimal128(v.precision(), v.scale()),
10782            Literal::Boolean(_) => DataType::Boolean,
10783            Literal::String(_) => DataType::Utf8,
10784            Literal::Date32(_) => DataType::Date32,
10785            Literal::Null => DataType::Null,
10786            Literal::Struct(_) => DataType::Utf8,
10787            Literal::Interval(_) => {
10788                DataType::Interval(arrow::datatypes::IntervalUnit::MonthDayNano)
10789            }
10790        }),
10791        ScalarExpr::Column(name) => resolve_column_type_in_plan(engine, name, plan),
10792        ScalarExpr::Binary { left, right, .. } => {
10793            let l = infer_expr_type(engine, left, plan)?;
10794            let r = infer_expr_type(engine, right, plan)?;
10795            if matches!(l, DataType::Float64) || matches!(r, DataType::Float64) {
10796                Ok(DataType::Float64)
10797            } else if let DataType::Decimal128(_, s1) = l {
10798                if let DataType::Decimal128(_, s2) = r {
10799                    Ok(DataType::Decimal128(38, s1.max(s2)))
10800                } else {
10801                    Ok(DataType::Decimal128(38, s1))
10802                }
10803            } else if let DataType::Decimal128(_, s2) = r {
10804                Ok(DataType::Decimal128(38, s2))
10805            } else {
10806                Ok(l)
10807            }
10808        }
10809        ScalarExpr::Cast { data_type, .. } => Ok(data_type.clone()),
10810        ScalarExpr::ScalarSubquery(sub) => Ok(sub.data_type.clone()),
10811        ScalarExpr::Aggregate(_) => Ok(DataType::Float64),
10812        _ => Ok(DataType::Int64),
10813    }
10814}
10815
10816fn resolve_column_type_in_plan(
10817    engine: &SqlEngine,
10818    col_name: &str,
10819    plan: &llkv_plan::SelectPlan,
10820) -> SqlResult<DataType> {
10821    for table_ref in &plan.tables {
10822        if let Ok((_, canonical)) = llkv_table::resolvers::canonical_table_name(&table_ref.table)
10823            && let Ok(table) = engine.engine.context().lookup_table(&canonical)
10824        {
10825            let (target_col, matches_alias) = if let Some(alias) = &table_ref.alias {
10826                if col_name.starts_with(alias) && col_name.chars().nth(alias.len()) == Some('.') {
10827                    (&col_name[alias.len() + 1..], true)
10828                } else {
10829                    (col_name, false)
10830                }
10831            } else {
10832                (col_name, false)
10833            };
10834
10835            let target_col = if !matches_alias
10836                && col_name.starts_with(&table_ref.table)
10837                && col_name.chars().nth(table_ref.table.len()) == Some('.')
10838            {
10839                &col_name[table_ref.table.len() + 1..]
10840            } else {
10841                target_col
10842            };
10843
10844            if let Some(field) = table.schema.resolve(target_col) {
10845                return Ok(field.data_type.clone());
10846            }
10847        }
10848    }
10849    Ok(DataType::Int64)
10850}
10851struct ScalarSubqueryPlanner<'engine, 'vec> {
10852    engine: &'engine SqlEngine,
10853    scalar_subqueries: &'vec mut Vec<llkv_plan::ScalarSubquery>,
10854}
10855
10856impl<'engine, 'vec> ScalarSubqueryResolver for ScalarSubqueryPlanner<'engine, 'vec> {
10857    fn handle_scalar_subquery(
10858        &mut self,
10859        subquery: &Query,
10860        resolver: &IdentifierResolver<'_>,
10861        context: &IdentifierContext,
10862        outer_scopes: &[IdentifierContext],
10863    ) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
10864        let mut nested_scopes = outer_scopes.to_vec();
10865        nested_scopes.push(context.clone());
10866        let mut tracker = SubqueryCorrelatedColumnTracker::new();
10867        let mut nested_filter_subqueries = Vec::new();
10868
10869        let plan = self.engine.build_select_plan_internal(
10870            subquery.clone(),
10871            resolver,
10872            &nested_scopes,
10873            &mut nested_filter_subqueries,
10874            Some(&mut tracker),
10875        )?;
10876
10877        debug_assert!(nested_filter_subqueries.is_empty());
10878
10879        let id = u32::try_from(self.scalar_subqueries.len()).map_err(|_| {
10880            Error::InvalidArgumentError(
10881                "scalar subquery limit exceeded for current query".to_string(),
10882            )
10883        })?;
10884        let subquery_id = llkv_expr::SubqueryId(id);
10885        self.scalar_subqueries.push(llkv_plan::ScalarSubquery {
10886            id: subquery_id,
10887            plan: Box::new(plan.clone()),
10888            correlated_columns: tracker.into_columns(),
10889        });
10890
10891        let data_type = infer_query_output_type(self.engine, &plan)?;
10892        Ok(llkv_expr::expr::ScalarExpr::scalar_subquery(
10893            subquery_id,
10894            data_type,
10895        ))
10896    }
10897}
10898
10899fn build_abs_case_expr(
10900    arg: llkv_expr::expr::ScalarExpr<String>,
10901) -> llkv_expr::expr::ScalarExpr<String> {
10902    use llkv_expr::expr::{BinaryOp, CompareOp, ScalarExpr};
10903
10904    let zero = ScalarExpr::literal(Literal::Int128(0));
10905    let condition = ScalarExpr::compare(arg.clone(), CompareOp::Lt, zero.clone());
10906    let negated = ScalarExpr::binary(zero.clone(), BinaryOp::Subtract, arg.clone());
10907
10908    ScalarExpr::case(None, vec![(condition, negated)], Some(arg))
10909}
10910
10911fn literal_from_value(value: &ValueWithSpan) -> SqlResult<llkv_expr::expr::ScalarExpr<String>> {
10912    match &value.value {
10913        Value::Placeholder(name) => {
10914            let index = register_placeholder(name)?;
10915            Ok(llkv_expr::expr::ScalarExpr::literal(literal_placeholder(
10916                index,
10917            )))
10918        }
10919        Value::Number(text, _) => {
10920            if text.contains(['.', 'e', 'E']) {
10921                // Try parsing as Decimal first to preserve precision
10922                if let Ok(decimal) = text.parse::<DecimalValue>() {
10923                    Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Decimal128(
10924                        decimal,
10925                    )))
10926                } else {
10927                    let parsed = text.parse::<f64>().map_err(|err| {
10928                        Error::InvalidArgumentError(format!("invalid float literal: {err}"))
10929                    })?;
10930                    Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Float64(
10931                        parsed,
10932                    )))
10933                }
10934            } else {
10935                let parsed = text.parse::<i128>().map_err(|err| {
10936                    Error::InvalidArgumentError(format!("invalid integer literal: {err}"))
10937                })?;
10938                Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Int128(
10939                    parsed,
10940                )))
10941            }
10942        }
10943        Value::Boolean(value) => Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Boolean(
10944            *value,
10945        ))),
10946        Value::Null => Ok(llkv_expr::expr::ScalarExpr::literal(Literal::Null)),
10947        other => {
10948            if let Some(text) = other.clone().into_string() {
10949                Ok(llkv_expr::expr::ScalarExpr::literal(Literal::String(text)))
10950            } else {
10951                Err(Error::InvalidArgumentError(format!(
10952                    "unsupported literal: {other:?}"
10953                )))
10954            }
10955        }
10956    }
10957}
10958
10959fn resolve_assignment_column_name(target: &AssignmentTarget) -> SqlResult<String> {
10960    match target {
10961        AssignmentTarget::ColumnName(name) => {
10962            if name.0.len() != 1 {
10963                return Err(Error::InvalidArgumentError(
10964                    "qualified column names in UPDATE assignments are not supported yet".into(),
10965                ));
10966            }
10967            match &name.0[0] {
10968                ObjectNamePart::Identifier(ident) => Ok(ident.value.clone()),
10969                other => Err(Error::InvalidArgumentError(format!(
10970                    "unsupported column reference in UPDATE assignment: {other:?}"
10971                ))),
10972            }
10973        }
10974        AssignmentTarget::Tuple(_) => Err(Error::InvalidArgumentError(
10975            "tuple assignments are not supported yet".into(),
10976        )),
10977    }
10978}
10979
10980fn arrow_type_from_sql(data_type: &SqlDataType) -> SqlResult<arrow::datatypes::DataType> {
10981    use arrow::datatypes::DataType;
10982    match data_type {
10983        SqlDataType::Int(_)
10984        | SqlDataType::Integer(_)
10985        | SqlDataType::BigInt(_)
10986        | SqlDataType::SmallInt(_)
10987        | SqlDataType::TinyInt(_) => Ok(DataType::Int64),
10988        SqlDataType::Float(_)
10989        | SqlDataType::Real
10990        | SqlDataType::Double(_)
10991        | SqlDataType::DoublePrecision => Ok(DataType::Float64),
10992        SqlDataType::Text
10993        | SqlDataType::String(_)
10994        | SqlDataType::Varchar(_)
10995        | SqlDataType::Char(_)
10996        | SqlDataType::Uuid => Ok(DataType::Utf8),
10997        SqlDataType::Date => Ok(DataType::Date32),
10998        SqlDataType::Decimal(exact_number_info) | SqlDataType::Numeric(exact_number_info) => {
10999            // Parse DECIMAL(precision, scale) or use defaults
11000            match exact_number_info {
11001                sqlparser::ast::ExactNumberInfo::PrecisionAndScale(p, s) => {
11002                    Ok(DataType::Decimal128(*p as u8, *s as i8))
11003                }
11004                sqlparser::ast::ExactNumberInfo::Precision(p) => {
11005                    // DECIMAL(p) means scale = 0
11006                    Ok(DataType::Decimal128(*p as u8, 0))
11007                }
11008                sqlparser::ast::ExactNumberInfo::None => {
11009                    // DECIMAL without precision defaults to DECIMAL(38, 0) per SQL standard
11010                    Ok(DataType::Decimal128(38, 0))
11011                }
11012            }
11013        }
11014        SqlDataType::Boolean => Ok(DataType::Boolean),
11015        SqlDataType::Custom(name, args) => {
11016            if name.0.len() == 1
11017                && let ObjectNamePart::Identifier(ident) = &name.0[0]
11018                && ident.value.eq_ignore_ascii_case("row")
11019            {
11020                return row_type_to_arrow(data_type, args);
11021            }
11022            Err(Error::InvalidArgumentError(format!(
11023                "unsupported SQL data type: {data_type:?}"
11024            )))
11025        }
11026        other => Err(Error::InvalidArgumentError(format!(
11027            "unsupported SQL data type: {other:?}"
11028        ))),
11029    }
11030}
11031
11032fn row_type_to_arrow(
11033    data_type: &SqlDataType,
11034    tokens: &[String],
11035) -> SqlResult<arrow::datatypes::DataType> {
11036    use arrow::datatypes::{DataType, Field, FieldRef, Fields};
11037
11038    let row_str = data_type.to_string();
11039    if tokens.is_empty() {
11040        return Err(Error::InvalidArgumentError(
11041            "ROW type must define at least one field".into(),
11042        ));
11043    }
11044
11045    let dialect = GenericDialect {};
11046    let field_definitions = resolve_row_field_types(tokens, &dialect).map_err(|err| {
11047        Error::InvalidArgumentError(format!("unable to parse ROW type '{row_str}': {err}"))
11048    })?;
11049
11050    let mut fields: Vec<FieldRef> = Vec::with_capacity(field_definitions.len());
11051    for (field_name, field_type) in field_definitions {
11052        let arrow_field_type = arrow_type_from_sql(&field_type)?;
11053        fields.push(Arc::new(Field::new(field_name, arrow_field_type, true)));
11054    }
11055
11056    let struct_fields: Fields = fields.into();
11057    Ok(DataType::Struct(struct_fields))
11058}
11059
11060fn resolve_row_field_types(
11061    tokens: &[String],
11062    dialect: &GenericDialect,
11063) -> SqlResult<Vec<(String, SqlDataType)>> {
11064    if tokens.is_empty() {
11065        return Err(Error::InvalidArgumentError(
11066            "ROW type must define at least one field".into(),
11067        ));
11068    }
11069
11070    let mut start = 0;
11071    let mut end = tokens.len();
11072    if tokens[start] == "(" {
11073        if end == 0 || tokens[end - 1] != ")" {
11074            return Err(Error::InvalidArgumentError(
11075                "ROW type is missing closing ')'".into(),
11076            ));
11077        }
11078        start += 1;
11079        end -= 1;
11080    } else if tokens[end - 1] == ")" {
11081        return Err(Error::InvalidArgumentError(
11082            "ROW type contains unmatched ')'".into(),
11083        ));
11084    }
11085
11086    let slice = &tokens[start..end];
11087    if slice.is_empty() {
11088        return Err(Error::InvalidArgumentError(
11089            "ROW type did not provide any field definitions".into(),
11090        ));
11091    }
11092
11093    let mut fields = Vec::new();
11094    let mut index = 0;
11095
11096    while index < slice.len() {
11097        if slice[index] == "," {
11098            index += 1;
11099            continue;
11100        }
11101
11102        let field_name = normalize_row_field_name(&slice[index])?;
11103        index += 1;
11104
11105        if index >= slice.len() {
11106            return Err(Error::InvalidArgumentError(format!(
11107                "ROW field '{field_name}' is missing a type specification"
11108            )));
11109        }
11110
11111        let mut last_success: Option<(usize, SqlDataType)> = None;
11112        let mut type_end = index;
11113
11114        while type_end <= slice.len() {
11115            let candidate = slice[index..type_end].join(" ");
11116            if candidate.trim().is_empty() {
11117                type_end += 1;
11118                continue;
11119            }
11120
11121            if let Ok(parsed_type) = parse_sql_data_type(&candidate, dialect) {
11122                last_success = Some((type_end, parsed_type));
11123            }
11124
11125            if type_end == slice.len() {
11126                break;
11127            }
11128
11129            if slice[type_end] == "," && last_success.is_some() {
11130                break;
11131            }
11132
11133            type_end += 1;
11134        }
11135
11136        let Some((next_index, data_type)) = last_success else {
11137            return Err(Error::InvalidArgumentError(format!(
11138                "failed to parse ROW field type for '{field_name}'"
11139            )));
11140        };
11141
11142        fields.push((field_name, data_type));
11143        index = next_index;
11144
11145        if index < slice.len() && slice[index] == "," {
11146            index += 1;
11147        }
11148    }
11149
11150    if fields.is_empty() {
11151        return Err(Error::InvalidArgumentError(
11152            "ROW type did not provide any field definitions".into(),
11153        ));
11154    }
11155
11156    Ok(fields)
11157}
11158
11159/// Parse SQL string into statements with increased recursion limit.
11160///
11161/// This helper wraps sqlparser's `Parser` with a custom recursion limit to handle
11162/// deeply nested queries that exceed the default limit of 50.
11163///
11164/// # Arguments
11165///
11166/// * `dialect` - SQL dialect to use for parsing
11167/// * `sql` - SQL string to parse
11168///
11169/// # Returns
11170///
11171/// Parsed statements or parser error
11172fn parse_sql_with_recursion_limit(
11173    dialect: &GenericDialect,
11174    sql: &str,
11175) -> Result<Vec<Statement>, sqlparser::parser::ParserError> {
11176    Parser::new(dialect)
11177        .with_recursion_limit(PARSER_RECURSION_LIMIT)
11178        .try_with_sql(sql)?
11179        .parse_statements()
11180}
11181
11182fn normalize_row_field_name(raw: &str) -> SqlResult<String> {
11183    let trimmed = raw.trim();
11184    if trimmed.is_empty() {
11185        return Err(Error::InvalidArgumentError(
11186            "ROW field name must not be empty".into(),
11187        ));
11188    }
11189
11190    if let Some(stripped) = trimmed.strip_prefix('"') {
11191        let without_end = stripped.strip_suffix('"').ok_or_else(|| {
11192            Error::InvalidArgumentError(format!("unterminated quoted ROW field name: {trimmed}"))
11193        })?;
11194        let name = without_end.replace("\"\"", "\"");
11195        return Ok(name);
11196    }
11197
11198    Ok(trimmed.to_string())
11199}
11200
11201fn parse_sql_data_type(type_str: &str, dialect: &GenericDialect) -> SqlResult<SqlDataType> {
11202    let trimmed = type_str.trim();
11203    let sql = format!("CREATE TABLE __row(__field {trimmed});");
11204    let statements = parse_sql_with_recursion_limit(dialect, &sql).map_err(|err| {
11205        Error::InvalidArgumentError(format!("failed to parse ROW field type '{trimmed}': {err}"))
11206    })?;
11207
11208    let stmt = statements.into_iter().next().ok_or_else(|| {
11209        Error::InvalidArgumentError(format!(
11210            "ROW field type '{trimmed}' did not produce a statement"
11211        ))
11212    })?;
11213
11214    match stmt {
11215        Statement::CreateTable(table) => table
11216            .columns
11217            .first()
11218            .map(|col| col.data_type.clone())
11219            .ok_or_else(|| {
11220                Error::InvalidArgumentError(format!(
11221                    "ROW field type '{trimmed}' missing column definition"
11222                ))
11223            }),
11224        other => Err(Error::InvalidArgumentError(format!(
11225            "unexpected statement while parsing ROW field type: {other:?}"
11226        ))),
11227    }
11228}
11229
11230/// Extract VALUES data from a derived table in FROM clause.
11231/// Returns (rows, column_names) if the pattern matches: SELECT ... FROM (VALUES ...) alias(col1, col2, ...)
11232type ExtractValuesResult = Option<(Vec<Vec<PlanValue>>, Vec<String>)>;
11233
11234#[allow(clippy::type_complexity)]
11235fn extract_values_from_derived_table(from: &[TableWithJoins]) -> SqlResult<ExtractValuesResult> {
11236    if from.len() != 1 {
11237        return Ok(None);
11238    }
11239
11240    let table_with_joins = &from[0];
11241    if !table_with_joins.joins.is_empty() {
11242        return Ok(None);
11243    }
11244
11245    match &table_with_joins.relation {
11246        TableFactor::Derived {
11247            subquery, alias, ..
11248        } => {
11249            // Check if the subquery is a VALUES expression
11250            let values = match subquery.body.as_ref() {
11251                SetExpr::Values(v) => v,
11252                _ => return Ok(None),
11253            };
11254
11255            // Extract column names from alias
11256            let column_names = if let Some(alias) = alias {
11257                alias
11258                    .columns
11259                    .iter()
11260                    .map(|col_def| col_def.name.value.clone())
11261                    .collect::<Vec<_>>()
11262            } else {
11263                // Generate default column names if no alias provided
11264                if values.rows.is_empty() {
11265                    return Err(Error::InvalidArgumentError(
11266                        "VALUES expression must have at least one row".into(),
11267                    ));
11268                }
11269                let first_row = &values.rows[0];
11270                (0..first_row.len())
11271                    .map(|i| format!("column{}", i))
11272                    .collect()
11273            };
11274
11275            // Extract rows
11276            if values.rows.is_empty() {
11277                return Err(Error::InvalidArgumentError(
11278                    "VALUES expression must have at least one row".into(),
11279                ));
11280            }
11281
11282            let mut rows = Vec::with_capacity(values.rows.len());
11283            for row in &values.rows {
11284                if row.len() != column_names.len() {
11285                    return Err(Error::InvalidArgumentError(format!(
11286                        "VALUES row has {} columns but table alias specifies {} columns",
11287                        row.len(),
11288                        column_names.len()
11289                    )));
11290                }
11291
11292                let mut converted_row = Vec::with_capacity(row.len());
11293                for expr in row {
11294                    let value = SqlValue::try_from_expr(expr)?;
11295                    converted_row.push(PlanValue::from(value));
11296                }
11297                rows.push(converted_row);
11298            }
11299
11300            Ok(Some((rows, column_names)))
11301        }
11302        _ => Ok(None),
11303    }
11304}
11305
11306fn extract_constant_select_rows(select: &Select) -> SqlResult<Option<Vec<Vec<PlanValue>>>> {
11307    if !select.from.is_empty() {
11308        return Ok(None);
11309    }
11310
11311    if select.selection.is_some()
11312        || select.having.is_some()
11313        || !select.named_window.is_empty()
11314        || select.qualify.is_some()
11315        || select.distinct.is_some()
11316        || select.top.is_some()
11317        || select.into.is_some()
11318        || select.prewhere.is_some()
11319        || !select.lateral_views.is_empty()
11320        || select.value_table_mode.is_some()
11321        || !group_by_is_empty(&select.group_by)
11322    {
11323        return Err(Error::InvalidArgumentError(
11324            "constant SELECT statements do not support advanced clauses".into(),
11325        ));
11326    }
11327
11328    if select.projection.is_empty() {
11329        return Err(Error::InvalidArgumentError(
11330            "constant SELECT requires at least one projection".into(),
11331        ));
11332    }
11333
11334    let mut row: Vec<PlanValue> = Vec::with_capacity(select.projection.len());
11335    for item in &select.projection {
11336        let expr = match item {
11337            SelectItem::UnnamedExpr(expr) => expr,
11338            SelectItem::ExprWithAlias { expr, .. } => expr,
11339            other => {
11340                return Err(Error::InvalidArgumentError(format!(
11341                    "unsupported projection in constant SELECT: {other:?}"
11342                )));
11343            }
11344        };
11345
11346        let value = SqlValue::try_from_expr(expr)?;
11347        row.push(PlanValue::from(value));
11348    }
11349
11350    Ok(Some(vec![row]))
11351}
11352
11353/// Check if a query references any information_schema tables.
11354///
11355/// This traverses the query AST to detect references to tables in the `information_schema` namespace,
11356/// allowing the engine to lazily refresh information_schema only when needed.
11357fn query_references_information_schema(query: &Query) -> bool {
11358    fn check_set_expr(expr: &SetExpr) -> bool {
11359        match expr {
11360            SetExpr::Select(select) => {
11361                for table_with_joins in &select.from {
11362                    if check_table_with_joins(table_with_joins) {
11363                        return true;
11364                    }
11365                }
11366                false
11367            }
11368            SetExpr::Query(query) => check_set_expr(&query.body),
11369            SetExpr::SetOperation { left, right, .. } => {
11370                check_set_expr(left) || check_set_expr(right)
11371            }
11372            SetExpr::Values(_)
11373            | SetExpr::Insert(_)
11374            | SetExpr::Update(_)
11375            | SetExpr::Table(_)
11376            | SetExpr::Delete(_)
11377            | SetExpr::Merge(_) => false,
11378        }
11379    }
11380
11381    fn check_table_with_joins(item: &TableWithJoins) -> bool {
11382        if check_table_factor(&item.relation) {
11383            return true;
11384        }
11385        for join in &item.joins {
11386            if check_table_factor(&join.relation) {
11387                return true;
11388            }
11389        }
11390        false
11391    }
11392
11393    fn check_table_factor(factor: &TableFactor) -> bool {
11394        match factor {
11395            TableFactor::Table { name, .. } => {
11396                // Check each part of the table name for "information_schema"
11397                for part in &name.0 {
11398                    if let ObjectNamePart::Identifier(ident) = part {
11399                        let value_lower = ident.value.to_ascii_lowercase();
11400                        tracing::debug!("Checking table name part: '{}'", value_lower);
11401                        // Check if this part is "information_schema" (unqualified)
11402                        if value_lower == "information_schema" {
11403                            tracing::debug!("  -> Found information_schema!");
11404                            return true;
11405                        }
11406                        // Check if this part contains "information_schema." as a prefix
11407                        // (for quoted identifiers like "information_schema.columns")
11408                        if value_lower.starts_with("information_schema.") {
11409                            tracing::debug!("  -> Found information_schema. prefix!");
11410                            return true;
11411                        }
11412                    }
11413                }
11414                false
11415            }
11416            TableFactor::NestedJoin {
11417                table_with_joins, ..
11418            } => check_table_with_joins(table_with_joins),
11419            TableFactor::Derived { subquery, .. } => check_set_expr(&subquery.body),
11420            _ => false,
11421        }
11422    }
11423
11424    check_set_expr(&query.body)
11425}
11426
11427fn extract_single_table(from: &[TableWithJoins]) -> SqlResult<(String, String)> {
11428    if from.len() != 1 {
11429        return Err(Error::InvalidArgumentError(
11430            "queries over multiple tables are not supported yet".into(),
11431        ));
11432    }
11433    let item = &from[0];
11434
11435    if table_with_joins_has_join(item) {
11436        return Err(Error::InvalidArgumentError(
11437            "JOIN clauses are not supported yet".into(),
11438        ));
11439    }
11440    match &item.relation {
11441        TableFactor::Table { name, .. } => canonical_object_name(name),
11442        TableFactor::Derived { alias, .. } => {
11443            // Derived table (subquery) - use the alias as the table name if provided
11444            // For CTAS, this allows: CREATE TABLE t AS SELECT * FROM (VALUES ...) v(id)
11445            let table_name = alias
11446                .as_ref()
11447                .map(|a| a.name.value.clone())
11448                .unwrap_or_else(|| "derived".to_string());
11449            let canonical = table_name.to_ascii_lowercase();
11450            Ok((table_name, canonical))
11451        }
11452        TableFactor::NestedJoin { .. } => Err(Error::InvalidArgumentError(
11453            "JOIN clauses are not supported yet".into(),
11454        )),
11455        _ => Err(Error::InvalidArgumentError(
11456            "queries require a plain table name or derived table".into(),
11457        )),
11458    }
11459}
11460
11461// TODO: Rename for clarity?  i.e. "...nested_joins" or something?
11462fn table_with_joins_has_join(item: &TableWithJoins) -> bool {
11463    if !item.joins.is_empty() {
11464        return true;
11465    }
11466    match &item.relation {
11467        TableFactor::NestedJoin {
11468            table_with_joins, ..
11469        } => table_with_joins_has_join(table_with_joins.as_ref()),
11470        _ => false,
11471    }
11472}
11473
11474/// Extract table references from a FROM clause, flattening supported JOINs and
11475/// collecting any join predicates that must be applied as filters.
11476///
11477type ExtractedJoinData = (
11478    Vec<llkv_plan::TableRef>,
11479    Vec<llkv_plan::JoinMetadata>,
11480    Vec<Option<SqlExpr>>,
11481);
11482
11483/// Returns [`ExtractedJoinData`] (tables, join metadata, join filters).
11484/// - `tables`: list of all table references in order
11485/// - `join_metadata`: [`llkv_plan::JoinMetadata`] entries pairing consecutive tables
11486/// - `join_filters`: ON conditions to be merged into WHERE clause
11487fn extract_tables(from: &[TableWithJoins]) -> SqlResult<ExtractedJoinData> {
11488    let mut tables = Vec::new();
11489    let mut join_metadata = Vec::new();
11490    let mut join_filters = Vec::new();
11491
11492    for item in from {
11493        let prior_table_len = tables.len();
11494        let prior_join_len = join_metadata.len();
11495
11496        flatten_table_with_joins(item, &mut tables, &mut join_metadata, &mut join_filters)?;
11497
11498        let new_table_len = tables.len();
11499        if prior_table_len > 0 && new_table_len > prior_table_len {
11500            join_metadata.insert(
11501                prior_join_len,
11502                llkv_plan::JoinMetadata {
11503                    left_table_index: prior_table_len - 1,
11504                    join_type: llkv_plan::JoinPlan::Inner,
11505                    on_condition: None,
11506                },
11507            );
11508            join_filters.insert(prior_join_len, None);
11509        }
11510    }
11511
11512    Ok((tables, join_metadata, join_filters))
11513}
11514
11515fn push_table_factor(
11516    factor: &TableFactor,
11517    tables: &mut Vec<llkv_plan::TableRef>,
11518    join_metadata: &mut Vec<llkv_plan::JoinMetadata>,
11519    join_filters: &mut Vec<Option<SqlExpr>>,
11520) -> SqlResult<()> {
11521    match factor {
11522        TableFactor::Table { name, alias, .. } => {
11523            // Note: Index hints (INDEXED BY, NOT INDEXED) are SQLite-specific query hints
11524            // that are ignored by the `..` pattern. We accept them for compatibility.
11525            let (schema_opt, table) = parse_schema_qualified_name(name)?;
11526            let schema = schema_opt.unwrap_or_default();
11527            let alias_name = alias.as_ref().map(|a| a.name.value.clone());
11528            tables.push(llkv_plan::TableRef::with_alias(schema, table, alias_name));
11529            Ok(())
11530        }
11531        TableFactor::NestedJoin {
11532            table_with_joins,
11533            alias,
11534        } => {
11535            if alias.is_some() {
11536                return Err(Error::InvalidArgumentError(
11537                    "parenthesized JOINs with aliases are not supported yet".into(),
11538                ));
11539            }
11540            flatten_table_with_joins(
11541                table_with_joins.as_ref(),
11542                tables,
11543                join_metadata,
11544                join_filters,
11545            )
11546        }
11547        TableFactor::Derived { .. } => Err(Error::InvalidArgumentError(
11548            "JOIN clauses require base tables; derived tables are not supported".into(),
11549        )),
11550        _ => Err(Error::InvalidArgumentError(
11551            "queries require a plain table name".into(),
11552        )),
11553    }
11554}
11555
11556fn flatten_table_with_joins(
11557    item: &TableWithJoins,
11558    tables: &mut Vec<llkv_plan::TableRef>,
11559    join_metadata: &mut Vec<llkv_plan::JoinMetadata>,
11560    join_filters: &mut Vec<Option<SqlExpr>>,
11561) -> SqlResult<()> {
11562    push_table_factor(&item.relation, tables, join_metadata, join_filters)?;
11563
11564    for join in &item.joins {
11565        let left_table_index = tables.len() - 1;
11566
11567        match &join.join_operator {
11568            JoinOperator::CrossJoin(JoinConstraint::None)
11569            | JoinOperator::Join(JoinConstraint::None)
11570            | JoinOperator::Inner(JoinConstraint::None) => {
11571                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
11572                join_metadata.push(llkv_plan::JoinMetadata {
11573                    left_table_index,
11574                    join_type: llkv_plan::JoinPlan::Inner,
11575                    on_condition: None,
11576                });
11577                join_filters.push(None);
11578            }
11579            JoinOperator::Join(JoinConstraint::On(condition))
11580            | JoinOperator::Inner(JoinConstraint::On(condition)) => {
11581                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
11582                join_filters.push(Some(condition.clone()));
11583                join_metadata.push(llkv_plan::JoinMetadata {
11584                    left_table_index,
11585                    join_type: llkv_plan::JoinPlan::Inner,
11586                    on_condition: None,
11587                });
11588            }
11589            JoinOperator::Left(JoinConstraint::On(condition))
11590            | JoinOperator::LeftOuter(JoinConstraint::On(condition)) => {
11591                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
11592                join_filters.push(Some(condition.clone()));
11593                join_metadata.push(llkv_plan::JoinMetadata {
11594                    left_table_index,
11595                    join_type: llkv_plan::JoinPlan::Left,
11596                    on_condition: None,
11597                });
11598            }
11599            JoinOperator::Left(JoinConstraint::None)
11600            | JoinOperator::LeftOuter(JoinConstraint::None) => {
11601                push_table_factor(&join.relation, tables, join_metadata, join_filters)?;
11602                join_metadata.push(llkv_plan::JoinMetadata {
11603                    left_table_index,
11604                    join_type: llkv_plan::JoinPlan::Left,
11605                    on_condition: None,
11606                });
11607                join_filters.push(None);
11608            }
11609            JoinOperator::CrossJoin(_) => {
11610                return Err(Error::InvalidArgumentError(
11611                    "CROSS JOIN with constraints is not supported".into(),
11612                ));
11613            }
11614            JoinOperator::Join(JoinConstraint::Using(_))
11615            | JoinOperator::Inner(JoinConstraint::Using(_))
11616            | JoinOperator::Left(JoinConstraint::Using(_))
11617            | JoinOperator::LeftOuter(JoinConstraint::Using(_)) => {
11618                return Err(Error::InvalidArgumentError(
11619                    "JOIN ... USING (...) is not supported yet".into(),
11620                ));
11621            }
11622            JoinOperator::Join(JoinConstraint::Natural)
11623            | JoinOperator::Inner(JoinConstraint::Natural)
11624            | JoinOperator::Left(JoinConstraint::Natural)
11625            | JoinOperator::LeftOuter(JoinConstraint::Natural)
11626            | JoinOperator::Right(_)
11627            | JoinOperator::RightOuter(_)
11628            | JoinOperator::FullOuter(_)
11629            | JoinOperator::Semi(_)
11630            | JoinOperator::LeftSemi(_)
11631            | JoinOperator::LeftAnti(_)
11632            | JoinOperator::RightSemi(_)
11633            | JoinOperator::RightAnti(_)
11634            | JoinOperator::CrossApply
11635            | JoinOperator::OuterApply
11636            | JoinOperator::Anti(_)
11637            | JoinOperator::StraightJoin(_) => {
11638                return Err(Error::InvalidArgumentError(
11639                    "only INNER JOIN and LEFT JOIN with optional ON constraints are supported"
11640                        .into(),
11641                ));
11642            }
11643            other => {
11644                return Err(Error::InvalidArgumentError(format!(
11645                    "unsupported JOIN clause: {other:?}"
11646                )));
11647            }
11648        }
11649    }
11650
11651    Ok(())
11652}
11653
11654fn group_by_is_empty(expr: &GroupByExpr) -> bool {
11655    matches!(
11656        expr,
11657        GroupByExpr::Expressions(exprs, modifiers)
11658            if exprs.is_empty() && modifiers.is_empty()
11659    )
11660}
11661
11662fn convert_value_table_mode(mode: sqlparser::ast::ValueTableMode) -> llkv_plan::ValueTableMode {
11663    use llkv_plan::ValueTableMode as PlanMode;
11664    match mode {
11665        sqlparser::ast::ValueTableMode::AsStruct => PlanMode::AsStruct,
11666        sqlparser::ast::ValueTableMode::AsValue => PlanMode::AsValue,
11667        sqlparser::ast::ValueTableMode::DistinctAsStruct => PlanMode::DistinctAsStruct,
11668        sqlparser::ast::ValueTableMode::DistinctAsValue => PlanMode::DistinctAsValue,
11669    }
11670}
11671#[cfg(test)]
11672mod tests {
11673    use super::*;
11674    use arrow::array::{Array, Float64Array, Int32Array, Int64Array, StringArray};
11675    use arrow::record_batch::RecordBatch;
11676    use llkv_storage::pager::MemPager;
11677
11678    fn extract_string_options(batches: &[RecordBatch]) -> Vec<Option<String>> {
11679        let mut values: Vec<Option<String>> = Vec::new();
11680        for batch in batches {
11681            let column = batch
11682                .column(0)
11683                .as_any()
11684                .downcast_ref::<StringArray>()
11685                .expect("string column");
11686            for idx in 0..column.len() {
11687                if column.is_null(idx) {
11688                    values.push(None);
11689                } else {
11690                    values.push(Some(column.value(idx).to_string()));
11691                }
11692            }
11693        }
11694        values
11695    }
11696
11697    #[test]
11698    fn set_constraint_mode_updates_session() {
11699        let pager = Arc::new(MemPager::default());
11700        let engine = SqlEngine::new(pager);
11701
11702        assert_eq!(
11703            engine.session().constraint_enforcement_mode(),
11704            ConstraintEnforcementMode::Immediate
11705        );
11706
11707        engine
11708            .execute("SET constraint_enforcement_mode = deferred")
11709            .expect("set deferred mode");
11710
11711        assert_eq!(
11712            engine.session().constraint_enforcement_mode(),
11713            ConstraintEnforcementMode::Deferred
11714        );
11715
11716        engine
11717            .execute("SET constraint_enforcement_mode = IMMEDIATE")
11718            .expect("set immediate mode");
11719
11720        assert_eq!(
11721            engine.session().constraint_enforcement_mode(),
11722            ConstraintEnforcementMode::Immediate
11723        );
11724    }
11725
11726    #[test]
11727    fn set_constraint_mode_is_session_scoped() {
11728        let pager = Arc::new(MemPager::default());
11729        let engine = SqlEngine::new(Arc::clone(&pager));
11730        let shared_context = engine.runtime_context();
11731        let peer = SqlEngine::with_context(
11732            Arc::clone(&shared_context),
11733            engine.default_nulls_first_for_tests(),
11734        );
11735
11736        engine
11737            .execute("SET constraint_enforcement_mode = deferred")
11738            .expect("set deferred mode");
11739
11740        assert_eq!(
11741            engine.session().constraint_enforcement_mode(),
11742            ConstraintEnforcementMode::Deferred
11743        );
11744        assert_eq!(
11745            peer.session().constraint_enforcement_mode(),
11746            ConstraintEnforcementMode::Immediate
11747        );
11748    }
11749
11750    #[test]
11751    fn test_interval_expr_structure() {
11752        use sqlparser::ast::{BinaryOperator, Expr as SqlExprAst, Query, SetExpr, Statement};
11753        use sqlparser::dialect::GenericDialect;
11754
11755        let dialect = GenericDialect {};
11756        let sql = "SELECT CAST('1998-12-01' AS DATE) - INTERVAL '90' DAY";
11757        let statements = Parser::parse_sql(&dialect, sql).unwrap();
11758
11759        assert_eq!(statements.len(), 1, "expected single statement");
11760
11761        let Statement::Query(query) = &statements[0] else {
11762            panic!("expected Query statement");
11763        };
11764
11765        let Query { body, .. } = query.as_ref();
11766        let SetExpr::Select(select) = body.as_ref() else {
11767            panic!("expected Select body");
11768        };
11769
11770        assert_eq!(select.projection.len(), 1, "expected single projection");
11771
11772        // Verify the projection is a BinaryOp with Minus operator and Interval on the right
11773        match &select.projection[0] {
11774            sqlparser::ast::SelectItem::UnnamedExpr(SqlExprAst::BinaryOp { left, op, right }) => {
11775                // Left side should be a CAST expression
11776                assert!(
11777                    matches!(left.as_ref(), SqlExprAst::Cast { .. }),
11778                    "expected CAST on left"
11779                );
11780
11781                // Operator should be Minus
11782                assert_eq!(*op, BinaryOperator::Minus, "expected Minus operator");
11783
11784                // Right side should be an Interval
11785                assert!(
11786                    matches!(right.as_ref(), SqlExprAst::Interval(_)),
11787                    "expected Interval on right"
11788                );
11789
11790                if let SqlExprAst::Interval(interval) = right.as_ref() {
11791                    assert_eq!(
11792                        interval.leading_field,
11793                        Some(sqlparser::ast::DateTimeField::Day)
11794                    );
11795                }
11796            }
11797            other => panic!("unexpected projection structure: {other:?}"),
11798        }
11799    }
11800
11801    #[test]
11802    fn test_insert_batching_across_calls() {
11803        let engine = SqlEngine::new(Arc::new(MemPager::default()));
11804
11805        // Create table
11806        engine.execute("CREATE TABLE test (id INTEGER)").unwrap();
11807
11808        // Insert two rows in SEPARATE execute() calls (simulating SLT)
11809        engine.execute("INSERT INTO test VALUES (1)").unwrap();
11810        engine.execute("INSERT INTO test VALUES (2)").unwrap();
11811
11812        // SELECT will flush the buffer - result will have [INSERT, SELECT]
11813        let result = engine.execute("SELECT * FROM test ORDER BY id").unwrap();
11814        let select_result = result
11815            .into_iter()
11816            .find_map(|res| match res {
11817                RuntimeStatementResult::Select { execution, .. } => {
11818                    Some(execution.collect().unwrap())
11819                }
11820                _ => None,
11821            })
11822            .expect("expected SELECT result in response");
11823        let batches = select_result;
11824        assert_eq!(
11825            batches[0].num_rows(),
11826            2,
11827            "Should have 2 rows after cross-call batching"
11828        );
11829    }
11830
11831    #[test]
11832    fn create_insert_select_roundtrip() {
11833        let pager = Arc::new(MemPager::default());
11834        let engine = SqlEngine::new(pager);
11835
11836        let result = engine
11837            .execute("CREATE TABLE people (id INT NOT NULL, name TEXT NOT NULL)")
11838            .expect("create table");
11839        assert!(matches!(
11840            result[0],
11841            RuntimeStatementResult::CreateTable { .. }
11842        ));
11843
11844        let result = engine
11845            .execute("INSERT INTO people (id, name) VALUES (1, 'alice'), (2, 'bob')")
11846            .expect("insert rows");
11847        assert!(matches!(
11848            result[0],
11849            RuntimeStatementResult::Insert {
11850                rows_inserted: 2,
11851                ..
11852            }
11853        ));
11854
11855        let mut result = engine
11856            .execute("SELECT name FROM people WHERE id = 2")
11857            .expect("select rows");
11858        let select_result = result.remove(0);
11859        let batches = match select_result {
11860            RuntimeStatementResult::Select { execution, .. } => {
11861                execution.collect().expect("collect batches")
11862            }
11863            _ => panic!("expected select result"),
11864        };
11865        assert_eq!(batches.len(), 1);
11866        let column = batches[0]
11867            .column(0)
11868            .as_any()
11869            .downcast_ref::<StringArray>()
11870            .expect("string column");
11871        assert_eq!(column.len(), 1);
11872        assert_eq!(column.value(0), "bob");
11873    }
11874
11875    #[test]
11876    fn insert_select_constant_including_null() {
11877        let pager = Arc::new(MemPager::default());
11878        let engine = SqlEngine::new(pager);
11879
11880        engine
11881            .execute("CREATE TABLE integers(i INTEGER)")
11882            .expect("create table");
11883
11884        let result = engine
11885            .execute("INSERT INTO integers SELECT 42")
11886            .expect("insert literal");
11887        assert!(matches!(
11888            result[0],
11889            RuntimeStatementResult::Insert {
11890                rows_inserted: 1,
11891                ..
11892            }
11893        ));
11894
11895        let result = engine
11896            .execute("INSERT INTO integers SELECT CAST(NULL AS VARCHAR)")
11897            .expect("insert null literal");
11898        assert!(matches!(
11899            result[0],
11900            RuntimeStatementResult::Insert {
11901                rows_inserted: 1,
11902                ..
11903            }
11904        ));
11905
11906        let mut result = engine
11907            .execute("SELECT * FROM integers")
11908            .expect("select rows");
11909        let select_result = result.remove(0);
11910        let batches = match select_result {
11911            RuntimeStatementResult::Select { execution, .. } => {
11912                execution.collect().expect("collect batches")
11913            }
11914            _ => panic!("expected select result"),
11915        };
11916
11917        let mut values: Vec<Option<i64>> = Vec::new();
11918        for batch in &batches {
11919            let column = batch
11920                .column(0)
11921                .as_any()
11922                .downcast_ref::<Int64Array>()
11923                .expect("int column");
11924            for idx in 0..column.len() {
11925                if column.is_null(idx) {
11926                    values.push(None);
11927                } else {
11928                    values.push(Some(column.value(idx)));
11929                }
11930            }
11931        }
11932
11933        assert_eq!(values, vec![Some(42), None]);
11934    }
11935
11936    #[test]
11937    fn not_null_comparison_filters_all_rows() {
11938        let pager = Arc::new(MemPager::default());
11939        let engine = SqlEngine::new(pager);
11940
11941        engine
11942            .execute("CREATE TABLE single(col INTEGER)")
11943            .expect("create table");
11944        engine
11945            .execute("INSERT INTO single VALUES (1)")
11946            .expect("insert row");
11947
11948        let batches = engine
11949            .sql("SELECT * FROM single WHERE NOT ( NULL ) >= NULL")
11950            .expect("run constant null comparison");
11951
11952        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
11953        assert_eq!(total_rows, 0, "expected filter to remove all rows");
11954    }
11955
11956    #[test]
11957    fn not_null_in_list_filters_all_rows() {
11958        let pager = Arc::new(MemPager::default());
11959        let engine = SqlEngine::new(pager);
11960
11961        engine
11962            .execute("CREATE TABLE tab0(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
11963            .expect("create table");
11964        engine
11965            .execute("INSERT INTO tab0 VALUES (1, 2, 3)")
11966            .expect("insert row");
11967
11968        let batches = engine
11969            .sql("SELECT * FROM tab0 WHERE NOT ( NULL ) IN ( - col2 * + col2 )")
11970            .expect("run IN list null comparison");
11971
11972        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
11973        assert_eq!(total_rows, 0, "expected IN list filter to remove all rows");
11974    }
11975
11976    #[test]
11977    fn empty_in_list_filters_all_rows() {
11978        let pager = Arc::new(MemPager::default());
11979        let engine = SqlEngine::new(pager);
11980
11981        engine
11982            .execute("CREATE TABLE test_table(col INTEGER)")
11983            .expect("create table");
11984        engine
11985            .execute("INSERT INTO test_table VALUES (1), (2), (3)")
11986            .expect("insert rows");
11987
11988        let batches = engine
11989            .sql("SELECT * FROM test_table WHERE col IN ()")
11990            .expect("run empty IN list");
11991
11992        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
11993        assert_eq!(total_rows, 0, "expected empty IN list to filter all rows");
11994    }
11995
11996    #[test]
11997    fn empty_not_in_list_preserves_all_rows() {
11998        let pager = Arc::new(MemPager::default());
11999        let engine = SqlEngine::new(pager);
12000
12001        engine
12002            .execute("CREATE TABLE test_table(col INTEGER)")
12003            .expect("create table");
12004        engine
12005            .execute("INSERT INTO test_table VALUES (1), (2), (3)")
12006            .expect("insert rows");
12007
12008        let batches = engine
12009            .sql("SELECT * FROM test_table WHERE col NOT IN () ORDER BY col")
12010            .expect("run empty NOT IN list");
12011
12012        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
12013        assert_eq!(
12014            total_rows, 3,
12015            "expected empty NOT IN list to preserve all rows"
12016        );
12017
12018        let mut values: Vec<i64> = Vec::new();
12019        for batch in &batches {
12020            let column = batch
12021                .column(0)
12022                .as_any()
12023                .downcast_ref::<Int64Array>()
12024                .expect("int column");
12025            for idx in 0..column.len() {
12026                if !column.is_null(idx) {
12027                    values.push(column.value(idx));
12028                }
12029            }
12030        }
12031
12032        assert_eq!(values, vec![1, 2, 3]);
12033    }
12034
12035    #[test]
12036    fn empty_in_list_with_constant_expression() {
12037        let pager = Arc::new(MemPager::default());
12038        let engine = SqlEngine::new(pager);
12039
12040        let batches = engine
12041            .sql("SELECT 1 IN ()")
12042            .expect("run constant empty IN list");
12043
12044        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
12045        assert_eq!(total_rows, 1, "expected one result row");
12046
12047        let value = batches[0]
12048            .column(0)
12049            .as_any()
12050            .downcast_ref::<Int64Array>()
12051            .expect("int column")
12052            .value(0);
12053
12054        assert_eq!(value, 0, "expected 1 IN () to evaluate to 0 (false)");
12055    }
12056
12057    #[test]
12058    fn empty_not_in_list_with_constant_expression() {
12059        let pager = Arc::new(MemPager::default());
12060        let engine = SqlEngine::new(pager);
12061
12062        let batches = engine
12063            .sql("SELECT 1 NOT IN ()")
12064            .expect("run constant empty NOT IN list");
12065
12066        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
12067        assert_eq!(total_rows, 1, "expected one result row");
12068
12069        let value = batches[0]
12070            .column(0)
12071            .as_any()
12072            .downcast_ref::<Int64Array>()
12073            .expect("int column")
12074            .value(0);
12075
12076        assert_eq!(value, 1, "expected 1 NOT IN () to evaluate to 1 (true)");
12077    }
12078
12079    #[test]
12080    fn not_in_with_cast_preserves_rows_for_self_comparison() {
12081        let pager = Arc::new(MemPager::default());
12082        let engine = SqlEngine::new(pager);
12083
12084        engine
12085            .execute("CREATE TABLE tab2(col1 INTEGER, col2 INTEGER)")
12086            .expect("create tab2");
12087        engine
12088            .execute("INSERT INTO tab2 VALUES (51, 51), (67, 67), (77, 77)")
12089            .expect("seed tab2");
12090
12091        let batches = engine
12092            .sql(
12093                "SELECT col1 FROM tab2 WHERE NOT col2 NOT IN ( + CAST ( + + col2 AS REAL ) ) ORDER BY col1",
12094            )
12095            .expect("run NOT IN self comparison query");
12096
12097        let mut values: Vec<i64> = Vec::new();
12098        for batch in &batches {
12099            let column = batch
12100                .column(0)
12101                .as_any()
12102                .downcast_ref::<Int64Array>()
12103                .expect("int column");
12104            for idx in 0..column.len() {
12105                if !column.is_null(idx) {
12106                    values.push(column.value(idx));
12107                }
12108            }
12109        }
12110
12111        assert_eq!(values, vec![51, 67, 77]);
12112    }
12113
12114    #[test]
12115    fn cross_join_not_null_comparison_filters_all_rows() {
12116        let pager = Arc::new(MemPager::default());
12117        let engine = SqlEngine::new(pager);
12118
12119        engine
12120            .execute("CREATE TABLE left_side(col INTEGER)")
12121            .expect("create left table");
12122        engine
12123            .execute("CREATE TABLE right_side(col INTEGER)")
12124            .expect("create right table");
12125        engine
12126            .execute("INSERT INTO left_side VALUES (1)")
12127            .expect("insert left row");
12128        engine
12129            .execute("INSERT INTO right_side VALUES (2)")
12130            .expect("insert right row");
12131
12132        let batches = engine
12133            .sql("SELECT * FROM left_side CROSS JOIN right_side WHERE NOT ( NULL ) >= NULL")
12134            .expect("run cross join null comparison");
12135
12136        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
12137        assert_eq!(
12138            total_rows, 0,
12139            "expected cross join filter to remove all rows"
12140        );
12141    }
12142
12143    #[test]
12144    fn extract_tables_inserts_cross_join_metadata_for_comma_lists() {
12145        use sqlparser::ast::{SetExpr, Statement};
12146        use sqlparser::dialect::GenericDialect;
12147        use sqlparser::parser::Parser;
12148
12149        let dialect = GenericDialect {};
12150        let sql = "SELECT * FROM tab0, tab1 AS cor0 CROSS JOIN tab2";
12151        let statements = Parser::parse_sql(&dialect, sql).expect("parse sql");
12152        let Statement::Query(query) = &statements[0] else {
12153            panic!("expected SELECT query");
12154        };
12155        let select = match query.body.as_ref() {
12156            SetExpr::Select(select) => select.as_ref(),
12157            other => panic!("unexpected query body: {other:?}"),
12158        };
12159
12160        let (tables, join_metadata, join_filters) =
12161            extract_tables(&select.from).expect("extract tables");
12162
12163        assert_eq!(tables.len(), 3, "expected three table refs");
12164        assert_eq!(join_metadata.len(), 2, "expected two join edges");
12165        assert_eq!(join_filters.len(), 2, "join filters mirror metadata len");
12166
12167        assert_eq!(join_metadata[0].left_table_index, 0, "implicit comma join");
12168        assert_eq!(join_metadata[1].left_table_index, 1, "explicit cross join");
12169    }
12170
12171    #[test]
12172    fn implicit_cross_join_populates_literal_true_on_condition() {
12173        use sqlparser::ast::Statement;
12174        use sqlparser::dialect::SQLiteDialect;
12175        use sqlparser::parser::Parser;
12176
12177        let pager = Arc::new(MemPager::default());
12178        let engine = SqlEngine::new(pager);
12179
12180        engine
12181            .execute(
12182                "CREATE TABLE tab0(col0 INTEGER);
12183                 CREATE TABLE tab1(col0 INTEGER);",
12184            )
12185            .expect("create tables");
12186
12187        let dialect = SQLiteDialect {};
12188        let mut statements =
12189            Parser::parse_sql(&dialect, "SELECT * FROM tab0, tab1").expect("parse sql");
12190        let Statement::Query(query) = statements.pop().expect("statement") else {
12191            unreachable!();
12192        };
12193        let plan = engine.build_select_plan(*query).expect("build select plan");
12194
12195        assert_eq!(plan.tables.len(), 2, "expected two tables");
12196        assert_eq!(plan.joins.len(), 1, "expected implicit join edge");
12197
12198        let join_meta = &plan.joins[0];
12199        match join_meta.on_condition.as_ref() {
12200            Some(llkv_expr::expr::Expr::Literal(value)) => {
12201                assert!(*value, "implicit join should be ON TRUE");
12202            }
12203            other => panic!("unexpected join predicate: {other:?}"),
12204        }
12205    }
12206
12207    #[test]
12208    fn not_between_null_bounds_matches_sqlite_behavior() {
12209        let pager = Arc::new(MemPager::default());
12210        let engine = SqlEngine::new(pager);
12211
12212        engine
12213            .execute("CREATE TABLE tab2(col1 INTEGER, col2 INTEGER)")
12214            .expect("create tab2");
12215        engine
12216            .execute("INSERT INTO tab2 VALUES (1, 2), (-5, 7), (NULL, 11)")
12217            .expect("seed rows");
12218
12219        let batches = engine
12220            .sql(
12221                "SELECT DISTINCT - col2 AS col1 FROM tab2 WHERE NOT ( col1 ) BETWEEN ( NULL ) AND ( + col1 - col2 )",
12222            )
12223            .expect("run NOT BETWEEN query with NULL bounds");
12224
12225        let mut values: Vec<i64> = Vec::new();
12226        for batch in &batches {
12227            let column = batch.column(0);
12228            match column.data_type() {
12229                arrow::datatypes::DataType::Int64 => {
12230                    let array = column
12231                        .as_any()
12232                        .downcast_ref::<Int64Array>()
12233                        .expect("int64 column");
12234                    for idx in 0..array.len() {
12235                        if !array.is_null(idx) {
12236                            values.push(array.value(idx));
12237                        }
12238                    }
12239                }
12240                arrow::datatypes::DataType::Int32 => {
12241                    let array = column
12242                        .as_any()
12243                        .downcast_ref::<Int32Array>()
12244                        .expect("int32 column");
12245                    for idx in 0..array.len() {
12246                        if !array.is_null(idx) {
12247                            values.push(array.value(idx) as i64);
12248                        }
12249                    }
12250                }
12251                other => panic!("unexpected data type: {other:?}"),
12252            }
12253        }
12254
12255        values.sort_unstable();
12256        assert_eq!(values, vec![-7, -2]);
12257    }
12258
12259    #[test]
12260    fn not_between_leading_not_matches_sqlite_behavior() {
12261        let pager = Arc::new(MemPager::default());
12262        let engine = SqlEngine::new(pager);
12263
12264        engine
12265            .execute("CREATE TABLE tab0(col1 INTEGER)")
12266            .expect("create tab0");
12267        engine
12268            .execute("INSERT INTO tab0 VALUES (10), (20), (30)")
12269            .expect("seed tab0");
12270
12271        use sqlparser::ast::Statement;
12272        use sqlparser::dialect::SQLiteDialect;
12273        use sqlparser::parser::Parser;
12274
12275        let dialect = SQLiteDialect {};
12276        let mut statements = Parser::parse_sql(
12277            &dialect,
12278            "SELECT COUNT ( * ) FROM tab0 WHERE NOT ( NOT ( 41 + + 79 ) NOT BETWEEN NULL AND col1 )",
12279        )
12280        .expect("parse NOT BETWEEN query");
12281        let Statement::Query(query_ast) = statements.pop().expect("statement") else {
12282            panic!("expected SELECT query");
12283        };
12284        let plan = engine
12285            .build_select_plan(*query_ast)
12286            .expect("build select plan");
12287        let filter_plan = plan.filter.expect("expected filter predicate");
12288        match &filter_plan.predicate {
12289            llkv_expr::expr::Expr::Or(children) => {
12290                assert_eq!(
12291                    children.len(),
12292                    2,
12293                    "NOT BETWEEN should expand to two comparisons"
12294                );
12295            }
12296            other => panic!("unexpected filter shape: {other:?}"),
12297        }
12298
12299        let batches = engine
12300            .sql(
12301                "SELECT COUNT ( * ) FROM tab0 WHERE NOT ( NOT ( 41 + + 79 ) NOT BETWEEN NULL AND col1 )",
12302            )
12303            .expect("run NOT BETWEEN query with nested NOT tokens");
12304
12305        assert_eq!(batches.len(), 1, "expected single result batch");
12306        let batch = &batches[0];
12307        assert_eq!(batch.num_columns(), 1, "expected single column result");
12308        let array = batch
12309            .column(0)
12310            .as_any()
12311            .downcast_ref::<Int64Array>()
12312            .expect("count column as int64");
12313        assert_eq!(array.len(), 1, "expected scalar aggregate result");
12314        assert_eq!(
12315            array.value(0),
12316            3,
12317            "SQLite and engine should agree on row count"
12318        );
12319    }
12320
12321    #[test]
12322    fn not_chain_precedence_matches_sqlite_behavior() {
12323        let pager = Arc::new(MemPager::default());
12324        let engine = SqlEngine::new(pager);
12325
12326        engine
12327            .execute("CREATE TABLE tab1(col0 INTEGER)")
12328            .expect("create tab1");
12329        engine
12330            .execute("INSERT INTO tab1 VALUES (1), (2)")
12331            .expect("seed tab1");
12332
12333        use sqlparser::ast::Statement;
12334        use sqlparser::dialect::SQLiteDialect;
12335        use sqlparser::parser::Parser;
12336
12337        let dialect = SQLiteDialect {};
12338        let mut statements = Parser::parse_sql(
12339            &dialect,
12340            "SELECT DISTINCT 85 AS value FROM tab1 WHERE NOT + 84 < - + 69 GROUP BY col0, col0",
12341        )
12342        .expect("parse sql");
12343        let statement = statements.pop().expect("expected single statement");
12344        let Statement::Query(query_ast) = statement else {
12345            panic!("expected SELECT query");
12346        };
12347        let plan = engine
12348            .build_select_plan(*query_ast)
12349            .expect("build select plan");
12350        let filter_expr = plan.filter.expect("expected filter predicate").predicate;
12351        if let llkv_expr::expr::Expr::Not(inner) = &filter_expr {
12352            if !matches!(inner.as_ref(), llkv_expr::expr::Expr::Compare { .. }) {
12353                panic!("expected NOT to wrap comparison, got: {inner:?}");
12354            }
12355        } else {
12356            panic!("expected filter to be NOT-wrapped comparison: {filter_expr:?}");
12357        }
12358
12359        let batches = engine
12360            .sql(
12361                "SELECT DISTINCT 85 AS value FROM tab1 WHERE NOT + 84 < - + 69 GROUP BY col0, col0",
12362            )
12363            .expect("run NOT precedence query");
12364
12365        let mut values: Vec<i64> = Vec::new();
12366        for batch in &batches {
12367            let column = batch.column(0);
12368            match column.data_type() {
12369                arrow::datatypes::DataType::Int64 => {
12370                    let array = column
12371                        .as_any()
12372                        .downcast_ref::<Int64Array>()
12373                        .expect("int64 column");
12374                    for idx in 0..array.len() {
12375                        if !array.is_null(idx) {
12376                            values.push(array.value(idx));
12377                        }
12378                    }
12379                }
12380                arrow::datatypes::DataType::Int32 => {
12381                    let array = column
12382                        .as_any()
12383                        .downcast_ref::<Int32Array>()
12384                        .expect("int32 column");
12385                    for idx in 0..array.len() {
12386                        if !array.is_null(idx) {
12387                            values.push(array.value(idx) as i64);
12388                        }
12389                    }
12390                }
12391                arrow::datatypes::DataType::Float64 => {
12392                    let array = column
12393                        .as_any()
12394                        .downcast_ref::<Float64Array>()
12395                        .expect("float64 column");
12396                    for idx in 0..array.len() {
12397                        if !array.is_null(idx) {
12398                            values.push(array.value(idx) as i64);
12399                        }
12400                    }
12401                }
12402                other => panic!("unexpected data type: {other:?}"),
12403            }
12404        }
12405
12406        values.sort_unstable();
12407        assert_eq!(values, vec![85]);
12408    }
12409
12410    #[test]
12411    fn not_between_null_bounds_matches_harness_fixture() {
12412        let pager = Arc::new(MemPager::default());
12413        let engine = SqlEngine::new(pager);
12414
12415        engine
12416            .execute("CREATE TABLE tab2(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
12417            .expect("create tab2");
12418        engine
12419            .execute("INSERT INTO tab2 VALUES (7, 31, 27), (79, 17, 38), (78, 59, 26)")
12420            .expect("seed rows");
12421
12422        let batches = engine
12423            .sql(
12424                "SELECT DISTINCT - col2 AS col1 FROM tab2 WHERE NOT ( col1 ) BETWEEN ( NULL ) AND ( + col1 - col2 )",
12425            )
12426            .expect("run harness-matched NOT BETWEEN query");
12427
12428        let mut values: Vec<i64> = Vec::new();
12429        for batch in &batches {
12430            let column = batch
12431                .column(0)
12432                .as_any()
12433                .downcast_ref::<Int64Array>()
12434                .expect("integer column");
12435            for idx in 0..column.len() {
12436                if !column.is_null(idx) {
12437                    values.push(column.value(idx));
12438                }
12439            }
12440        }
12441
12442        values.sort_unstable();
12443        assert_eq!(values, vec![-38, -27, -26]);
12444    }
12445
12446    #[test]
12447    fn not_between_null_bounds_parser_negated_flag() {
12448        use sqlparser::ast::{Expr as SqlExprAst, Statement};
12449        use sqlparser::dialect::SQLiteDialect;
12450        use sqlparser::parser::Parser;
12451
12452        let dialect = SQLiteDialect {};
12453        let sql = "SELECT DISTINCT - col2 AS col1 FROM tab2 WHERE NOT ( col1 ) BETWEEN ( NULL ) AND ( + col1 - col2 )";
12454
12455        let mut statements = Parser::parse_sql(&dialect, sql).expect("parse sql");
12456        let statement = statements.pop().expect("expected single statement");
12457        let Statement::Query(query) = statement else {
12458            panic!("expected SELECT query");
12459        };
12460        let select = query.body.as_select().expect("expected SELECT body");
12461        let where_expr = select.selection.as_ref().expect("expected WHERE clause");
12462
12463        match where_expr {
12464            SqlExprAst::UnaryOp {
12465                op: sqlparser::ast::UnaryOperator::Not,
12466                expr,
12467            } => match expr.as_ref() {
12468                SqlExprAst::Between { negated, .. } => {
12469                    assert!(
12470                        !negated,
12471                        "expected BETWEEN parser to treat leading NOT as part of expression"
12472                    );
12473                }
12474                other => panic!("unexpected inner expression: {other:?}"),
12475            },
12476            other => panic!("unexpected where expression: {other:?}"),
12477        }
12478    }
12479
12480    #[test]
12481    fn double_negated_between_null_bounds_filters_all_rows() {
12482        let pager = Arc::new(MemPager::default());
12483        let engine = SqlEngine::new(pager);
12484
12485        engine
12486            .execute("CREATE TABLE tab2(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
12487            .expect("create tab2");
12488        engine
12489            .execute("INSERT INTO tab2 VALUES (1, 2, 3), (-2, -13, 19), (NULL, 5, 7)")
12490            .expect("seed rows");
12491
12492        let batches = engine
12493            .sql(
12494                "SELECT - col1 * + col2 FROM tab2 WHERE NOT ( col1 ) NOT BETWEEN ( NULL ) AND ( col0 )",
12495            )
12496            .expect("run double NOT BETWEEN query with NULL bounds");
12497
12498        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
12499        assert_eq!(
12500            total_rows, 0,
12501            "expected double NOT BETWEEN to filter all rows"
12502        );
12503    }
12504
12505    #[test]
12506    fn not_with_empty_in_list_complements_all_rows() {
12507        let pager = Arc::new(MemPager::default());
12508        let engine = SqlEngine::new(pager);
12509
12510        engine
12511            .execute("CREATE TABLE tab(pk INTEGER PRIMARY KEY, col0 INTEGER)")
12512            .expect("create tab");
12513        engine
12514            .execute("INSERT INTO tab VALUES (0, 1), (1, 2), (2, 3)")
12515            .expect("seed rows");
12516
12517        let batches = engine
12518            .sql(
12519                "SELECT COUNT(*) AS cnt FROM tab WHERE NOT (col0 = 999 AND col0 IN (SELECT col0 FROM tab WHERE 0 = 1))",
12520            )
12521            .expect("run NOT query with empty IN list");
12522
12523        assert_eq!(batches.len(), 1, "expected single aggregate batch");
12524        let counts = batches[0]
12525            .column(0)
12526            .as_any()
12527            .downcast_ref::<Int64Array>()
12528            .expect("count column as int64");
12529        assert_eq!(counts.value(0), 3, "NOT should match every row");
12530
12531        let batches = engine
12532            .sql(
12533                "SELECT pk FROM tab WHERE NOT (col0 = 999 AND col0 IN (SELECT col0 FROM tab WHERE 0 = 1)) ORDER BY pk",
12534            )
12535            .expect("fetch rows from NOT query");
12536        let expected: Vec<i64> = vec![0, 1, 2];
12537        let mut actual = Vec::new();
12538        for batch in &batches {
12539            let column = batch
12540                .column(0)
12541                .as_any()
12542                .downcast_ref::<Int64Array>()
12543                .expect("pk column as int64");
12544            for idx in 0..column.len() {
12545                if !column.is_null(idx) {
12546                    actual.push(column.value(idx));
12547                }
12548            }
12549        }
12550        assert_eq!(actual, expected, "NOT should preserve all primary keys");
12551    }
12552
12553    #[test]
12554    fn not_scalar_less_than_null_filters_all_rows() {
12555        let pager = Arc::new(MemPager::default());
12556        let engine = SqlEngine::new(pager);
12557
12558        engine
12559            .execute("CREATE TABLE tab(col0 INTEGER, col2 INTEGER)")
12560            .expect("create tab");
12561        engine
12562            .execute("INSERT INTO tab VALUES (1, 2), (5, 10), (-3, 7)")
12563            .expect("seed rows");
12564
12565        let batches = engine
12566            .sql("SELECT col0 FROM tab WHERE NOT ( - col0 / - col2 + - col0 ) < NULL")
12567            .expect("run NOT < NULL query");
12568
12569        let total_rows: usize = batches.iter().map(|batch| batch.num_rows()).sum();
12570        assert_eq!(total_rows, 0, "expected NOT < NULL to filter all rows");
12571    }
12572
12573    #[test]
12574    fn left_join_not_is_not_null_on_literal_flips_to_is_null() {
12575        use sqlparser::ast::Statement;
12576        use sqlparser::dialect::SQLiteDialect;
12577        use sqlparser::parser::Parser;
12578
12579        let pager = Arc::new(MemPager::default());
12580        let engine = SqlEngine::new(pager);
12581
12582        engine
12583            .execute(
12584                "CREATE TABLE tab0(col0 INTEGER, col1 INTEGER, col2 INTEGER);\
12585                 CREATE TABLE tab1(col0 INTEGER, col1 INTEGER, col2 INTEGER);",
12586            )
12587            .expect("create tables");
12588
12589        let sql = "SELECT DISTINCT * FROM tab1 AS cor0 LEFT JOIN tab1 AS cor1 ON NOT 86 IS NOT NULL, tab0 AS cor2";
12590        let dialect = SQLiteDialect {};
12591        let mut statements = Parser::parse_sql(&dialect, sql).expect("parse sql");
12592        let statement = statements.pop().expect("expected statement");
12593        let Statement::Query(query) = statement else {
12594            panic!("expected SELECT query");
12595        };
12596
12597        let plan = engine.build_select_plan(*query).expect("build select plan");
12598
12599        let left_join = plan
12600            .joins
12601            .iter()
12602            .find(|join| join.join_type == llkv_plan::JoinPlan::Left)
12603            .expect("expected explicit LEFT JOIN entry");
12604        let on_condition = left_join
12605            .on_condition
12606            .as_ref()
12607            .expect("left join should preserve ON predicate");
12608
12609        match on_condition {
12610            llkv_expr::expr::Expr::IsNull { expr, negated } => {
12611                assert!(!negated, "expected NOT to flip into IS NULL");
12612                assert!(matches!(
12613                    expr,
12614                    llkv_expr::expr::ScalarExpr::Literal(llkv_expr::literal::Literal::Int128(86))
12615                ));
12616            }
12617            other => panic!("unexpected ON predicate: {other:?}"),
12618        }
12619    }
12620
12621    #[test]
12622    fn left_join_constant_false_preserves_left_rows_with_null_right() {
12623        let pager = Arc::new(MemPager::default());
12624        let engine = SqlEngine::new(pager);
12625
12626        engine
12627            .execute(
12628                "CREATE TABLE tab0(col0 INTEGER, col1 INTEGER, col2 INTEGER);\
12629                 CREATE TABLE tab1(col0 INTEGER, col1 INTEGER, col2 INTEGER);",
12630            )
12631            .expect("create tables");
12632
12633        engine
12634            .execute(
12635                "INSERT INTO tab0 VALUES (1, 2, 3), (4, 5, 6);\
12636                 INSERT INTO tab1 VALUES (10, 11, 12), (13, 14, 15);",
12637            )
12638            .expect("seed rows");
12639
12640        let batches = engine
12641            .sql(
12642                "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",
12643            )
12644            .expect("execute join query");
12645
12646        let mut total_rows = 0;
12647        for batch in &batches {
12648            total_rows += batch.num_rows();
12649
12650            // Columns 0-2 belong to cor0, 3-5 to cor1, 6-8 to cor2.
12651            for row_idx in 0..batch.num_rows() {
12652                for col_idx in 3..6 {
12653                    assert!(
12654                        batch.column(col_idx).is_null(row_idx),
12655                        "expected right table column {} to be NULL in row {}",
12656                        col_idx,
12657                        row_idx
12658                    );
12659                }
12660            }
12661        }
12662
12663        // Two left rows cross two tab0 rows -> four total results.
12664        assert_eq!(total_rows, 4, "expected Cartesian product with tab0 only");
12665    }
12666
12667    #[test]
12668    fn cross_join_duplicate_table_name_resolves_columns() {
12669        let pager = Arc::new(MemPager::default());
12670        let engine = SqlEngine::new(pager);
12671
12672        use sqlparser::ast::{SetExpr, Statement};
12673        use sqlparser::dialect::SQLiteDialect;
12674        use sqlparser::parser::Parser;
12675
12676        engine
12677            .execute("CREATE TABLE tab1(col0 INTEGER, col1 INTEGER, col2 INTEGER)")
12678            .expect("create tab1");
12679        engine
12680            .execute("INSERT INTO tab1 VALUES (7, 8, 9)")
12681            .expect("insert tab1 row");
12682
12683        let dialect = SQLiteDialect {};
12684        let ast = Parser::parse_sql(
12685            &dialect,
12686            "SELECT tab1.col2 FROM tab1 AS cor0 CROSS JOIN tab1",
12687        )
12688        .expect("parse cross join query");
12689        let Statement::Query(query) = &ast[0] else {
12690            panic!("expected SELECT query");
12691        };
12692        let select = match query.body.as_ref() {
12693            SetExpr::Select(select) => select.as_ref(),
12694            other => panic!("unexpected query body: {other:?}"),
12695        };
12696        assert_eq!(select.from.len(), 1);
12697        assert!(!select.from[0].joins.is_empty());
12698
12699        let batches = engine
12700            .sql("SELECT tab1.col2 FROM tab1 AS cor0 CROSS JOIN tab1")
12701            .expect("run cross join with alias and base table");
12702
12703        let mut values = Vec::new();
12704        for batch in &batches {
12705            let column = batch
12706                .column(0)
12707                .as_any()
12708                .downcast_ref::<Int64Array>()
12709                .expect("int64 column");
12710            for idx in 0..column.len() {
12711                if !column.is_null(idx) {
12712                    values.push(column.value(idx));
12713                }
12714            }
12715        }
12716        assert_eq!(values, vec![9]);
12717
12718        engine
12719            .execute("CREATE TABLE strings(a TEXT)")
12720            .expect("create table");
12721
12722        engine
12723            .execute("INSERT INTO strings VALUES ('3'), ('4'), (NULL)")
12724            .expect("insert seed rows");
12725
12726        let result = engine
12727            .execute("UPDATE strings SET a = 13 WHERE a = '3'")
12728            .expect("update rows");
12729        assert!(matches!(
12730            result[0],
12731            RuntimeStatementResult::Update {
12732                rows_updated: 1,
12733                ..
12734            }
12735        ));
12736
12737        let mut result = engine
12738            .execute("SELECT * FROM strings ORDER BY cast(a AS INTEGER)")
12739            .expect("select rows");
12740        let select_result = result.remove(0);
12741        let batches = match select_result {
12742            RuntimeStatementResult::Select { execution, .. } => {
12743                execution.collect().expect("collect batches")
12744            }
12745            _ => panic!("expected select result"),
12746        };
12747
12748        let mut values: Vec<Option<String>> = Vec::new();
12749        for batch in &batches {
12750            let column = batch
12751                .column(0)
12752                .as_any()
12753                .downcast_ref::<StringArray>()
12754                .expect("string column");
12755            for idx in 0..column.len() {
12756                if column.is_null(idx) {
12757                    values.push(None);
12758                } else {
12759                    values.push(Some(column.value(idx).to_string()));
12760                }
12761            }
12762        }
12763
12764        values.sort_by(|a, b| match (a, b) {
12765            (None, None) => std::cmp::Ordering::Equal,
12766            (None, Some(_)) => std::cmp::Ordering::Less,
12767            (Some(_), None) => std::cmp::Ordering::Greater,
12768            (Some(av), Some(bv)) => {
12769                let a_val = av.parse::<i64>().unwrap_or_default();
12770                let b_val = bv.parse::<i64>().unwrap_or_default();
12771                a_val.cmp(&b_val)
12772            }
12773        });
12774
12775        assert_eq!(
12776            values,
12777            vec![None, Some("4".to_string()), Some("13".to_string())]
12778        );
12779    }
12780
12781    #[test]
12782    fn order_by_honors_configured_default_null_order() {
12783        let pager = Arc::new(MemPager::default());
12784        let engine = SqlEngine::new(pager);
12785
12786        engine
12787            .execute("CREATE TABLE strings(a VARCHAR)")
12788            .expect("create table");
12789        engine
12790            .execute("INSERT INTO strings VALUES ('3'), ('4'), (NULL)")
12791            .expect("insert values");
12792        engine
12793            .execute("UPDATE strings SET a = 13 WHERE a = '3'")
12794            .expect("update value");
12795
12796        let mut result = engine
12797            .execute("SELECT * FROM strings ORDER BY cast(a AS INTEGER)")
12798            .expect("select rows");
12799        let select_result = result.remove(0);
12800        let batches = match select_result {
12801            RuntimeStatementResult::Select { execution, .. } => {
12802                execution.collect().expect("collect batches")
12803            }
12804            _ => panic!("expected select result"),
12805        };
12806
12807        let values = extract_string_options(&batches);
12808        assert_eq!(
12809            values,
12810            vec![Some("4".to_string()), Some("13".to_string()), None]
12811        );
12812
12813        assert!(!engine.default_nulls_first_for_tests());
12814
12815        engine
12816            .execute("SET default_null_order='nulls_first'")
12817            .expect("set default null order");
12818
12819        assert!(engine.default_nulls_first_for_tests());
12820
12821        let mut result = engine
12822            .execute("SELECT * FROM strings ORDER BY cast(a AS INTEGER)")
12823            .expect("select rows");
12824        let select_result = result.remove(0);
12825        let batches = match select_result {
12826            RuntimeStatementResult::Select { execution, .. } => {
12827                execution.collect().expect("collect batches")
12828            }
12829            _ => panic!("expected select result"),
12830        };
12831
12832        let values = extract_string_options(&batches);
12833        assert_eq!(
12834            values,
12835            vec![None, Some("4".to_string()), Some("13".to_string())]
12836        );
12837    }
12838
12839    #[test]
12840    fn arrow_type_from_row_returns_struct_fields() {
12841        let dialect = GenericDialect {};
12842        let statements = parse_sql_with_recursion_limit(
12843            &dialect,
12844            "CREATE TABLE row_types(payload ROW(a INTEGER, b VARCHAR));",
12845        )
12846        .expect("parse ROW type definition");
12847
12848        let data_type = match &statements[0] {
12849            Statement::CreateTable(stmt) => stmt.columns[0].data_type.clone(),
12850            other => panic!("unexpected statement: {other:?}"),
12851        };
12852
12853        let arrow_type = arrow_type_from_sql(&data_type).expect("convert ROW type");
12854        match arrow_type {
12855            arrow::datatypes::DataType::Struct(fields) => {
12856                assert_eq!(fields.len(), 2, "unexpected field count");
12857                assert_eq!(fields[0].name(), "a");
12858                assert_eq!(fields[1].name(), "b");
12859                assert_eq!(fields[0].data_type(), &arrow::datatypes::DataType::Int64);
12860                assert_eq!(fields[1].data_type(), &arrow::datatypes::DataType::Utf8);
12861            }
12862            other => panic!("expected struct type, got {other:?}"),
12863        }
12864    }
12865}