vibesql_parser/arena_parser/
ddl.rs

1//! Arena-allocated DDL statement parsing.
2//!
3//! This module provides parsing for DDL statements including:
4//! - Transaction statements (BEGIN, COMMIT, ROLLBACK, SAVEPOINT)
5//! - CREATE/DROP TABLE, INDEX, VIEW
6//! - ALTER TABLE (including MySQL MODIFY/CHANGE COLUMN)
7//! - ANALYZE
8
9use bumpalo::collections::Vec as BumpVec;
10use vibesql_ast::arena::{
11    AddColumnStmt, AddConstraintStmt, AlterColumnStmt, AlterTableStmt, AnalyzeStmt, BeginStmt,
12    ChangeColumnStmt, ColumnConstraint, ColumnConstraintKind, ColumnDef, CommitStmt,
13    CreateIndexStmt, CreateViewStmt, DropColumnStmt, DropConstraintStmt, DropIndexStmt,
14    DropTableStmt, DropViewStmt, DurabilityHint, Expression, IndexColumn, IndexType,
15    ModifyColumnStmt, OrderDirection, PragmaStmt, PragmaValue, ReferentialAction,
16    ReleaseSavepointStmt, RenameTableStmt, RollbackStmt, RollbackToSavepointStmt, SavepointStmt,
17    Symbol, TableConstraint, TableConstraintKind, TruncateCascadeOption, TruncateTableStmt,
18};
19
20use super::ArenaParser;
21use crate::{keywords::Keyword, token::Token, ParseError};
22
23impl<'arena> ArenaParser<'arena> {
24    // ========================================================================
25    // Transaction Statements
26    // ========================================================================
27
28    /// Parse BEGIN [TRANSACTION] [WITH DURABILITY = <mode>] or START TRANSACTION [WITH DURABILITY =
29    /// <mode>] statement.
30    pub(crate) fn parse_begin_statement(&mut self) -> Result<BeginStmt, ParseError> {
31        if self.peek_keyword(Keyword::Begin) {
32            self.consume_keyword(Keyword::Begin)?;
33        } else if self.peek_keyword(Keyword::Start) {
34            self.consume_keyword(Keyword::Start)?;
35        } else {
36            return Err(ParseError { message: "Expected BEGIN or START".to_string() });
37        }
38
39        // Optional TRANSACTION keyword
40        self.try_consume_keyword(Keyword::Transaction);
41
42        // Parse optional WITH DURABILITY = <mode> clause
43        let durability = if self.peek_keyword(Keyword::With) {
44            self.consume_keyword(Keyword::With)?;
45            self.consume_keyword(Keyword::Durability)?;
46
47            // Optional = sign
48            self.try_consume(&Token::Symbol('='));
49
50            // Parse durability mode
51            self.parse_durability_hint()?
52        } else {
53            DurabilityHint::Default
54        };
55
56        Ok(BeginStmt { durability })
57    }
58
59    /// Parse a durability hint keyword.
60    fn parse_durability_hint(&mut self) -> Result<DurabilityHint, ParseError> {
61        if self.try_consume_keyword(Keyword::Default) {
62            Ok(DurabilityHint::Default)
63        } else if self.try_consume_keyword(Keyword::Durable) {
64            Ok(DurabilityHint::Durable)
65        } else if self.try_consume_keyword(Keyword::Lazy) {
66            Ok(DurabilityHint::Lazy)
67        } else if self.try_consume_keyword(Keyword::Volatile) {
68            Ok(DurabilityHint::Volatile)
69        } else {
70            Err(ParseError {
71                message: "Expected durability mode: DEFAULT, DURABLE, LAZY, or VOLATILE"
72                    .to_string(),
73            })
74        }
75    }
76
77    /// Parse COMMIT or END statement (END is SQLite alias for COMMIT).
78    pub(crate) fn parse_commit_statement(&mut self) -> Result<CommitStmt, ParseError> {
79        // Consume COMMIT or END (SQLite uses END as alias for COMMIT)
80        if self.peek_keyword(Keyword::Commit) {
81            self.consume_keyword(Keyword::Commit)?;
82        } else if self.peek_keyword(Keyword::End) {
83            self.consume_keyword(Keyword::End)?;
84        } else {
85            return Err(ParseError { message: "Expected COMMIT or END".to_string() });
86        }
87        Ok(CommitStmt)
88    }
89
90    /// Parse ROLLBACK statement.
91    pub(crate) fn parse_rollback_statement(&mut self) -> Result<RollbackStmt, ParseError> {
92        self.consume_keyword(Keyword::Rollback)?;
93        Ok(RollbackStmt)
94    }
95
96    /// Parse ROLLBACK TO SAVEPOINT statement.
97    pub(crate) fn parse_rollback_to_savepoint_statement(
98        &mut self,
99    ) -> Result<RollbackToSavepointStmt, ParseError> {
100        self.consume_keyword(Keyword::Rollback)?;
101        self.consume_keyword(Keyword::To)?;
102        self.consume_keyword(Keyword::Savepoint)?;
103        let name = self.parse_arena_identifier()?;
104        Ok(RollbackToSavepointStmt { name })
105    }
106
107    /// Parse SAVEPOINT statement.
108    pub(crate) fn parse_savepoint_statement(&mut self) -> Result<SavepointStmt, ParseError> {
109        self.consume_keyword(Keyword::Savepoint)?;
110        let name = self.parse_arena_identifier()?;
111        Ok(SavepointStmt { name })
112    }
113
114    /// Parse RELEASE SAVEPOINT statement.
115    pub(crate) fn parse_release_savepoint_statement(
116        &mut self,
117    ) -> Result<ReleaseSavepointStmt, ParseError> {
118        self.consume_keyword(Keyword::Release)?;
119        self.consume_keyword(Keyword::Savepoint)?;
120        let name = self.parse_arena_identifier()?;
121        Ok(ReleaseSavepointStmt { name })
122    }
123
124    // ========================================================================
125    // CREATE Statements
126    // ========================================================================
127
128    /// Parse CREATE INDEX statement.
129    pub(crate) fn parse_create_index_statement(
130        &mut self,
131    ) -> Result<CreateIndexStmt<'arena>, ParseError> {
132        self.consume_keyword(Keyword::Create)?;
133
134        // Check for UNIQUE or FULLTEXT
135        let index_type = if self.try_consume_keyword(Keyword::Unique) {
136            IndexType::BTree { unique: true }
137        } else if self.try_consume_keyword(Keyword::Fulltext) {
138            IndexType::Fulltext
139        } else if self.try_consume_keyword(Keyword::Spatial) {
140            IndexType::Spatial
141        } else {
142            IndexType::BTree { unique: false }
143        };
144
145        self.consume_keyword(Keyword::Index)?;
146
147        // Check for IF NOT EXISTS
148        let if_not_exists = if self.try_consume_keyword(Keyword::If) {
149            self.expect_keyword(Keyword::Not)?;
150            self.expect_keyword(Keyword::Exists)?;
151            true
152        } else {
153            false
154        };
155
156        let index_name = self.parse_arena_identifier()?;
157
158        self.consume_keyword(Keyword::On)?;
159        let table_name = self.parse_arena_identifier()?;
160
161        self.expect_token(Token::LParen)?;
162        let columns = self.parse_index_columns()?;
163        self.expect_token(Token::RParen)?;
164
165        Ok(CreateIndexStmt { if_not_exists, index_name, table_name, index_type, columns })
166    }
167
168    /// Parse CREATE VIEW statement.
169    pub(crate) fn parse_create_view_statement(
170        &mut self,
171    ) -> Result<CreateViewStmt<'arena>, ParseError> {
172        self.consume_keyword(Keyword::Create)?;
173
174        // Check for OR REPLACE
175        let or_replace = if self.try_consume_keyword(Keyword::Or) {
176            self.expect_keyword(Keyword::Replace)?;
177            true
178        } else {
179            false
180        };
181
182        // Check for TEMP/TEMPORARY
183        let temporary =
184            self.try_consume_keyword(Keyword::Temp) || self.try_consume_keyword(Keyword::Temporary);
185
186        self.consume_keyword(Keyword::View)?;
187
188        // Check for IF NOT EXISTS
189        let if_not_exists = if self.try_consume_keyword(Keyword::If) {
190            self.expect_keyword(Keyword::Not)?;
191            self.expect_keyword(Keyword::Exists)?;
192            true
193        } else {
194            false
195        };
196
197        let view_name = self.parse_arena_identifier()?;
198
199        // Parse optional column list
200        let columns = if self.try_consume(&Token::LParen) {
201            let cols = self.parse_identifier_list()?;
202            self.expect_token(Token::RParen)?;
203            Some(cols)
204        } else {
205            None
206        };
207
208        self.consume_keyword(Keyword::As)?;
209
210        // Parse the query: can be SELECT or VALUES
211        // Example: CREATE VIEW dual AS VALUES('x')
212        let query = if self.peek_keyword(Keyword::Values) {
213            self.parse_values_statement()?
214        } else {
215            self.parse_select_statement()?
216        };
217
218        // Check for WITH CHECK OPTION
219        let with_check_option = if self.try_consume_keyword(Keyword::With) {
220            self.expect_keyword(Keyword::Check)?;
221            self.expect_keyword(Keyword::Option)?;
222            true
223        } else {
224            false
225        };
226
227        Ok(CreateViewStmt {
228            view_name,
229            columns,
230            query,
231            with_check_option,
232            or_replace,
233            if_not_exists,
234            temporary,
235        })
236    }
237
238    // ========================================================================
239    // DROP Statements
240    // ========================================================================
241
242    /// Parse DROP TABLE statement.
243    pub(crate) fn parse_drop_table_statement(&mut self) -> Result<DropTableStmt, ParseError> {
244        self.consume_keyword(Keyword::Drop)?;
245        self.consume_keyword(Keyword::Table)?;
246
247        let if_exists = if self.try_consume_keyword(Keyword::If) {
248            self.expect_keyword(Keyword::Exists)?;
249            true
250        } else {
251            false
252        };
253
254        let table_name = self.parse_arena_identifier()?;
255
256        Ok(DropTableStmt { table_name, if_exists })
257    }
258
259    /// Parse DROP INDEX statement.
260    pub(crate) fn parse_drop_index_statement(&mut self) -> Result<DropIndexStmt, ParseError> {
261        self.consume_keyword(Keyword::Drop)?;
262        self.consume_keyword(Keyword::Index)?;
263
264        let if_exists = if self.try_consume_keyword(Keyword::If) {
265            self.expect_keyword(Keyword::Exists)?;
266            true
267        } else {
268            false
269        };
270
271        let index_name = self.parse_arena_identifier()?;
272
273        Ok(DropIndexStmt { if_exists, index_name })
274    }
275
276    /// Parse DROP VIEW statement.
277    pub(crate) fn parse_drop_view_statement(&mut self) -> Result<DropViewStmt, ParseError> {
278        self.consume_keyword(Keyword::Drop)?;
279        self.consume_keyword(Keyword::View)?;
280
281        let if_exists = if self.try_consume_keyword(Keyword::If) {
282            self.expect_keyword(Keyword::Exists)?;
283            true
284        } else {
285            false
286        };
287
288        let view_name = self.parse_arena_identifier()?;
289
290        let (cascade, restrict) = if self.try_consume_keyword(Keyword::Cascade) {
291            (true, false)
292        } else if self.try_consume_keyword(Keyword::Restrict) {
293            (false, true)
294        } else {
295            (false, false)
296        };
297
298        Ok(DropViewStmt { view_name, if_exists, cascade, restrict })
299    }
300
301    /// Parse TRUNCATE TABLE statement.
302    pub(crate) fn parse_truncate_table_statement(
303        &mut self,
304    ) -> Result<TruncateTableStmt<'arena>, ParseError> {
305        self.consume_keyword(Keyword::Truncate)?;
306        self.try_consume_keyword(Keyword::Table);
307
308        let if_exists = if self.try_consume_keyword(Keyword::If) {
309            self.expect_keyword(Keyword::Exists)?;
310            true
311        } else {
312            false
313        };
314
315        // Parse table names (can be comma-separated)
316        let mut table_names = BumpVec::new_in(self.arena);
317        loop {
318            table_names.push(self.parse_arena_identifier()?);
319            if !self.try_consume(&Token::Comma) {
320                break;
321            }
322        }
323
324        let cascade = if self.try_consume_keyword(Keyword::Cascade) {
325            Some(TruncateCascadeOption::Cascade)
326        } else if self.try_consume_keyword(Keyword::Restrict) {
327            Some(TruncateCascadeOption::Restrict)
328        } else {
329            None
330        };
331
332        Ok(TruncateTableStmt { table_names, if_exists, cascade })
333    }
334
335    // ========================================================================
336    // ALTER TABLE Statements
337    // ========================================================================
338
339    /// Parse ALTER TABLE statement.
340    pub fn parse_alter_table_statement(
341        &mut self,
342    ) -> Result<&'arena AlterTableStmt<'arena>, ParseError> {
343        // ALTER TABLE
344        self.expect_keyword(Keyword::Alter)?;
345        self.expect_keyword(Keyword::Table)?;
346
347        let table_name = self.parse_table_name()?;
348
349        // Dispatch based on operation
350        let stmt = match self.peek() {
351            Token::Keyword { keyword: Keyword::Add, .. } => {
352                self.advance();
353                match self.peek() {
354                    Token::Keyword { keyword: Keyword::Column, .. } => {
355                        self.advance();
356                        self.parse_add_column(table_name)?
357                    }
358                    // SQL:1999 allows adding constraints with or without CONSTRAINT keyword
359                    Token::Keyword {
360                        keyword:
361                            Keyword::Constraint
362                            | Keyword::Check
363                            | Keyword::Unique
364                            | Keyword::Primary
365                            | Keyword::Foreign,
366                        ..
367                    } => self.parse_add_constraint(table_name)?,
368                    // SQL:1999 allows ADD COLUMN without the COLUMN keyword
369                    Token::Identifier(_) => self.parse_add_column(table_name)?,
370                    _ => {
371                        return Err(ParseError {
372                            message:
373                                "Expected COLUMN, constraint keyword, or column name after ADD"
374                                    .to_string(),
375                        })
376                    }
377                }
378            }
379            Token::Keyword { keyword: Keyword::Drop, .. } => {
380                self.advance();
381                match self.peek() {
382                    Token::Keyword { keyword: Keyword::Column, .. } => {
383                        self.advance();
384                        self.parse_drop_column(table_name)?
385                    }
386                    Token::Keyword { keyword: Keyword::Constraint, .. } => {
387                        self.advance();
388                        self.parse_drop_constraint(table_name)?
389                    }
390                    _ => {
391                        return Err(ParseError {
392                            message: "Expected COLUMN or CONSTRAINT after DROP".to_string(),
393                        })
394                    }
395                }
396            }
397            Token::Keyword { keyword: Keyword::Alter, .. } => {
398                self.advance();
399                self.expect_keyword(Keyword::Column)?;
400                self.parse_alter_column(table_name)?
401            }
402            Token::Keyword { keyword: Keyword::Rename, .. } => {
403                self.advance();
404                self.parse_rename_table(table_name)?
405            }
406            Token::Keyword { keyword: Keyword::Modify, .. } => {
407                self.advance();
408                self.parse_modify_column(table_name)?
409            }
410            Token::Keyword { keyword: Keyword::Change, .. } => {
411                self.advance();
412                self.parse_change_column(table_name)?
413            }
414            _ => {
415                return Err(ParseError {
416                    message:
417                        "Expected ADD, DROP, ALTER, RENAME, MODIFY, or CHANGE after table name"
418                            .to_string(),
419                })
420            }
421        };
422
423        Ok(self.arena.alloc(stmt))
424    }
425
426    /// Parse a table name (identifier).
427    fn parse_table_name(&mut self) -> Result<Symbol, ParseError> {
428        match self.peek() {
429            Token::Identifier(name) => {
430                let name = name.clone();
431                self.advance();
432                Ok(self.intern(&name))
433            }
434            _ => Err(ParseError { message: self.peek().syntax_error() }),
435        }
436    }
437
438    /// Parse a column name (identifier).
439    fn parse_column_name(&mut self) -> Result<Symbol, ParseError> {
440        match self.peek() {
441            Token::Identifier(name) => {
442                let name = name.clone();
443                self.advance();
444                Ok(self.intern(&name))
445            }
446            _ => Err(ParseError { message: self.peek().syntax_error() }),
447        }
448    }
449
450    /// Parse ADD COLUMN operation.
451    fn parse_add_column(
452        &mut self,
453        table_name: Symbol,
454    ) -> Result<AlterTableStmt<'arena>, ParseError> {
455        let column_name = self.parse_column_name()?;
456        let data_type = self.parse_data_type()?;
457
458        // Parse optional DEFAULT clause
459        let default_value: Option<&'arena Expression<'arena>> =
460            if self.peek_keyword(Keyword::Default) {
461                self.advance();
462                let expr = self.parse_expression()?;
463                Some(self.arena.alloc(expr))
464            } else {
465                None
466            };
467
468        // Parse column constraints
469        let mut nullable = true;
470        let mut constraints = BumpVec::new_in(self.arena);
471
472        loop {
473            match self.peek() {
474                Token::Keyword { keyword: Keyword::Not, .. } => {
475                    self.advance();
476                    self.expect_keyword(Keyword::Null)?;
477                    nullable = false;
478                }
479                Token::Keyword { keyword: Keyword::Primary, .. } => {
480                    self.advance();
481                    self.expect_keyword(Keyword::Key)?;
482                    constraints.push(ColumnConstraint {
483                        name: None,
484                        kind: ColumnConstraintKind::PrimaryKey,
485                    });
486                }
487                Token::Keyword { keyword: Keyword::Unique, .. } => {
488                    self.advance();
489                    constraints
490                        .push(ColumnConstraint { name: None, kind: ColumnConstraintKind::Unique });
491                }
492                Token::Keyword { keyword: Keyword::References, .. } => {
493                    self.advance();
494                    let ref_table = self.parse_table_name()?;
495                    // Column specification is optional in SQLite - defaults to PK
496                    let ref_column = if self.peek() == &Token::LParen {
497                        self.advance(); // consume LParen
498                        let col = self.parse_column_name()?;
499                        self.expect_token(Token::RParen)?;
500                        Some(col)
501                    } else {
502                        None
503                    };
504                    constraints.push(ColumnConstraint {
505                        name: None,
506                        kind: ColumnConstraintKind::References {
507                            table: ref_table,
508                            column: ref_column,
509                            on_delete: None,
510                            on_update: None,
511                        },
512                    });
513                }
514                _ => break,
515            }
516        }
517
518        let column_def = ColumnDef {
519            name: column_name,
520            data_type,
521            nullable,
522            constraints,
523            default_value,
524            comment: None,
525            generated_expr: None,
526        };
527
528        Ok(AlterTableStmt::AddColumn(AddColumnStmt { table_name, column_def }))
529    }
530
531    /// Parse DROP COLUMN operation.
532    fn parse_drop_column(
533        &mut self,
534        table_name: Symbol,
535    ) -> Result<AlterTableStmt<'arena>, ParseError> {
536        let if_exists =
537            self.try_consume_keyword(Keyword::If) && self.try_consume_keyword(Keyword::Exists);
538
539        let column_name = self.parse_column_name()?;
540
541        Ok(AlterTableStmt::DropColumn(DropColumnStmt { table_name, column_name, if_exists }))
542    }
543
544    /// Parse ALTER COLUMN operation.
545    fn parse_alter_column(
546        &mut self,
547        table_name: Symbol,
548    ) -> Result<AlterTableStmt<'arena>, ParseError> {
549        let column_name = self.parse_column_name()?;
550
551        match self.peek() {
552            Token::Keyword { keyword: Keyword::Set, .. } => {
553                self.advance();
554                match self.peek() {
555                    Token::Keyword { keyword: Keyword::Default, .. } => {
556                        self.advance();
557                        let default = self.parse_expression()?;
558                        Ok(AlterTableStmt::AlterColumn(AlterColumnStmt::SetDefault {
559                            table_name,
560                            column_name,
561                            default,
562                        }))
563                    }
564                    Token::Keyword { keyword: Keyword::Not, .. } => {
565                        self.advance();
566                        self.expect_keyword(Keyword::Null)?;
567                        Ok(AlterTableStmt::AlterColumn(AlterColumnStmt::SetNotNull {
568                            table_name,
569                            column_name,
570                        }))
571                    }
572                    _ => Err(ParseError {
573                        message: "Expected DEFAULT or NOT NULL after SET".to_string(),
574                    }),
575                }
576            }
577            Token::Keyword { keyword: Keyword::Drop, .. } => {
578                self.advance();
579                match self.peek() {
580                    Token::Keyword { keyword: Keyword::Default, .. } => {
581                        self.advance();
582                        Ok(AlterTableStmt::AlterColumn(AlterColumnStmt::DropDefault {
583                            table_name,
584                            column_name,
585                        }))
586                    }
587                    Token::Keyword { keyword: Keyword::Not, .. } => {
588                        self.advance();
589                        self.expect_keyword(Keyword::Null)?;
590                        Ok(AlterTableStmt::AlterColumn(AlterColumnStmt::DropNotNull {
591                            table_name,
592                            column_name,
593                        }))
594                    }
595                    _ => Err(ParseError {
596                        message: "Expected DEFAULT or NOT NULL after DROP".to_string(),
597                    }),
598                }
599            }
600            _ => Err(ParseError { message: "Expected SET or DROP after column name".to_string() }),
601        }
602    }
603
604    /// Parse ADD CONSTRAINT operation.
605    fn parse_add_constraint(
606        &mut self,
607        table_name: Symbol,
608    ) -> Result<AlterTableStmt<'arena>, ParseError> {
609        let constraint = self.parse_table_constraint()?;
610        Ok(AlterTableStmt::AddConstraint(AddConstraintStmt { table_name, constraint }))
611    }
612
613    /// Parse DROP CONSTRAINT operation.
614    fn parse_drop_constraint(
615        &mut self,
616        table_name: Symbol,
617    ) -> Result<AlterTableStmt<'arena>, ParseError> {
618        let constraint_name = self.parse_column_name()?;
619        Ok(AlterTableStmt::DropConstraint(DropConstraintStmt { table_name, constraint_name }))
620    }
621
622    /// Parse RENAME TO operation.
623    fn parse_rename_table(
624        &mut self,
625        table_name: Symbol,
626    ) -> Result<AlterTableStmt<'arena>, ParseError> {
627        self.expect_keyword(Keyword::To)?;
628        let new_table_name = self.parse_table_name()?;
629        Ok(AlterTableStmt::RenameTable(RenameTableStmt { table_name, new_table_name }))
630    }
631
632    /// Parse MODIFY COLUMN operation (MySQL-style).
633    fn parse_modify_column(
634        &mut self,
635        table_name: Symbol,
636    ) -> Result<AlterTableStmt<'arena>, ParseError> {
637        // MODIFY [COLUMN] column_name new_definition
638        if self.peek_keyword(Keyword::Column) {
639            self.advance();
640        }
641
642        let column_name = self.parse_column_name()?;
643        let data_type = self.parse_data_type()?;
644
645        // Parse optional DEFAULT clause
646        let default_value: Option<&'arena Expression<'arena>> =
647            if self.peek_keyword(Keyword::Default) {
648                self.advance();
649                let expr = self.parse_expression()?;
650                Some(self.arena.alloc(expr))
651            } else {
652                None
653            };
654
655        // Parse column constraints
656        let (nullable, constraints) = self.parse_column_constraints()?;
657
658        let new_column_def = ColumnDef {
659            name: column_name,
660            data_type,
661            nullable,
662            constraints,
663            default_value,
664            comment: None,
665            generated_expr: None,
666        };
667
668        Ok(AlterTableStmt::ModifyColumn(ModifyColumnStmt {
669            table_name,
670            column_name,
671            new_column_def,
672        }))
673    }
674
675    /// Parse CHANGE COLUMN operation (MySQL-style - rename and modify).
676    fn parse_change_column(
677        &mut self,
678        table_name: Symbol,
679    ) -> Result<AlterTableStmt<'arena>, ParseError> {
680        // CHANGE [COLUMN] old_column_name new_column_name new_definition
681        if self.peek_keyword(Keyword::Column) {
682            self.advance();
683        }
684
685        let old_column_name = self.parse_column_name()?;
686        let new_column_name = self.parse_column_name()?;
687        let data_type = self.parse_data_type()?;
688
689        // Parse optional DEFAULT clause
690        let default_value: Option<&'arena Expression<'arena>> =
691            if self.peek_keyword(Keyword::Default) {
692                self.advance();
693                let expr = self.parse_expression()?;
694                Some(self.arena.alloc(expr))
695            } else {
696                None
697            };
698
699        // Parse column constraints
700        let (nullable, constraints) = self.parse_column_constraints()?;
701
702        let new_column_def = ColumnDef {
703            name: new_column_name,
704            data_type,
705            nullable,
706            constraints,
707            default_value,
708            comment: None,
709            generated_expr: None,
710        };
711
712        Ok(AlterTableStmt::ChangeColumn(ChangeColumnStmt {
713            table_name,
714            old_column_name,
715            new_column_def,
716        }))
717    }
718
719    /// Parse column constraints.
720    fn parse_column_constraints(
721        &mut self,
722    ) -> Result<(bool, BumpVec<'arena, ColumnConstraint<'arena>>), ParseError> {
723        let mut nullable = true;
724        let mut constraints = BumpVec::new_in(self.arena);
725
726        loop {
727            match self.peek() {
728                Token::Keyword { keyword: Keyword::Not, .. } => {
729                    self.advance();
730                    self.expect_keyword(Keyword::Null)?;
731                    nullable = false;
732                }
733                Token::Keyword { keyword: Keyword::Primary, .. } => {
734                    self.advance();
735                    self.expect_keyword(Keyword::Key)?;
736                    constraints.push(ColumnConstraint {
737                        name: None,
738                        kind: ColumnConstraintKind::PrimaryKey,
739                    });
740                }
741                Token::Keyword { keyword: Keyword::Unique, .. } => {
742                    self.advance();
743                    constraints
744                        .push(ColumnConstraint { name: None, kind: ColumnConstraintKind::Unique });
745                }
746                Token::Keyword { keyword: Keyword::References, .. } => {
747                    self.advance();
748                    let ref_table = self.parse_table_name()?;
749                    // Column specification is optional in SQLite - defaults to PK
750                    let ref_column = if self.peek() == &Token::LParen {
751                        self.advance(); // consume LParen
752                        let col = self.parse_column_name()?;
753                        self.expect_token(Token::RParen)?;
754                        Some(col)
755                    } else {
756                        None
757                    };
758                    constraints.push(ColumnConstraint {
759                        name: None,
760                        kind: ColumnConstraintKind::References {
761                            table: ref_table,
762                            column: ref_column,
763                            on_delete: None,
764                            on_update: None,
765                        },
766                    });
767                }
768                _ => break,
769            }
770        }
771
772        Ok((nullable, constraints))
773    }
774
775    /// Parse a table-level constraint.
776    fn parse_table_constraint(&mut self) -> Result<TableConstraint<'arena>, ParseError> {
777        // Optional constraint name: CONSTRAINT name
778        let name = if self.try_consume_keyword(Keyword::Constraint) {
779            Some(self.parse_column_name()?)
780        } else {
781            None
782        };
783
784        let kind = match self.peek() {
785            Token::Keyword { keyword: Keyword::Primary, .. } => {
786                self.advance();
787                self.expect_keyword(Keyword::Key)?;
788                self.expect_token(Token::LParen)?;
789                let columns = self.parse_index_column_list()?;
790                self.expect_token(Token::RParen)?;
791                TableConstraintKind::PrimaryKey { columns }
792            }
793            Token::Keyword { keyword: Keyword::Unique, .. } => {
794                self.advance();
795                self.expect_token(Token::LParen)?;
796                let columns = self.parse_index_column_list()?;
797                self.expect_token(Token::RParen)?;
798                TableConstraintKind::Unique { columns }
799            }
800            Token::Keyword { keyword: Keyword::Foreign, .. } => {
801                self.advance();
802                self.expect_keyword(Keyword::Key)?;
803                self.expect_token(Token::LParen)?;
804                let columns = self.parse_column_name_list()?;
805                self.expect_token(Token::RParen)?;
806                self.expect_keyword(Keyword::References)?;
807                let references_table = self.parse_table_name()?;
808                self.expect_token(Token::LParen)?;
809                let references_columns = self.parse_column_name_list()?;
810                self.expect_token(Token::RParen)?;
811
812                let (on_delete, on_update) = self.parse_referential_actions()?;
813
814                TableConstraintKind::ForeignKey {
815                    columns,
816                    references_table,
817                    references_columns,
818                    on_delete,
819                    on_update,
820                }
821            }
822            Token::Keyword { keyword: Keyword::Check, .. } => {
823                self.advance();
824                self.expect_token(Token::LParen)?;
825                let expr = self.parse_expression()?;
826                self.expect_token(Token::RParen)?;
827                TableConstraintKind::Check { expr: self.arena.alloc(expr) }
828            }
829            _ => return Err(ParseError { message: self.peek().syntax_error() }),
830        };
831
832        Ok(TableConstraint { name, kind })
833    }
834
835    /// Parse index column list for constraints.
836    fn parse_index_column_list(
837        &mut self,
838    ) -> Result<BumpVec<'arena, IndexColumn<'arena>>, ParseError> {
839        let mut columns = BumpVec::new_in(self.arena);
840
841        loop {
842            let column_name = self.parse_column_name()?;
843
844            // Optional prefix length for prefix indexes
845            let prefix_length = if self.try_consume(&Token::LParen) {
846                let len = match self.peek() {
847                    Token::Number(n) => {
848                        let len = n.parse::<u64>().ok();
849                        self.advance();
850                        len
851                    }
852                    _ => None,
853                };
854                self.expect_token(Token::RParen)?;
855                len
856            } else {
857                None
858            };
859
860            // Optional sort order
861            let direction = if self.try_consume_keyword(Keyword::Asc) {
862                OrderDirection::Asc
863            } else if self.try_consume_keyword(Keyword::Desc) {
864                OrderDirection::Desc
865            } else {
866                OrderDirection::Asc
867            };
868
869            columns.push(IndexColumn::Column { column_name, direction, prefix_length });
870
871            if !self.try_consume(&Token::Comma) {
872                break;
873            }
874        }
875
876        Ok(columns)
877    }
878
879    /// Parse column name list.
880    fn parse_column_name_list(&mut self) -> Result<BumpVec<'arena, Symbol>, ParseError> {
881        let mut columns = BumpVec::new_in(self.arena);
882
883        loop {
884            let col = self.parse_column_name()?;
885            columns.push(col);
886
887            if !self.try_consume(&Token::Comma) {
888                break;
889            }
890        }
891
892        Ok(columns)
893    }
894
895    /// Parse referential actions (ON DELETE, ON UPDATE).
896    fn parse_referential_actions(
897        &mut self,
898    ) -> Result<(Option<ReferentialAction>, Option<ReferentialAction>), ParseError> {
899        let mut on_delete = None;
900        let mut on_update = None;
901
902        for _ in 0..2 {
903            if self.try_consume_keyword(Keyword::On) {
904                if self.try_consume_keyword(Keyword::Delete) {
905                    on_delete = Some(self.parse_referential_action()?);
906                } else if self.try_consume_keyword(Keyword::Update) {
907                    on_update = Some(self.parse_referential_action()?);
908                }
909            }
910        }
911
912        Ok((on_delete, on_update))
913    }
914
915    /// Parse a single referential action.
916    fn parse_referential_action(&mut self) -> Result<ReferentialAction, ParseError> {
917        if self.try_consume_keyword(Keyword::Cascade) {
918            Ok(ReferentialAction::Cascade)
919        } else if self.try_consume_keyword(Keyword::Restrict) {
920            Ok(ReferentialAction::Restrict)
921        } else if self.try_consume_keyword(Keyword::Set) {
922            if self.try_consume_keyword(Keyword::Null) {
923                Ok(ReferentialAction::SetNull)
924            } else if self.try_consume_keyword(Keyword::Default) {
925                Ok(ReferentialAction::SetDefault)
926            } else {
927                Err(ParseError { message: "Expected NULL or DEFAULT after SET".to_string() })
928            }
929        } else if self.try_consume_keyword(Keyword::No) {
930            self.expect_keyword(Keyword::Action)?;
931            Ok(ReferentialAction::NoAction)
932        } else {
933            Err(ParseError {
934                message: "Expected CASCADE, RESTRICT, SET NULL, SET DEFAULT, or NO ACTION"
935                    .to_string(),
936            })
937        }
938    }
939
940    // ========================================================================
941    // ANALYZE Statement
942    // ========================================================================
943
944    /// Parse ANALYZE statement.
945    pub(crate) fn parse_analyze_statement(&mut self) -> Result<AnalyzeStmt<'arena>, ParseError> {
946        self.consume_keyword(Keyword::Analyze)?;
947
948        // Parse optional table name
949        let table_name = if let Token::Identifier(_) = self.peek() {
950            Some(self.parse_arena_identifier()?)
951        } else {
952            None
953        };
954
955        // Parse optional column list
956        let columns = if table_name.is_some() && self.try_consume(&Token::LParen) {
957            let cols = self.parse_identifier_list()?;
958            self.expect_token(Token::RParen)?;
959            Some(cols)
960        } else {
961            None
962        };
963
964        Ok(AnalyzeStmt { table_name, columns })
965    }
966
967    // ========================================================================
968    // Helper methods
969    // ========================================================================
970
971    /// Parse index column specifications.
972    fn parse_index_columns(&mut self) -> Result<BumpVec<'arena, IndexColumn<'arena>>, ParseError> {
973        let mut columns = BumpVec::new_in(self.arena);
974        loop {
975            let column_name = self.parse_arena_identifier()?;
976
977            // Parse optional prefix length (e.g., name(10))
978            let prefix_length = if self.try_consume(&Token::LParen) {
979                let len = match self.peek() {
980                    Token::Number(n) => n
981                        .parse::<u64>()
982                        .map_err(|_| ParseError { message: "Invalid prefix length".to_string() })?,
983                    _ => {
984                        return Err(ParseError {
985                            message: "Expected number for prefix length".to_string(),
986                        })
987                    }
988                };
989                self.advance();
990                self.expect_token(Token::RParen)?;
991                Some(len)
992            } else {
993                None
994            };
995
996            // Parse optional direction
997            let direction = if self.try_consume_keyword(Keyword::Desc) {
998                OrderDirection::Desc
999            } else {
1000                self.try_consume_keyword(Keyword::Asc);
1001                OrderDirection::Asc
1002            };
1003
1004            columns.push(IndexColumn::Column { column_name, direction, prefix_length });
1005
1006            if !self.try_consume(&Token::Comma) {
1007                break;
1008            }
1009        }
1010        Ok(columns)
1011    }
1012
1013    // ========================================================================
1014    // PRAGMA Statement
1015    // ========================================================================
1016
1017    /// Parse PRAGMA statement (SQLite compatibility).
1018    ///
1019    /// Syntax:
1020    /// - PRAGMA pragma_name;
1021    /// - PRAGMA pragma_name = value;
1022    /// - PRAGMA pragma_name(value);
1023    /// - PRAGMA database.pragma_name;
1024    pub(crate) fn parse_pragma_statement(&mut self) -> Result<PragmaStmt, ParseError> {
1025        self.consume_keyword(Keyword::Pragma)?;
1026
1027        // Parse first identifier (could be database or pragma name)
1028        let first_ident = self.parse_arena_identifier()?;
1029
1030        // Check for dot (database.pragma_name syntax)
1031        let (database, name) = if self.try_consume(&Token::Symbol('.')) {
1032            let pragma_name = self.parse_arena_identifier()?;
1033            (Some(first_ident), pragma_name)
1034        } else {
1035            (None, first_ident)
1036        };
1037
1038        // Check for optional value assignment or function-style argument
1039        let value = if self.try_consume(&Token::Symbol('=')) {
1040            Some(self.parse_pragma_value()?)
1041        } else if self.try_consume(&Token::LParen) {
1042            let val = self.parse_pragma_value()?;
1043            self.expect_token(Token::RParen)?;
1044            Some(val)
1045        } else {
1046            None
1047        };
1048
1049        Ok(PragmaStmt { database, name, value })
1050    }
1051
1052    /// Parse a PRAGMA value.
1053    fn parse_pragma_value(&mut self) -> Result<PragmaValue, ParseError> {
1054        match self.peek().clone() {
1055            Token::Identifier(ident) => {
1056                self.advance();
1057                let sym = self.interner.intern(&ident);
1058                Ok(PragmaValue::Identifier(sym))
1059            }
1060            Token::Keyword { keyword: kw, .. } => {
1061                self.advance();
1062                let sym = self.interner.intern(&kw.to_string());
1063                Ok(PragmaValue::Identifier(sym))
1064            }
1065            Token::String(s) => {
1066                self.advance();
1067                let sym = self.interner.intern(&s);
1068                Ok(PragmaValue::String(sym))
1069            }
1070            Token::Number(n) => {
1071                self.advance();
1072                let sym = self.interner.intern(&n);
1073                Ok(PragmaValue::Number(sym))
1074            }
1075            Token::Symbol('-') => {
1076                self.advance(); // consume '-'
1077                match self.peek().clone() {
1078                    Token::Number(n) => {
1079                        self.advance();
1080                        let sym = self.interner.intern(&format!("-{}", n));
1081                        Ok(PragmaValue::SignedNumber(sym))
1082                    }
1083                    _ => Err(ParseError {
1084                        message: "Expected number after minus sign in PRAGMA value".to_string(),
1085                    }),
1086                }
1087            }
1088            Token::Symbol('+') => {
1089                self.advance(); // consume '+'
1090                match self.peek().clone() {
1091                    Token::Number(n) => {
1092                        self.advance();
1093                        let sym = self.interner.intern(&n);
1094                        Ok(PragmaValue::Number(sym))
1095                    }
1096                    _ => Err(ParseError {
1097                        message: "Expected number after plus sign in PRAGMA value".to_string(),
1098                    }),
1099                }
1100            }
1101            _ => Err(ParseError { message: self.peek().syntax_error() }),
1102        }
1103    }
1104}