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