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