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