Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we convert this into a dialect method?

Actually looking at the code this feature seems to be solving a similar problem to is_column_type_sqlite_unspecified - likely we can instead have that parse_column_def function optionally accept a datatype if this method is true (and can rename the method accordingly)

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();
Expand Down Expand Up @@ -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<ColumnDef>, Vec<TableConstraint>), 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<ProcedureParam, ParserError> {
let mode = if self.parse_keyword(Keyword::IN) {
Expand Down
12 changes: 12 additions & 0 deletions tests/sqlparser_redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Comment on lines +511 to +512
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test/PR isn't related to temporary tables? if so it would probably be better to remove the temp qualifiers and we can use verified_stmt as usual?

);
}
Loading