Skip to content

Commit f4ed213

Browse files
committed
Fixed create table as with columns without types for redshift
1 parent b3e176d commit f4ed213

File tree

2 files changed

+57
-1
lines changed

2 files changed

+57
-1
lines changed

src/parser/mod.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8374,7 +8374,15 @@ impl<'a> Parser<'a> {
83748374
};
83758375

83768376
// parse optional column list (schema)
8377-
let (columns, constraints) = self.parse_columns()?;
8377+
// Redshift CTAS allows column names without types:
8378+
// CREATE TABLE t (col1, col2) AS SELECT 1, 2
8379+
// Detect this by peeking for `( ident ,` or `( ident )` patterns.
8380+
let (columns, constraints) =
8381+
if dialect_of!(self is RedshiftSqlDialect) && self.peek_column_names_only() {
8382+
self.parse_columns_without_types()?
8383+
} else {
8384+
self.parse_columns()?
8385+
};
83788386
let comment_after_column_def =
83798387
if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::COMMENT) {
83808388
let next_token = self.next_token();
@@ -8993,6 +9001,42 @@ impl<'a> Parser<'a> {
89939001
Ok((columns, constraints))
89949002
}
89959003

9004+
/// Returns true if the token stream looks like a parenthesized list of
9005+
/// bare column names (no types), e.g. `(col1, col2)`.
9006+
fn peek_column_names_only(&self) -> bool {
9007+
if self.peek_token_ref().token != Token::LParen {
9008+
return false;
9009+
}
9010+
matches!(
9011+
(
9012+
&self.peek_nth_token_ref(1).token,
9013+
&self.peek_nth_token_ref(2).token
9014+
),
9015+
(Token::Word(_), Token::Comma | Token::RParen)
9016+
)
9017+
}
9018+
9019+
/// Parse a parenthesized list of column names without data types,
9020+
/// used for Redshift CTAS: `CREATE TABLE t (c1, c2) AS SELECT ...`
9021+
fn parse_columns_without_types(
9022+
&mut self,
9023+
) -> Result<(Vec<ColumnDef>, Vec<TableConstraint>), ParserError> {
9024+
if !self.consume_token(&Token::LParen) || self.consume_token(&Token::RParen) {
9025+
return Ok((vec![], vec![]));
9026+
}
9027+
let column_names = self.parse_comma_separated(|p| p.parse_identifier())?;
9028+
self.expect_token(&Token::RParen)?;
9029+
let columns = column_names
9030+
.into_iter()
9031+
.map(|name| ColumnDef {
9032+
name,
9033+
data_type: DataType::Unspecified,
9034+
options: vec![],
9035+
})
9036+
.collect();
9037+
Ok((columns, vec![]))
9038+
}
9039+
89969040
/// Parse procedure parameter.
89979041
pub fn parse_procedure_param(&mut self) -> Result<ProcedureParam, ParserError> {
89989042
let mode = if self.parse_keyword(Keyword::IN) {

tests/sqlparser_redshift.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,15 @@ fn test_alter_table_alter_sortkey() {
500500
redshift().verified_stmt("ALTER TABLE users ALTER SORTKEY(created_at)");
501501
redshift().verified_stmt("ALTER TABLE users ALTER SORTKEY(c1, c2)");
502502
}
503+
504+
#[test]
505+
fn test_create_table_as_with_column_names() {
506+
redshift().verified_stmt(
507+
"CREATE TEMPORARY TABLE volt_tt (userid, days_played_in_last_31) AS SELECT 1, 2",
508+
);
509+
// TEMP is an alias for TEMPORARY
510+
redshift().one_statement_parses_to(
511+
"CREATE TEMP TABLE volt_tt(userid, days_played_in_last_31) AS SELECT 1, 2",
512+
"CREATE TEMPORARY TABLE volt_tt (userid, days_played_in_last_31) AS SELECT 1, 2",
513+
);
514+
}

0 commit comments

Comments
 (0)