From c444b80d2e65ab7d2ec526a44d11536839952447 Mon Sep 17 00:00:00 2001 From: TcMits Date: Tue, 12 Aug 2025 19:14:10 +0700 Subject: [PATCH] finish CREATE VIRTUAL TABLE --- parser/src/lexer.rs | 2 +- parser/src/parser.rs | 149 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index e67453e6b..ee9f1f1df 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -25,7 +25,7 @@ pub struct Token<'a> { pub struct Lexer<'a> { pub(crate) offset: usize, - input: &'a [u8], + pub(crate) input: &'a [u8], } impl<'a> Iterator for Lexer<'a> { diff --git a/parser/src/parser.rs b/parser/src/parser.rs index e55136491..5716c465c 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -732,6 +732,93 @@ impl<'a> Parser<'a> { }) } + fn parse_vtab_arg(&mut self) -> Result { + let tok = self.peek_no_eof()?; + + // minus len() because lexer already consumed the token + let start_idx = self.lexer.offset - tok.value.len(); + + loop { + match self.peek_no_eof()?.token_type.unwrap() { + TokenType::TK_LP => { + let mut lp_count: usize = 0; + loop { + let tok = self.eat_no_eof()?; + match tok.token_type.unwrap() { + TokenType::TK_LP => { + lp_count += 1; + } + TokenType::TK_RP => { + lp_count -= 1; // FIXME: no need to check underflow + if lp_count == 0 { + break; + } + } + _ => {} + } + } + } + TokenType::TK_COMMA | TokenType::TK_RP => break, + _ => { + self.eat_no_eof()?; + } + } + } + + // minus 1 because lexer already consumed TK_COMMA or TK_RP + let end_idx = self.lexer.offset - 1; + if start_idx == end_idx { + return Err(Error::Custom("unexpected COMMA in vtab args".to_owned())); + } + + Ok(from_bytes(&self.lexer.input[start_idx..end_idx])) + } + + fn parse_create_virtual(&mut self) -> Result { + self.eat_assert(&[TokenType::TK_VIRTUAL]); + self.eat_expect(&[TokenType::TK_TABLE])?; + let if_not_exists = self.parse_if_not_exists()?; + let tbl_name = self.parse_fullname(false)?; + self.eat_expect(&[TokenType::TK_USING])?; + let module_name = self.parse_nm()?; + let args = match self.peek()? { + Some(tok) => match tok.token_type.unwrap() { + TokenType::TK_LP => { + self.eat_assert(&[TokenType::TK_LP]); + let mut result = vec![]; + loop { + match self.peek_no_eof()?.token_type.unwrap() { + TokenType::TK_RP => break, // handle empty args case + _ => {} + } + + result.push(self.parse_vtab_arg()?); + let tok = self.peek_expect(&[TokenType::TK_COMMA, TokenType::TK_RP])?; + match tok.token_type.unwrap() { + TokenType::TK_COMMA => { + self.eat_assert(&[TokenType::TK_COMMA]); + } + TokenType::TK_RP => break, + _ => unreachable!(), + } + } + + self.eat_assert(&[TokenType::TK_RP]); + result + } + _ => vec![], + }, + _ => vec![], + }; + + Ok(Stmt::CreateVirtualTable { + if_not_exists, + tbl_name, + module_name, + args, + }) + } + fn parse_create_stmt(&mut self) -> Result { self.eat_assert(&[TokenType::TK_CREATE]); let mut first_tok = self.peek_expect(&[ @@ -756,7 +843,7 @@ impl<'a> Parser<'a> { match first_tok.token_type.unwrap() { TokenType::TK_TABLE => self.parse_create_table(temp), - TokenType::TK_VIRTUAL => todo!(), + TokenType::TK_VIRTUAL => self.parse_create_virtual(), TokenType::TK_VIEW => self.parse_create_view(temp), TokenType::TK_INDEX | TokenType::TK_UNIQUE => self.parse_create_index(), TokenType::TK_TRIGGER => self.parse_create_trigger(temp), @@ -10507,6 +10594,66 @@ mod tests { }, })], ), + // parse CREATE VIRTUAL TABLE + ( + b"CREATE VIRTUAL TABLE foo USING bar".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + module_name: Name::Ident("bar".to_owned()), + args: vec![], + })], + ), + ( + b"CREATE VIRTUAL TABLE foo USING bar()".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + module_name: Name::Ident("bar".to_owned()), + args: vec![], + })], + ), + ( + b"CREATE VIRTUAL TABLE IF NOT EXISTS foo USING bar(1, 2, ('hello', (3.333), 'world', (1, 2)))".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: true, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("foo".to_owned()), + alias: None, + }, + module_name: Name::Ident("bar".to_owned()), + args: vec![ + "1".to_owned(), + "2".to_owned(), + "('hello', (3.333), 'world', (1, 2))".to_owned(), + ], + })], + ), + ( + b"CREATE VIRTUAL TABLE ft USING fts5(x, tokenize = '''porter'' ''ascii''')".as_slice(), + vec![Cmd::Stmt(Stmt::CreateVirtualTable { + if_not_exists: false, + tbl_name: QualifiedName { + db_name: None, + name: Name::Ident("ft".to_owned()), + alias: None, + }, + module_name: Name::Ident("fts5".to_owned()), + args: vec![ + "x".to_owned(), + "tokenize = '''porter'' ''ascii'''".to_owned(), + ], + })], + ), ]; for (input, expected) in test_cases {