Skip to main content

vibesql_executor/
create_table.rs

1//! CREATE TABLE statement execution
2
3use vibesql_ast::{CreateTableStmt, IndexColumn, OrderDirection};
4use vibesql_catalog::{ColumnSchema, TableIdentifier, TableSchema};
5use vibesql_storage::Database;
6use vibesql_types::DataType;
7
8use crate::{
9    constraint_validator::ConstraintValidator, errors::ExecutorError,
10    privilege_checker::PrivilegeChecker, SelectExecutor,
11};
12
13/// Executor for CREATE TABLE statements
14pub struct CreateTableExecutor;
15
16impl CreateTableExecutor {
17    /// Execute a CREATE TABLE statement
18    ///
19    /// # Arguments
20    ///
21    /// * `stmt` - The CREATE TABLE statement AST node
22    /// * `database` - The database to create the table in
23    ///
24    /// # Returns
25    ///
26    /// Success message or error
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// use vibesql_ast::{ColumnDef, CreateTableStmt};
32    /// use vibesql_executor::CreateTableExecutor;
33    /// use vibesql_storage::Database;
34    /// use vibesql_types::DataType;
35    ///
36    /// let mut db = Database::new();
37    /// let stmt = CreateTableStmt { temporary: false,
38    ///     if_not_exists: false,
39    ///     table_name: "users".to_string(),
40    ///     columns: vec![
41    ///         ColumnDef {
42    ///             name: "id".to_string(),
43    ///             data_type: DataType::Integer,
44    ///             nullable: false,
45    ///             constraints: vec![],
46    ///             default_value: None,
47    ///             comment: None,
48    ///             generated_expr: None, is_exact_integer_type: false,
49    ///         },
50    ///         ColumnDef {
51    ///             name: "name".to_string(),
52    ///             data_type: DataType::Varchar { max_length: Some(255) },
53    ///             nullable: true,
54    ///             constraints: vec![],
55    ///             default_value: None,
56    ///             comment: None,
57    ///             generated_expr: None, is_exact_integer_type: false,
58    ///         },
59    ///     ],
60    ///     table_constraints: vec![],
61    ///     table_options: vec![],
62    ///     quoted: false,
63    ///     name_source: None,
64    ///     as_query: None, without_rowid: false,
65    /// };
66    ///
67    /// let result = CreateTableExecutor::execute(&stmt, &mut db);
68    /// assert!(result.is_ok());
69    /// ```
70    pub fn execute(
71        stmt: &CreateTableStmt,
72        database: &mut Database,
73    ) -> Result<String, ExecutorError> {
74        Self::execute_impl(stmt, database, false, None)
75    }
76
77    /// Execute a `CREATE TABLE` statement (user-facing path), recording the
78    /// verbatim original source text.
79    ///
80    /// SQLite stores the byte-for-byte original `CREATE TABLE` statement in
81    /// `sqlite_master.sql` (whitespace and formatting preserved). When
82    /// `sql_source` is `Some`, it is stamped onto the catalog `TableSchema` so
83    /// that `sqlite_master`/`sqlite_schema` returns it verbatim instead of a
84    /// reconstruction. The trailing semicolon (if any) is stripped by
85    /// `TableSchema::set_sql_source` to match SQLite. Pass `None` when the source
86    /// text is unavailable (e.g. AST built programmatically); callers then fall
87    /// back to reconstructing the SQL. See issue #5619.
88    ///
89    /// This is the UNTRUSTED path — it keeps the #5614 reserved-name and #5553
90    /// duplicate-column guards. For replaying a persisted dump use
91    /// [`execute_for_load_with_source`].
92    pub fn execute_with_source(
93        stmt: &CreateTableStmt,
94        database: &mut Database,
95        sql_source: Option<&str>,
96    ) -> Result<String, ExecutorError> {
97        Self::execute_impl(stmt, database, false, sql_source)
98    }
99
100    /// Execute a CREATE TABLE statement on a TRUSTED (engine-internal) path.
101    ///
102    /// This is used when replaying the engine's own persisted schema (e.g.
103    /// `load_sql_dump` / consensus state-machine replay), not for user-issued
104    /// DDL. A persisted dump is engine-internal data that was already validated
105    /// when first created, so it must always round-trip — it must NOT be
106    /// re-rejected by the user-facing conformance guards.
107    ///
108    /// Specifically this skips:
109    /// - the reserved `sqlite_`-prefix guard, so a table that legitimately
110    ///   reached the catalog (e.g. a pre-#5614 DB where ALTER TABLE RENAME TO
111    ///   `sqlite_x` was still accepted) reloads cleanly instead of bricking the
112    ///   database (issue #5614);
113    /// - the duplicate-column guard, for the same backward-compat reason — a
114    ///   schema that was once persisted must remain loadable.
115    ///
116    /// All other validation (namespace collisions, etc.) still applies, matching
117    /// the prior behavior of the load path for #5613.
118    pub fn execute_for_load(
119        stmt: &CreateTableStmt,
120        database: &mut Database,
121    ) -> Result<String, ExecutorError> {
122        Self::execute_impl(stmt, database, true, None)
123    }
124
125    /// Trusted load path (see [`execute_for_load`]) that ALSO records the
126    /// verbatim original `CREATE TABLE` source text for `sqlite_master.sql`.
127    ///
128    /// This is the entry point used when reloading a persisted `.sql` dump: it
129    /// must bypass the user-facing guards (#5614/#5553) so a previously
130    /// persisted schema always round-trips, while still preserving the byte-for-
131    /// byte original source so `sqlite_master.sql` keeps the user's formatting
132    /// across a save/reload cycle (issue #5619).
133    pub fn execute_for_load_with_source(
134        stmt: &CreateTableStmt,
135        database: &mut Database,
136        sql_source: Option<&str>,
137    ) -> Result<String, ExecutorError> {
138        Self::execute_impl(stmt, database, true, sql_source)
139    }
140
141    fn execute_impl(
142        stmt: &CreateTableStmt,
143        database: &mut Database,
144        trusted: bool,
145        sql_source: Option<&str>,
146    ) -> Result<String, ExecutorError> {
147        // Parse qualified table name (schema.table or just table)
148        // For TEMP tables, use the session-specific temp schema (SQLite compatibility)
149        let (schema_name, table_name, identifier) = if stmt.temporary {
150            // Temporary table - use session-specific temp schema
151            // Each session gets its own temp schema (e.g., "temp_1", "temp_2")
152            // for isolation between database connections
153            let temp_schema = database.catalog.temp_schema_name();
154            let id = TableIdentifier::qualified(temp_schema, false, &stmt.table_name, stmt.quoted);
155            (temp_schema.to_string(), stmt.table_name.clone(), id)
156        } else if let Some((schema_part, table_part)) = stmt.table_name.split_once('.') {
157            // Schema-qualified table name - use qualified identifier
158            // Note: We use stmt.quoted for both parts since the parser combined them
159            // In a future iteration, CREATE TABLE could also store schema/table quoted status separately
160            let id = TableIdentifier::qualified(schema_part, stmt.quoted, table_part, stmt.quoted);
161            (schema_part.to_string(), table_part.to_string(), id)
162        } else {
163            // Simple table name - use current schema
164            let id = TableIdentifier::new(&stmt.table_name, stmt.quoted);
165            (database.catalog.get_current_schema().to_string(), stmt.table_name.clone(), id)
166        };
167
168        // Check CREATE privilege on the schema
169        PrivilegeChecker::check_create(database, &schema_name)?;
170
171        // The reserved-name and duplicate-column guards are USER-facing
172        // conformance checks (issue #5614). They are skipped on the trusted
173        // load/replay path so the engine's own persisted dump always reloads —
174        // see `execute_for_load`.
175        if !trusted {
176            // Reject user attempts to create a table with a reserved name. SQLite
177            // reserves the `sqlite_` prefix for its own schema objects and errors
178            // `object name reserved for internal use: <name>` (sqlite3 3.51.0),
179            // echoing the name exactly as the user spelled it (dequoted, original
180            // case — e.g. `SQLITE_foo`). `table_name` already holds that verbatim
181            // bare-name spelling. This guard is on the user-facing executor only;
182            // the engine's internal sqlite_-prefixed objects are created via
183            // dedicated catalog APIs and never pass through here (issue #5614).
184            if crate::sqlite_schema::is_reserved_object_name(&table_name) {
185                return Err(ExecutorError::SqliteCompatError(format!(
186                    "object name reserved for internal use: {}",
187                    table_name
188                )));
189            }
190
191            // Reject duplicate column names. SQLite compares column names
192            // case-insensitively (#5553) and errors `duplicate column name: <name>`,
193            // echoing the *second* (colliding) occurrence's original spelling —
194            // `CREATE TABLE t(a, A)` reports `duplicate column name: A`
195            // (sqlite3 3.51.0). The AST preserves each column's as-written casing.
196            for (i, col) in stmt.columns.iter().enumerate() {
197                if stmt.columns[..i].iter().any(|prev| prev.name.eq_ignore_ascii_case(&col.name)) {
198                    return Err(ExecutorError::SqliteCompatError(format!(
199                        "duplicate column name: {}",
200                        col.name
201                    )));
202                }
203            }
204        }
205
206        // Handle CREATE TABLE AS SELECT syntax
207        if let Some(query) = &stmt.as_query {
208            return Self::execute_create_as_select(
209                database,
210                &table_name,
211                &schema_name,
212                identifier,
213                stmt.if_not_exists,
214                stmt.name_source.as_deref(),
215                query,
216            );
217        }
218
219        // Check if table already exists in the target schema using SQL:1999 identifier semantics
220        // For CREATE TABLE, we only check the target schema (not temp schema)
221        // Temp tables can shadow main tables, but we allow creating in main even if temp exists
222        // Use table_exists_by_identifier which keys on the canonical (case-folded)
223        // form. SQLite case-folds identifiers regardless of quoting (issue #5553),
224        // so `CREATE TABLE "TBL1"` collides with an existing `tbl1`.
225        if database.catalog.table_exists_by_identifier(&identifier) {
226            if stmt.if_not_exists {
227                // IF NOT EXISTS - silently return success without creating the table
228                return Ok(format!(
229                    "Table '{}' already exists in schema '{}' (skipped)",
230                    table_name, schema_name
231                ));
232            }
233            // A bare `CREATE TABLE` over an existing name is an error. SQLite
234            // echoes the table name *exactly as written in the source*,
235            // preserving its quoting form and casing — `table "tbl1" already
236            // exists` / `table [tbl1] already exists` (sqlite3 3.51.0). The
237            // parser captures that verbatim spelling in `name_source`; fall back
238            // to the schema-qualified normalized name when it is unavailable
239            // (e.g. programmatically-built AST). Mirrors the CREATE TRIGGER
240            // mechanism from #5538.
241            let echoed = stmt
242                .name_source
243                .clone()
244                .unwrap_or_else(|| format!("{}.{}", schema_name, identifier.display()));
245            return Err(ExecutorError::TableAlreadyExists(echoed));
246        }
247
248        // SQLite places tables, indexes, and views in ONE object namespace per
249        // schema. A `CREATE TABLE <name>` must therefore fail when an index of
250        // that name already exists, reporting `there is already an index named
251        // <name>` (sqlite3 3.51.0). This mirrors the symmetric CREATE INDEX
252        // check (`there is already a table named <name>`) and—critically—keeps
253        // the on-disk schema reloadable: previously VibeSQL accepted the
254        // colliding table, then the DDL-replay on the next open hit its own
255        // collision and bricked the database (issue #5613).
256        //
257        // The check is schema-aware (a temp index does not collide with a main
258        // table) and case-insensitive (#5553 identifier folding), reusing the
259        // canonical identifier's table-name component for the comparison.
260        //
261        // Note: IF NOT EXISTS does NOT suppress this cross-type collision —
262        // sqlite3 still raises `there is already an index named X` (verified
263        // against 3.51.0). IF NOT EXISTS only silences a same-type (table)
264        // collision, handled above.
265        if database
266            .catalog
267            .index_name_exists_in_schema(&schema_name, identifier.table_canonical())
268        {
269            // Echo the name as the user spelled it (sqlite3 prints the bare
270            // index name without schema qualification).
271            return Err(ExecutorError::SqliteCompatError(format!(
272                "there is already an index named {}",
273                table_name
274            )));
275        }
276
277        // Check for AUTO_INCREMENT constraints
278        // MySQL allows only one AUTO_INCREMENT column per table
279        let auto_increment_columns: Vec<&str> = stmt
280            .columns
281            .iter()
282            .filter(|col_def| {
283                col_def
284                    .constraints
285                    .iter()
286                    .any(|c| matches!(c.kind, vibesql_ast::ColumnConstraintKind::AutoIncrement))
287            })
288            .map(|col_def| col_def.name.as_str())
289            .collect();
290
291        if auto_increment_columns.len() > 1 {
292            return Err(ExecutorError::ConstraintViolation(
293                "Only one AUTO_INCREMENT column allowed per table".to_string(),
294            ));
295        }
296
297        // Convert AST ColumnDef → Catalog ColumnSchema
298        let mut columns: Vec<ColumnSchema> = stmt
299            .columns
300            .iter()
301            .map(|col_def| {
302                // For AUTO_INCREMENT columns, set default to NEXT VALUE FOR sequence
303                let default_value = if col_def
304                    .constraints
305                    .iter()
306                    .any(|c| matches!(c.kind, vibesql_ast::ColumnConstraintKind::AutoIncrement))
307                {
308                    // Create sequence name: {table_name}_{column_name}_seq
309                    let sequence_name = format!("{}_{}_seq", table_name, col_def.name);
310                    Some(vibesql_ast::Expression::NextValue { sequence_name })
311                } else {
312                    col_def.default_value.as_ref().map(|expr| (**expr).clone())
313                };
314
315                // Extract column-level collation from constraints
316                let collation = col_def.constraints.iter().find_map(|c| {
317                    if let vibesql_ast::ColumnConstraintKind::Collate(coll) = &c.kind {
318                        Some(coll.clone())
319                    } else {
320                        None
321                    }
322                });
323
324                ColumnSchema {
325                    name: col_def.name.clone(),
326                    data_type: col_def.data_type.clone(),
327                    nullable: col_def.nullable,
328                    default_value,
329                    generated_expr: col_def.generated_expr.as_ref().map(|expr| (**expr).clone()),
330                    collation,
331                    is_exact_integer_type: col_def.is_exact_integer_type,
332                }
333            })
334            .collect();
335
336        // Process constraints using the constraint validator
337        let constraint_result =
338            ConstraintValidator::process_constraints(&stmt.columns, &stmt.table_constraints)?;
339
340        // Apply constraint results to columns (updates nullability)
341        ConstraintValidator::apply_to_columns(&mut columns, &constraint_result);
342
343        // Create TableSchema with unqualified name
344        let mut table_schema = TableSchema::new(table_name.clone(), columns);
345
346        // Preserve the verbatim original CREATE TABLE text for sqlite_master.sql
347        // (SQLite stores it byte-for-byte; see issue #5619). The trailing
348        // semicolon is stripped by set_sql_source to match SQLite.
349        if let Some(src) = sql_source {
350            table_schema.set_sql_source(src);
351        }
352
353        // Apply WITHOUT ROWID flag from AST (SQLite compatibility)
354        table_schema.without_rowid = stmt.without_rowid;
355
356        // Apply constraint results to schema (sets PK, unique, and check constraints)
357        ConstraintValidator::apply_to_schema(&mut table_schema, &constraint_result);
358
359        // WITHOUT ROWID tables must have a PRIMARY KEY (SQLite requirement, Issue #4953)
360        if stmt.without_rowid && table_schema.primary_key.is_none() {
361            return Err(ExecutorError::ConstraintViolation(
362                "WITHOUT ROWID tables must have a PRIMARY KEY".to_string(),
363            ));
364        }
365
366        // Detect INTEGER PRIMARY KEY for SQLite rowid aliasing (Issue #4536)
367        // In SQLite, a single-column PRIMARY KEY with exactly "INTEGER" type is an alias for rowid.
368        // The column's value IS the rowid, and SELECT rowid returns this column's value.
369        // IMPORTANT: Only exact "INTEGER" qualifies - "INT" does NOT (even though both parse to DataType::Integer)
370        if let Some(pk_cols) = &table_schema.primary_key {
371            if pk_cols.len() == 1 {
372                if let Some(col_idx) = table_schema.get_column_index(&pk_cols[0]) {
373                    let col = &table_schema.columns[col_idx];
374                    // Only exact "INTEGER" type qualifies for rowid aliasing, not "INT"
375                    if matches!(col.data_type, DataType::Integer) && col.is_exact_integer_type {
376                        table_schema.set_rowid_alias_column(Some(col_idx));
377                    }
378                }
379            }
380        }
381
382        // Check for STORAGE table option and apply storage format
383        for option in &stmt.table_options {
384            if let vibesql_ast::TableOption::Storage(format) = option {
385                table_schema.set_storage_format(*format);
386            }
387        }
388
389        // Process foreign key constraints from table_constraints
390        for constraint in &stmt.table_constraints {
391            if let vibesql_ast::TableConstraintKind::ForeignKey {
392                columns: fk_columns,
393                references_table,
394                references_columns,
395                on_delete,
396                on_update,
397                deferral,
398            } = &constraint.kind
399            {
400                // Resolve column indices for FK columns
401                let column_indices: Vec<usize> = fk_columns
402                    .iter()
403                    .map(|col_name| {
404                        table_schema.get_column_index(col_name).ok_or_else(|| {
405                            ExecutorError::ColumnNotFound {
406                                column_name: col_name.to_string(),
407                                table_name: table_name.clone(),
408                                searched_tables: vec![table_name.clone()],
409                                available_columns: table_schema
410                                    .columns
411                                    .iter()
412                                    .map(|c| c.name.clone())
413                                    .collect(),
414                            }
415                        })
416                    })
417                    .collect::<Result<Vec<_>, _>>()?;
418
419                // Lookup parent table to get parent column indices
420                // If the parent table doesn't exist yet, use placeholder indices.
421                // SQLite stores FK metadata at CREATE TABLE time even if the
422                // referenced table doesn't exist yet.
423                let parent_schema = database.catalog.get_table(references_table);
424
425                let parent_column_indices: Vec<usize> = references_columns
426                    .iter()
427                    .map(|col_name| {
428                        parent_schema
429                            .as_ref()
430                            .and_then(|s| s.get_column_index(col_name))
431                            .unwrap_or(0)
432                    })
433                    .collect();
434
435                // Convert ReferentialAction from AST to catalog type
436                let convert_action = |action: &Option<vibesql_ast::ReferentialAction>| match action
437                    .as_ref()
438                    .unwrap_or(&vibesql_ast::ReferentialAction::NoAction)
439                {
440                    vibesql_ast::ReferentialAction::Cascade => {
441                        vibesql_catalog::ReferentialAction::Cascade
442                    }
443                    vibesql_ast::ReferentialAction::SetNull => {
444                        vibesql_catalog::ReferentialAction::SetNull
445                    }
446                    vibesql_ast::ReferentialAction::SetDefault => {
447                        vibesql_catalog::ReferentialAction::SetDefault
448                    }
449                    vibesql_ast::ReferentialAction::Restrict => {
450                        vibesql_catalog::ReferentialAction::Restrict
451                    }
452                    vibesql_ast::ReferentialAction::NoAction => {
453                        vibesql_catalog::ReferentialAction::NoAction
454                    }
455                };
456
457                // If no parent columns specified, pad with empty strings to match
458                // the FK column count. SQLite stores empty "to" in PRAGMA foreign_key_list
459                // when no column list is given after REFERENCES.
460                let effective_parent_names = if references_columns.is_empty() {
461                    vec![String::new(); fk_columns.len()]
462                } else {
463                    references_columns.clone()
464                };
465                let effective_parent_indices = if parent_column_indices.is_empty() {
466                    vec![0; fk_columns.len()]
467                } else {
468                    parent_column_indices
469                };
470
471                let (is_deferrable, initially_deferred) = deferral
472                    .map(|d| (d.is_deferrable, d.initially_deferred))
473                    .unwrap_or((false, false));
474
475                let fk = vibesql_catalog::ForeignKeyConstraint {
476                    name: constraint.name.clone(),
477                    column_names: fk_columns.clone(),
478                    column_indices,
479                    parent_table: references_table.clone(),
480                    parent_column_names: effective_parent_names,
481                    parent_column_indices: effective_parent_indices,
482                    on_delete: convert_action(on_delete),
483                    on_update: convert_action(on_update),
484                    is_deferrable,
485                    initially_deferred,
486                };
487
488                table_schema.add_foreign_key(fk)?;
489            }
490        }
491
492        // Process column-level REFERENCES constraints
493        // These are parsed as ColumnConstraintKind::References but need to be
494        // converted into ForeignKeyConstraint entries in the schema.
495        // Note: In SQLite, FK constraints are stored as metadata at CREATE TABLE time
496        // even if the referenced table doesn't exist yet. Validation only happens
497        // at INSERT/UPDATE/DELETE time when PRAGMA foreign_keys=ON.
498        // SQLite assigns FK IDs in reverse column order for column-level constraints,
499        // so we collect them first and then add in reverse order.
500        let mut column_level_fks = Vec::new();
501        for col_def in &stmt.columns {
502            for constraint in &col_def.constraints {
503                if let vibesql_ast::ColumnConstraintKind::References {
504                    table: ref_table,
505                    column: ref_column,
506                    on_delete,
507                    on_update,
508                    deferral,
509                } = &constraint.kind
510                {
511                    let col_idx =
512                        table_schema.get_column_index(&col_def.name).ok_or_else(|| {
513                            ExecutorError::ColumnNotFound {
514                                column_name: col_def.name.clone(),
515                                table_name: table_name.clone(),
516                                searched_tables: vec![table_name.clone()],
517                                available_columns: table_schema
518                                    .columns
519                                    .iter()
520                                    .map(|c| c.name.clone())
521                                    .collect(),
522                            }
523                        })?;
524
525                    // Try to lookup parent table to resolve column references.
526                    // If the parent table doesn't exist yet, store the FK with
527                    // the column name but use placeholder index 0.
528                    let parent_schema = database.catalog.get_table(ref_table);
529
530                    let (parent_col_name, parent_col_idx) = if let Some(col) = ref_column {
531                        // Explicit column reference
532                        let idx = parent_schema
533                            .as_ref()
534                            .and_then(|s| s.get_column_index(col))
535                            .unwrap_or(0);
536                        (col.clone(), idx)
537                    } else {
538                        // No column specified - store empty string to match SQLite behavior.
539                        // PRAGMA foreign_key_list shows empty "to" column for implicit PK refs.
540                        // The actual PK column is resolved at enforcement time.
541                        (String::new(), 0)
542                    };
543
544                    let convert_action =
545                        |action: &Option<vibesql_ast::ReferentialAction>| match action
546                            .as_ref()
547                            .unwrap_or(&vibesql_ast::ReferentialAction::NoAction)
548                        {
549                            vibesql_ast::ReferentialAction::Cascade => {
550                                vibesql_catalog::ReferentialAction::Cascade
551                            }
552                            vibesql_ast::ReferentialAction::SetNull => {
553                                vibesql_catalog::ReferentialAction::SetNull
554                            }
555                            vibesql_ast::ReferentialAction::SetDefault => {
556                                vibesql_catalog::ReferentialAction::SetDefault
557                            }
558                            vibesql_ast::ReferentialAction::Restrict => {
559                                vibesql_catalog::ReferentialAction::Restrict
560                            }
561                            vibesql_ast::ReferentialAction::NoAction => {
562                                vibesql_catalog::ReferentialAction::NoAction
563                            }
564                        };
565
566                    let (is_deferrable, initially_deferred) = deferral
567                        .map(|d| (d.is_deferrable, d.initially_deferred))
568                        .unwrap_or((false, false));
569
570                    let fk = vibesql_catalog::ForeignKeyConstraint {
571                        name: constraint.name.clone(),
572                        column_names: vec![col_def.name.clone()],
573                        column_indices: vec![col_idx],
574                        parent_table: ref_table.clone(),
575                        parent_column_names: vec![parent_col_name],
576                        parent_column_indices: vec![parent_col_idx],
577                        on_delete: convert_action(on_delete),
578                        on_update: convert_action(on_update),
579                        is_deferrable,
580                        initially_deferred,
581                    };
582
583                    column_level_fks.push(fk);
584                }
585            }
586        }
587        // Add column-level FKs in reverse order to match SQLite's FK ID assignment
588        for fk in column_level_fks.into_iter().rev() {
589            table_schema.add_foreign_key(fk)?;
590        }
591
592        // If creating in a non-current schema, temporarily switch to it
593        let original_schema = database.catalog.get_current_schema().to_string();
594        let needs_schema_switch = schema_name != original_schema;
595
596        if needs_schema_switch {
597            database
598                .catalog
599                .set_current_schema(&schema_name)
600                .map_err(|e| ExecutorError::StorageError(format!("Schema error: {:?}", e)))?;
601        }
602
603        // Create internal sequences for AUTO_INCREMENT columns
604        for auto_inc_col in &auto_increment_columns {
605            let sequence_name = format!("{}_{}_seq", table_name, auto_inc_col);
606            database
607                .catalog
608                .create_sequence(
609                    sequence_name.clone(),
610                    Some(1), // start_with: 1
611                    1,       // increment_by: 1
612                    Some(1), // min_value: 1
613                    None,    // max_value: unlimited
614                    false,   // cycle: false
615                )
616                .map_err(|e| {
617                    ExecutorError::StorageError(format!(
618                        "Failed to create sequence for AUTO_INCREMENT: {:?}",
619                        e
620                    ))
621                })?;
622        }
623
624        // Create table using Database API with TableIdentifier (handles both catalog and storage)
625        // Note: identifier was created at the start of this function with proper quoted semantics
626        let result = database
627            .create_table_with_identifier(table_schema.clone(), identifier.clone())
628            .map_err(|e| ExecutorError::StorageError(e.to_string()));
629
630        // Check if table creation succeeded before creating indexes
631        result?;
632
633        // Auto-create indexes for PRIMARY KEY and UNIQUE constraints
634        Self::create_implicit_indexes(database, &table_name, &table_schema)?;
635
636        // Restore original schema if we switched
637        if needs_schema_switch {
638            database
639                .catalog
640                .set_current_schema(&original_schema)
641                .map_err(|e| ExecutorError::StorageError(format!("Schema error: {:?}", e)))?;
642        }
643
644        // Return success message
645        Ok(format!("Table '{}' created successfully in schema '{}'", table_name, schema_name))
646    }
647
648    /// Create implicit indexes for PRIMARY KEY and UNIQUE constraints
649    ///
650    /// Production databases automatically create B-tree indexes for these constraints
651    /// to enable efficient query optimization. This function replicates that behavior.
652    fn create_implicit_indexes(
653        database: &mut Database,
654        table_name: &str,
655        table_schema: &TableSchema,
656    ) -> Result<(), ExecutorError> {
657        // Counter for SQLite-compatible auto-index naming: sqlite_autoindex_<table>_<n>
658        let mut autoindex_counter = 1;
659
660        // Auto-create PRIMARY KEY index
661        // Skip autoindex for INTEGER PRIMARY KEY - it's an alias for rowid
662        // and doesn't need a separate B-tree index (matches SQLite behavior)
663        if let Some(pk_cols) = &table_schema.primary_key {
664            if table_schema.rowid_alias_column.is_none() {
665                let index_name = format!("sqlite_autoindex_{}_{}", table_name, autoindex_counter);
666                autoindex_counter += 1;
667
668                // Create IndexColumn specs for the PRIMARY KEY columns
669                let index_columns: Vec<IndexColumn> = pk_cols
670                    .iter()
671                    .map(|col_name| IndexColumn::Column {
672                        column_name: col_name.to_string(),
673                        direction: OrderDirection::Asc,
674                        prefix_length: None,
675                    })
676                    .collect();
677
678                // Add to catalog first
679                let index_metadata = vibesql_catalog::IndexMetadata::new(
680                    index_name.clone(),
681                    table_name.to_string(),
682                    vibesql_catalog::IndexType::BTree,
683                    index_columns
684                        .iter()
685                        .map(|col| {
686                            vibesql_catalog::IndexedColumn::new_column(
687                                col.expect_column_name().to_string(),
688                                vibesql_catalog::SortOrder::Ascending,
689                            )
690                        })
691                        .collect(),
692                    true, // unique
693                );
694                database
695                    .catalog
696                    .add_index(index_metadata)
697                    .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
698
699                // Create the actual B-tree index
700                database
701                    .create_index(index_name, table_name.to_string(), true, index_columns)
702                    .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
703            }
704        }
705
706        // Auto-create UNIQUE constraint indexes
707        for unique_cols in &table_schema.unique_constraints {
708            let index_name = format!("sqlite_autoindex_{}_{}", table_name, autoindex_counter);
709            autoindex_counter += 1;
710
711            // Create IndexColumn specs for the UNIQUE columns
712            let index_columns: Vec<IndexColumn> = unique_cols
713                .iter()
714                .map(|col_name| IndexColumn::Column {
715                    column_name: col_name.to_string(),
716                    direction: OrderDirection::Asc,
717                    prefix_length: None,
718                })
719                .collect();
720
721            // Add to catalog first
722            let index_metadata = vibesql_catalog::IndexMetadata::new(
723                index_name.clone(),
724                table_name.to_string(),
725                vibesql_catalog::IndexType::BTree,
726                index_columns
727                    .iter()
728                    .map(|col| {
729                        vibesql_catalog::IndexedColumn::new_column(
730                            col.expect_column_name().to_string(),
731                            vibesql_catalog::SortOrder::Ascending,
732                        )
733                    })
734                    .collect(),
735                true, // unique
736            );
737            database
738                .catalog
739                .add_index(index_metadata)
740                .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
741
742            // Create the actual B-tree index
743            database
744                .create_index(index_name, table_name.to_string(), true, index_columns)
745                .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
746        }
747
748        Ok(())
749    }
750
751    /// Execute CREATE TABLE ... AS SELECT
752    ///
753    /// Creates a new table with schema derived from the SELECT result,
754    /// and populates it with the query results.
755    fn execute_create_as_select(
756        database: &mut Database,
757        table_name: &str,
758        schema_name: &str,
759        identifier: TableIdentifier,
760        if_not_exists: bool,
761        name_source: Option<&str>,
762        query: &vibesql_ast::SelectStmt,
763    ) -> Result<String, ExecutorError> {
764        // Check if table already exists
765        if database.catalog.table_exists_by_identifier(&identifier) {
766            if if_not_exists {
767                return Ok(format!(
768                    "Table '{}' already exists in schema '{}' (skipped)",
769                    table_name, schema_name
770                ));
771            }
772            // Echo the source quoting form when available (see the bare CREATE
773            // TABLE path above for rationale); fall back to the schema-qualified
774            // normalized name for programmatically-built ASTs.
775            let echoed = name_source
776                .map(str::to_string)
777                .unwrap_or_else(|| format!("{}.{}", schema_name, identifier.display()));
778            return Err(ExecutorError::TableAlreadyExists(echoed));
779        }
780
781        // Cross-type namespace collision: a CTAS name already used by an index
782        // must fail with `there is already an index named <name>`, just like the
783        // bare CREATE TABLE path. Keeps the schema reloadable (issue #5613).
784        if database
785            .catalog
786            .index_name_exists_in_schema(schema_name, identifier.table_canonical())
787        {
788            return Err(ExecutorError::SqliteCompatError(format!(
789                "there is already an index named {}",
790                table_name
791            )));
792        }
793
794        // Execute the SELECT query to get results
795        let rows = SelectExecutor::new(database).execute(query)?;
796
797        // Derive column names from the SELECT list (expanding wildcards if needed)
798        let column_names =
799            Self::derive_column_names_from_select_list(&query.select_list, &query.from, database)?;
800
801        // Derive column schema from the first row (if any) or default to BLOB
802        let columns: Vec<ColumnSchema> = column_names
803            .iter()
804            .enumerate()
805            .map(|(idx, col_name)| {
806                // Try to infer data type from the first row if available
807                let data_type = if !rows.is_empty() && idx < rows[0].values.len() {
808                    Self::infer_data_type(&rows[0].values[idx])
809                } else {
810                    // No rows or column - default to BLOB affinity
811                    DataType::BinaryLargeObject
812                };
813
814                ColumnSchema {
815                    name: col_name.to_string(),
816                    data_type,
817                    nullable: true, // Default to nullable for CTAS
818                    default_value: None,
819                    generated_expr: None,
820                    collation: None,              // CTAS doesn't preserve collation
821                    is_exact_integer_type: false, // CTAS doesn't preserve exact type
822                }
823            })
824            .collect();
825
826        // Create the table schema
827        let table_schema = TableSchema::new(table_name.to_string(), columns);
828
829        // Create the table
830        database
831            .create_table_with_identifier(table_schema, identifier)
832            .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
833
834        // Insert the result rows into the new table
835        let row_count = rows.len();
836        for row in rows {
837            database
838                .insert_row(table_name, row)
839                .map_err(|e| ExecutorError::StorageError(e.to_string()))?;
840        }
841
842        Ok(format!(
843            "Table '{}' created successfully in schema '{}' with {} rows",
844            table_name, schema_name, row_count
845        ))
846    }
847
848    /// Derive column names from a SELECT list, expanding wildcards using the database schema
849    fn derive_column_names_from_select_list(
850        select_list: &[vibesql_ast::SelectItem],
851        from: &Option<vibesql_ast::FromClause>,
852        database: &Database,
853    ) -> Result<Vec<String>, ExecutorError> {
854        let mut names = Vec::new();
855        let mut counter = 0;
856
857        for item in select_list {
858            match item {
859                vibesql_ast::SelectItem::Wildcard { .. } => {
860                    // Expand wildcard using the FROM clause tables
861                    let table_names = Self::get_table_names_from_from(from)?;
862                    for table_name in table_names {
863                        if let Some(schema) = database.catalog.get_table(&table_name) {
864                            for col in &schema.columns {
865                                names.push(col.name.clone());
866                            }
867                        } else {
868                            return Err(ExecutorError::TableNotFound(table_name));
869                        }
870                    }
871                }
872                vibesql_ast::SelectItem::QualifiedWildcard { qualifier, .. } => {
873                    // Expand table.* using the specific table's schema
874                    if let Some(schema) = database.catalog.get_table(qualifier) {
875                        for col in &schema.columns {
876                            names.push(col.name.clone());
877                        }
878                    } else {
879                        return Err(ExecutorError::TableNotFound(qualifier.clone()));
880                    }
881                }
882                vibesql_ast::SelectItem::Expression { expr, alias, .. } => {
883                    let name = if let Some(alias) = alias {
884                        alias.clone()
885                    } else {
886                        // Try to derive from expression
887                        Self::derive_column_name_from_expr(expr, &mut counter)
888                    };
889                    names.push(name);
890                }
891            }
892        }
893
894        Ok(names)
895    }
896
897    /// Extract table names from a FROM clause
898    fn get_table_names_from_from(
899        from: &Option<vibesql_ast::FromClause>,
900    ) -> Result<Vec<String>, ExecutorError> {
901        let mut names = Vec::new();
902
903        match from {
904            None => {
905                // No FROM clause - can't expand wildcard
906                return Err(ExecutorError::UnsupportedFeature(
907                    "CREATE TABLE AS SELECT * requires a FROM clause".to_string(),
908                ));
909            }
910            Some(vibesql_ast::FromClause::Table { name, .. }) => {
911                names.push(name.clone());
912            }
913            Some(vibesql_ast::FromClause::Join { left, right, .. }) => {
914                // Recursively get tables from join
915                names.extend(Self::get_table_names_from_from(&Some(*left.clone()))?);
916                names.extend(Self::get_table_names_from_from(&Some(*right.clone()))?);
917            }
918            Some(vibesql_ast::FromClause::Subquery { alias, .. }) => {
919                // For derived tables (subqueries), we can't easily expand *
920                // because we'd need to recursively process the subquery
921                return Err(ExecutorError::UnsupportedFeature(format!(
922                    "CREATE TABLE AS SELECT * from subquery '{}' not supported - please specify columns explicitly",
923                    alias
924                )));
925            }
926            Some(vibesql_ast::FromClause::Values { alias, .. }) => {
927                // VALUES clause - can't determine column names from schema
928                return Err(ExecutorError::UnsupportedFeature(format!(
929                    "CREATE TABLE AS SELECT * from VALUES '{}' not supported - please specify columns explicitly",
930                    alias
931                )));
932            }
933        }
934
935        Ok(names)
936    }
937
938    /// Derive a column name from an expression
939    fn derive_column_name_from_expr(expr: &vibesql_ast::Expression, counter: &mut usize) -> String {
940        match expr {
941            vibesql_ast::Expression::ColumnRef(col_id) => col_id.column_canonical().to_string(),
942            vibesql_ast::Expression::Literal(_) => {
943                *counter += 1;
944                format!("column{}", counter)
945            }
946            vibesql_ast::Expression::Function { name, .. } => {
947                // Use the function name as the column name
948                name.to_string().to_lowercase()
949            }
950            _ => {
951                *counter += 1;
952                format!("column{}", counter)
953            }
954        }
955    }
956
957    /// Infer DataType from an SqlValue
958    fn infer_data_type(value: &vibesql_types::SqlValue) -> DataType {
959        use vibesql_types::SqlValue;
960        match value {
961            SqlValue::Null => DataType::BinaryLargeObject,
962            SqlValue::Boolean(_) => DataType::Boolean,
963            SqlValue::Integer(_) => DataType::Integer,
964            SqlValue::Bigint(_) => DataType::Bigint,
965            SqlValue::Smallint(_) => DataType::Smallint,
966            SqlValue::Unsigned(_) => DataType::Unsigned,
967            SqlValue::Float(_) | SqlValue::Real(_) => DataType::Real,
968            SqlValue::Double(_) | SqlValue::Numeric(_) => DataType::DoublePrecision,
969            SqlValue::Character(_) => DataType::Character { length: 255 },
970            SqlValue::Varchar(_) => DataType::Varchar { max_length: None },
971            SqlValue::Date(_) => DataType::Date,
972            SqlValue::Time(_) => DataType::Time { with_timezone: false },
973            SqlValue::Timestamp(_) => DataType::Timestamp { with_timezone: false },
974            SqlValue::Interval(_) => DataType::Interval {
975                start_field: vibesql_types::IntervalField::Day,
976                end_field: None,
977            },
978            SqlValue::Vector(v) => DataType::Vector { dimensions: v.len() as u32 },
979            SqlValue::Blob(_) => DataType::BinaryLargeObject,
980        }
981    }
982}