diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6adecb0c6..b34d3fc44 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8374,7 +8374,15 @@ impl<'a> Parser<'a> { }; // parse optional column list (schema) - let (columns, constraints) = self.parse_columns()?; + // Redshift CTAS allows column names without types: + // CREATE TABLE t (col1, col2) AS SELECT 1, 2 + // Detect this by peeking for `( ident ,` or `( ident )` patterns. + let (columns, constraints) = + if dialect_of!(self is RedshiftSqlDialect) && self.peek_column_names_only() { + self.parse_columns_without_types()? + } else { + self.parse_columns()? + }; let comment_after_column_def = if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::COMMENT) { let next_token = self.next_token(); @@ -8993,6 +9001,42 @@ impl<'a> Parser<'a> { Ok((columns, constraints)) } + /// Returns true if the token stream looks like a parenthesized list of + /// bare column names (no types), e.g. `(col1, col2)`. + fn peek_column_names_only(&self) -> bool { + if self.peek_token_ref().token != Token::LParen { + return false; + } + matches!( + ( + &self.peek_nth_token_ref(1).token, + &self.peek_nth_token_ref(2).token + ), + (Token::Word(_), Token::Comma | Token::RParen) + ) + } + + /// Parse a parenthesized list of column names without data types, + /// used for Redshift CTAS: `CREATE TABLE t (c1, c2) AS SELECT ...` + fn parse_columns_without_types( + &mut self, + ) -> Result<(Vec, Vec), ParserError> { + if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) { + return Ok((vec![], vec![])); + } + let column_names = self.parse_comma_separated(|p| p.parse_identifier())?; + self.expect_token(&Token::RParen)?; + let columns = column_names + .into_iter() + .map(|name| ColumnDef { + name, + data_type: DataType::Unspecified, + options: vec![], + }) + .collect(); + Ok((columns, vec![])) + } + /// Parse procedure parameter. pub fn parse_procedure_param(&mut self) -> Result { let mode = if self.parse_keyword(Keyword::IN) { diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 184aa5b69..a394bb3a4 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -500,3 +500,15 @@ fn test_alter_table_alter_sortkey() { redshift().verified_stmt("ALTER TABLE users ALTER SORTKEY(created_at)"); redshift().verified_stmt("ALTER TABLE users ALTER SORTKEY(c1, c2)"); } + +#[test] +fn test_create_table_as_with_column_names() { + redshift().verified_stmt( + "CREATE TEMPORARY TABLE volt_tt (userid, days_played_in_last_31) AS SELECT 1, 2", + ); + // TEMP is an alias for TEMPORARY + redshift().one_statement_parses_to( + "CREATE TEMP TABLE volt_tt(userid, days_played_in_last_31) AS SELECT 1, 2", + "CREATE TEMPORARY TABLE volt_tt (userid, days_played_in_last_31) AS SELECT 1, 2", + ); +}