From 4849db3335bf8d3767d5d452524547812d73d307 Mon Sep 17 00:00:00 2001 From: TcMits Date: Mon, 11 Aug 2025 16:17:26 +0700 Subject: [PATCH] finish CREATE TABLE --- parser/src/parser.rs | 575 +++++++++++++++++++++++++++++++++++++++---- parser/src/token.rs | 11 +- 2 files changed, 529 insertions(+), 57 deletions(-) diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 52b388b5d..3a0a273e4 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -3,10 +3,11 @@ use crate::ast::{ CompoundSelect, CreateTableBody, DeferSubclause, Distinctness, Expr, ForeignKeyClause, FrameBound, FrameClause, FrameExclude, FrameMode, FromClause, FunctionTail, GroupBy, Indexed, IndexedColumn, InitDeferredPred, JoinConstraint, JoinOperator, JoinType, JoinedSelectTable, - LikeOperator, Limit, Literal, Materialized, Name, NamedColumnConstraint, NullsOrder, OneSelect, - Operator, Over, PragmaBody, PragmaValue, QualifiedName, RefAct, RefArg, ResolveType, - ResultColumn, Select, SelectBody, SelectTable, SortOrder, SortedColumn, Stmt, TransactionType, - Type, TypeSize, UnaryOperator, Window, WindowDef, With, + LikeOperator, Limit, Literal, Materialized, Name, NamedColumnConstraint, NamedTableConstraint, + NullsOrder, OneSelect, Operator, Over, PragmaBody, PragmaValue, QualifiedName, RefAct, RefArg, + ResolveType, ResultColumn, Select, SelectBody, SelectTable, SortOrder, SortedColumn, Stmt, + TableConstraint, TableOptions, TransactionType, Type, TypeSize, UnaryOperator, Window, + WindowDef, With, }; use crate::error::Error; use crate::lexer::{Lexer, Token}; @@ -199,6 +200,7 @@ impl<'a> Parser<'a> { | TokenType::TK_EXCEPT | TokenType::TK_INTERSECT | TokenType::TK_GENERATED + | TokenType::TK_WITHOUT | TokenType::TK_COLUMNKW | TokenType::TK_WINDOW | TokenType::TK_FILTER @@ -258,6 +260,11 @@ impl<'a> Parser<'a> { ** ** * the next token is TK_ALWAYS. ** * the token after than one is TK_AS. + ** + ** WITHOUT is a keyword if: + ** + ** * the previous token is TK_RP|TK_COMMA. + ** * the next token is TK_ID. */ match tok.token_type.unwrap() { TokenType::TK_WINDOW => { @@ -393,6 +400,25 @@ impl<'a> Parser<'a> { tok.token_type = Some(TokenType::TK_ID); } } + TokenType::TK_WITHOUT => { + let prev_tt = self.current_token.token_type.unwrap_or(TokenType::TK_EOF); + let can_be_without = match prev_tt { + TokenType::TK_RP | TokenType::TK_COMMA => self.try_parse(|p| { + match p.consume_lexer_without_whitespaces_or_comments() { + None => return Ok(false), + Some(tok) => match get_token(tok?.token_type.unwrap()) { + TokenType::TK_ID => Ok(true), + _ => Ok(false), + }, + } + })?, + _ => false, + }; + + if !can_be_without { + tok.token_type = Some(TokenType::TK_ID); + } + } _ => {} } } @@ -709,6 +735,7 @@ impl<'a> Parser<'a> { ])?; let mut temp = false; if first_tok.token_type == Some(TokenType::TK_TEMP) { + self.eat_assert(&[TokenType::TK_TEMP]); temp = true; first_tok = self.peek_expect(&[ TokenType::TK_TABLE, @@ -1818,6 +1845,18 @@ impl<'a> Parser<'a> { } } + fn parse_eid(&mut self) -> Result { + self.peek_nm()?; + let nm = self.parse_nm(); + let collate = self.parse_collate()?; + let sort_order = self.parse_sort_order()?; + Ok(IndexedColumn { + col_name: nm, + collation_name: collate, + order: sort_order, + }) + } + fn parse_eid_list(&mut self) -> Result, Error> { if let Some(tok) = self.peek()? { if tok.token_type == Some(TokenType::TK_LP) { @@ -1829,28 +1868,17 @@ impl<'a> Parser<'a> { return Ok(vec![]); } - let mut columns = vec![]; + let mut columns = vec![self.parse_eid()?]; loop { - if self.peek_no_eof()?.token_type == Some(TokenType::TK_RP) { - self.eat_assert(&[TokenType::TK_RP]); - break; - } - - self.peek_nm()?; - let nm = self.parse_nm(); - let collate = self.parse_collate()?; - let sort_order = self.parse_sort_order()?; - columns.push(IndexedColumn { - col_name: nm, - collation_name: collate, - order: sort_order, - }); - - let tok = self.eat_expect(&[TokenType::TK_COMMA, TokenType::TK_RP])?; - if tok.token_type == Some(TokenType::TK_RP) { - break; + match self.peek()? { + Some(tok) if tok.token_type == Some(TokenType::TK_COMMA) => { + self.eat_assert(&[TokenType::TK_COMMA]); + columns.push(self.parse_eid()?); + } + _ => break, } } + self.eat_expect(&[TokenType::TK_RP])?; Ok(columns) } @@ -2540,11 +2568,207 @@ impl<'a> Parser<'a> { self.parse_select_without_cte(with) } + fn parse_primary_table_constraint(&mut self) -> Result { + self.eat_assert(&[TokenType::TK_PRIMARY]); + self.eat_expect(&[TokenType::TK_KEY])?; + self.eat_expect(&[TokenType::TK_LP])?; + let columns = self.parse_sort_list()?; + let auto_increment = self.parse_auto_increment()?; + self.eat_expect(&[TokenType::TK_RP])?; + let conflict_clause = self.parse_on_conflict()?; + Ok(TableConstraint::PrimaryKey { + columns, + auto_increment, + conflict_clause, + }) + } + + fn parse_unique_table_constraint(&mut self) -> Result { + self.eat_assert(&[TokenType::TK_UNIQUE]); + self.eat_expect(&[TokenType::TK_LP])?; + let columns = self.parse_sort_list()?; + self.eat_expect(&[TokenType::TK_RP])?; + let conflict_clause = self.parse_on_conflict()?; + Ok(TableConstraint::Unique { + columns, + conflict_clause, + }) + } + + fn parse_check_table_constraint(&mut self) -> Result { + self.eat_assert(&[TokenType::TK_CHECK]); + self.eat_expect(&[TokenType::TK_LP])?; + let expr = self.parse_expr(0)?; + self.eat_expect(&[TokenType::TK_RP])?; + Ok(TableConstraint::Check(expr)) + } + + fn parse_foreign_key_table_constraint(&mut self) -> Result { + self.eat_assert(&[TokenType::TK_FOREIGN]); + self.eat_expect(&[TokenType::TK_KEY])?; + self.peek_expect(&[TokenType::TK_LP])?; // make sure we have columns + let columns = self.parse_eid_list()?; + self.peek_expect(&[TokenType::TK_REFERENCES])?; + let clause = self.parse_foreign_key_clause()?; + let deref_clause = self.parse_defer_subclause()?; + Ok(TableConstraint::ForeignKey { + columns, + clause, + deref_clause, + }) + } + + fn parse_named_table_constraints(&mut self) -> Result, Error> { + let mut result = vec![]; + + loop { + match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TokenType::TK_COMMA => { + self.eat_assert(&[TokenType::TK_COMMA]); + } + TokenType::TK_CONSTRAINT + | TokenType::TK_PRIMARY + | TokenType::TK_UNIQUE + | TokenType::TK_CHECK + | TokenType::TK_FOREIGN => {} + _ => break, + }, + _ => break, + } + + let name = match self.peek_no_eof()?.token_type.unwrap() { + TokenType::TK_CONSTRAINT => { + self.eat_assert(&[TokenType::TK_CONSTRAINT]); + self.peek_nm()?; + Some(self.parse_nm()) + } + _ => None, + }; + + let tok = self.peek_expect(&[ + TokenType::TK_PRIMARY, + TokenType::TK_UNIQUE, + TokenType::TK_CHECK, + TokenType::TK_FOREIGN, + ])?; + + match tok.token_type.unwrap() { + TokenType::TK_PRIMARY => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_primary_table_constraint()?, + }); + } + TokenType::TK_UNIQUE => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_unique_table_constraint()?, + }); + } + TokenType::TK_CHECK => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_check_table_constraint()?, + }); + } + TokenType::TK_FOREIGN => { + result.push(NamedTableConstraint { + name, + constraint: self.parse_foreign_key_table_constraint()?, + }); + } + _ => unreachable!(), + } + } + + Ok(result) + } + + fn parse_table_option(&mut self) -> Result { + match self.peek()? { + Some(tok) => match tok.token_type.unwrap().fallback_id_if_ok() { + TokenType::TK_WITHOUT => { + self.eat_assert(&[TokenType::TK_WITHOUT]); + let tok = self.eat_expect(&[TokenType::TK_ID])?; + if b"ROWID".eq_ignore_ascii_case(tok.value) { + Ok(TableOptions::WITHOUT_ROWID) + } else { + Err(Error::Custom(format!( + "unknown table option: {}", + from_bytes(tok.value) + ))) + } + } + TokenType::TK_ID => { + let tok = self.eat_assert(&[TokenType::TK_ID]); + if b"STRICT".eq_ignore_ascii_case(tok.value) { + Ok(TableOptions::STRICT) + } else { + Err(Error::Custom(format!( + "unknown table option: {}", + from_bytes(tok.value) + ))) + } + } + _ => Ok(TableOptions::NONE), + }, + _ => Ok(TableOptions::NONE), + } + } + + fn parse_table_options(&mut self) -> Result { + let mut result = self.parse_table_option()?; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TokenType::TK_COMMA) => { + self.eat_assert(&[TokenType::TK_COMMA]); + result = result | self.parse_table_option()?; + } + _ => break, + } + } + + Ok(result) + } + fn parse_create_table_args(&mut self) -> Result { let tok = self.eat_expect(&[TokenType::TK_LP, TokenType::TK_AS])?; match tok.token_type.unwrap() { TokenType::TK_AS => Ok(CreateTableBody::AsSelect(self.parse_select()?)), - TokenType::TK_LP => todo!(), + TokenType::TK_LP => { + let mut columns = vec![self.parse_column_definition()?]; + let mut constraints = vec![]; + loop { + match self.peek()? { + Some(tok) if tok.token_type == Some(TokenType::TK_COMMA) => { + self.eat_assert(&[TokenType::TK_COMMA]); + match self.peek_no_eof()?.token_type.unwrap() { + TokenType::TK_CONSTRAINT + | TokenType::TK_PRIMARY + | TokenType::TK_UNIQUE + | TokenType::TK_CHECK + | TokenType::TK_FOREIGN => { + constraints = self.parse_named_table_constraints()?; + break; + } + _ => { + columns.push(self.parse_column_definition()?); + } + } + } + _ => break, + } + } + + self.eat_expect(&[TokenType::TK_RP])?; + let options = self.parse_table_options()?; + Ok(CreateTableBody::ColumnsAndConstraints { + columns, + constraints, + options, + }) + } _ => unreachable!(), } } @@ -2997,6 +3221,21 @@ impl<'a> Parser<'a> { _ => None, }; + if name.is_some() { + self.peek_expect(&[ + TokenType::TK_DEFAULT, + TokenType::TK_NOT, + TokenType::TK_NULL, + TokenType::TK_PRIMARY, + TokenType::TK_UNIQUE, + TokenType::TK_CHECK, + TokenType::TK_REFERENCES, + TokenType::TK_COLLATE, + TokenType::TK_GENERATED, + TokenType::TK_AS, + ])?; + } + match self.peek()? { Some(tok) => match tok.token_type.unwrap() { TokenType::TK_DEFAULT => { @@ -3049,32 +3288,27 @@ impl<'a> Parser<'a> { constraint: self.parse_generated_column_constraint()?, }); } - _ => { - if name.is_some() { - return Err(Error::Custom( - "Expected a column constraint name after CONSTRAINT keyword" - .to_owned(), - )); - } - - break; - } + _ => break, }, - _ => { - if name.is_some() { - return Err(Error::Custom( - "Expected a column constraint name after CONSTRAINT keyword".to_owned(), - )); - } - - break; - } + _ => break, } } Ok(result) } + fn parse_column_definition(&mut self) -> Result { + self.peek_nm()?; + let col_name = self.parse_nm(); + let col_type = self.parse_type()?; + let constraints = self.parse_named_column_constraints()?; + Ok(ColumnDefinition { + col_name, + col_type, + constraints, + }) + } + fn parse_alter(&mut self) -> Result { self.eat_assert(&[TokenType::TK_ALTER]); self.eat_expect(&[TokenType::TK_TABLE])?; @@ -3091,17 +3325,9 @@ impl<'a> Parser<'a> { _ => {} } - self.peek_nm()?; - let col_name = self.parse_nm(); - let col_type = self.parse_type()?; - let constraints = self.parse_named_column_constraints()?; Ok(Stmt::AlterTable { name: tbl_name, - body: AlterTableBody::AddColumn(ColumnDefinition { - col_name, - col_type, - constraints, - }), + body: AlterTableBody::AddColumn(self.parse_column_definition()?), }) } TokenType::TK_DROP => { @@ -8976,6 +9202,251 @@ mod tests { )), })], ), + // parse create table + ( + b"CREATE TABLE foo (column)".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: false, + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("column".to_owned()), + col_type: None, + constraints: vec![], + }, + ], + constraints: vec![], + options: TableOptions::NONE, + }, + })], + ), + ( + b"CREATE TABLE foo AS SELECT 1".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: false, + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::AsSelect(Select { + with: None, + body: SelectBody { + select: OneSelect::Select { + distinctness: None, + columns: vec![ + ResultColumn::Expr(Box::new(Expr::Literal(Literal::Numeric("1".to_owned()))), None) + ], + from: None, + where_clause: None, + group_by: None, + window_clause: vec![], + }, + compounds: vec![] + }, + order_by: vec![], + limit: None, + }), + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, CONSTRAINT tbl_cons PRIMARY KEY (bar AUTOINCREMENT) OR ROLLBACK) STRICT".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: Some(Name::Ident("tbl_cons".to_owned())), + constraint: TableConstraint::PrimaryKey { + columns: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + }, + ], + auto_increment: true, + conflict_clause: Some(ResolveType::Rollback) + } + }, + ], + options: TableOptions::STRICT, + }, + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, UNIQUE (bar) OR ROLLBACK) WITHOUT ROWID".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: None, + constraint: TableConstraint::Unique { + columns: vec![ + SortedColumn { + expr: Box::new(Expr::Id(Name::Ident("bar".to_owned()))), + order: None, + nulls: None, + }, + ], + conflict_clause: Some(ResolveType::Rollback) + } + }, + ], + options: TableOptions::WITHOUT_ROWID, + }, + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, CHECK (1))".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: None, + constraint: TableConstraint::Check(Box::new( + Expr::Literal(Literal::Numeric("1".to_owned())) + )), + }, + ], + options: TableOptions::NONE, + }, + })], + ), + ( + b"CREATE TEMP TABLE IF NOT EXISTS foo (bar, baz INTEGER, FOREIGN KEY (bar) REFERENCES foo_2(bar_2), CHECK (1))".as_slice(), + vec![Cmd::Stmt(Stmt::CreateTable { + temporary: true, + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + body: CreateTableBody::ColumnsAndConstraints { + columns: vec![ + ColumnDefinition { + col_name: Name::Ident("bar".to_owned()), + col_type: None, + constraints: vec![], + }, + ColumnDefinition { + col_name: Name::Ident("baz".to_owned()), + col_type: Some(Type { + name: "INTEGER".to_owned(), + size: None, + }), + constraints: vec![], + }, + ], + constraints: vec![ + NamedTableConstraint { + name: None, + constraint: TableConstraint::ForeignKey { + columns: vec![ + IndexedColumn { + col_name: Name::Ident("bar".to_owned()), + collation_name: None, + order: None, + }, + ], + clause: ForeignKeyClause { + tbl_name: Name::Ident("foo_2".to_owned()), + columns: vec![ + IndexedColumn { + col_name: Name::Ident("bar_2".to_owned()), + collation_name: None, + order: None, + }, + ], + args: vec![], + }, + deref_clause: None, + }, + }, + NamedTableConstraint { + name: None, + constraint: TableConstraint::Check(Box::new( + Expr::Literal(Literal::Numeric("1".to_owned())) + )), + }, + ], + options: TableOptions::NONE, + }, + })], + ), ]; for (input, expected) in test_cases { diff --git a/parser/src/token.rs b/parser/src/token.rs index a20c06393..c1a43005b 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -371,11 +371,12 @@ impl TokenType { | TK_LIKE_KW | TK_MATCH | TK_NO | TK_PLAN | TK_QUERY | TK_KEY | TK_OF | TK_OFFSET | TK_PRAGMA | TK_RAISE | TK_RECURSIVE | TK_RELEASE | TK_REPLACE | TK_RESTRICT | TK_ROW | TK_ROWS | TK_ROLLBACK | TK_SAVEPOINT | TK_TEMP | TK_TRIGGER | TK_VACUUM - | TK_VIEW | TK_VIRTUAL | TK_WITH | TK_WITHOUT | TK_NULLS | TK_FIRST | TK_LAST - | TK_CURRENT | TK_FOLLOWING | TK_PARTITION | TK_PRECEDING | TK_RANGE | TK_UNBOUNDED - | TK_EXCLUDE | TK_GROUPS | TK_OTHERS | TK_TIES | TK_ALWAYS | TK_MATERIALIZED - | TK_REINDEX | TK_RENAME | TK_CTIME_KW | TK_IF => TK_ID, - // | TK_COLUMNKW | TK_UNION | TK_EXCEPT | TK_INTERSECT | TK_GENERATED see comments in `next_token` of parser + | TK_VIEW | TK_VIRTUAL | TK_WITH | TK_NULLS | TK_FIRST | TK_LAST | TK_CURRENT + | TK_FOLLOWING | TK_PARTITION | TK_PRECEDING | TK_RANGE | TK_UNBOUNDED | TK_EXCLUDE + | TK_GROUPS | TK_OTHERS | TK_TIES | TK_ALWAYS | TK_MATERIALIZED | TK_REINDEX + | TK_RENAME | TK_CTIME_KW | TK_IF => TK_ID, + // | TK_COLUMNKW | TK_UNION | TK_EXCEPT | TK_INTERSECT | TK_GENERATED | TK_WITHOUT + // see comments in `next_token` of parser _ => self, } }