mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-12-23 11:12:51 +00:00
Added the rest of alter external table spec
This commit is contained in:
parent
791a9aea2d
commit
568c8e5651
4 changed files with 190 additions and 4 deletions
|
|
@ -387,6 +387,34 @@ pub enum AlterTableOperation {
|
|||
column_name: Ident,
|
||||
data_type: DataType,
|
||||
},
|
||||
/// `ADD FILES ( '<path>' [, '<path>', ...] )`
|
||||
///
|
||||
/// Note: this is Snowflake specific for external tables <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
|
||||
AddFiles {
|
||||
files: Vec<String>,
|
||||
},
|
||||
/// `REMOVE FILES ( '<path>' [, '<path>', ...] )`
|
||||
///
|
||||
/// Note: this is Snowflake specific for external tables <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
|
||||
RemoveFiles {
|
||||
files: Vec<String>,
|
||||
},
|
||||
/// `ADD PARTITION ( <part_col_name> = '<string>' [, ...] ) LOCATION '<path>'`
|
||||
///
|
||||
/// Note: this is Snowflake specific for external tables <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
|
||||
AddPartition {
|
||||
/// Partition column values as key-value pairs
|
||||
partition: Vec<(Ident, String)>,
|
||||
/// Location path
|
||||
location: String,
|
||||
},
|
||||
/// `DROP PARTITION LOCATION '<path>'`
|
||||
///
|
||||
/// Note: this is Snowflake specific for external tables <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
|
||||
DropPartitionLocation {
|
||||
/// Location path
|
||||
location: String,
|
||||
},
|
||||
/// `SUSPEND`
|
||||
///
|
||||
/// Note: this is Snowflake specific for dynamic tables <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
|
||||
|
|
@ -898,6 +926,46 @@ impl fmt::Display for AlterTableOperation {
|
|||
} => {
|
||||
write!(f, "ADD PARTITION COLUMN {column_name} {data_type}")
|
||||
}
|
||||
AlterTableOperation::AddFiles { files } => {
|
||||
write!(
|
||||
f,
|
||||
"ADD FILES ({})",
|
||||
files
|
||||
.iter()
|
||||
.map(|f| format!("'{f}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
AlterTableOperation::RemoveFiles { files } => {
|
||||
write!(
|
||||
f,
|
||||
"REMOVE FILES ({})",
|
||||
files
|
||||
.iter()
|
||||
.map(|f| format!("'{f}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
AlterTableOperation::AddPartition {
|
||||
partition,
|
||||
location,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"ADD PARTITION ({}) LOCATION '{}'",
|
||||
partition
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k} = '{v}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
location
|
||||
)
|
||||
}
|
||||
AlterTableOperation::DropPartitionLocation { location } => {
|
||||
write!(f, "DROP PARTITION LOCATION '{location}'")
|
||||
}
|
||||
AlterTableOperation::Suspend => {
|
||||
write!(f, "SUSPEND")
|
||||
}
|
||||
|
|
@ -3953,13 +4021,28 @@ impl fmt::Display for AlterTable {
|
|||
None => write!(f, "ALTER TABLE ")?,
|
||||
}
|
||||
|
||||
if self.if_exists {
|
||||
// For external table ADD PARTITION / DROP PARTITION operations,
|
||||
// IF EXISTS comes after the table name per Snowflake syntax
|
||||
let if_exists_after_table_name = self.table_type == Some(AlterTableType::External)
|
||||
&& self.operations.iter().any(|op| {
|
||||
matches!(
|
||||
op,
|
||||
AlterTableOperation::AddPartition { .. }
|
||||
| AlterTableOperation::DropPartitionLocation { .. }
|
||||
)
|
||||
});
|
||||
|
||||
if self.if_exists && !if_exists_after_table_name {
|
||||
write!(f, "IF EXISTS ")?;
|
||||
}
|
||||
if self.only {
|
||||
write!(f, "ONLY ")?;
|
||||
}
|
||||
write!(f, "{} ", &self.name)?;
|
||||
write!(f, "{}", &self.name)?;
|
||||
if self.if_exists && if_exists_after_table_name {
|
||||
write!(f, " IF EXISTS")?;
|
||||
}
|
||||
write!(f, " ")?;
|
||||
if let Some(cluster) = &self.on_cluster {
|
||||
write!(f, "ON CLUSTER {cluster} ")?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1147,6 +1147,12 @@ impl Spanned for AlterTableOperation {
|
|||
AlterTableOperation::ResumeRecluster => Span::empty(),
|
||||
AlterTableOperation::Refresh { .. } => Span::empty(),
|
||||
AlterTableOperation::AddPartitionColumn { column_name, .. } => column_name.span,
|
||||
AlterTableOperation::AddFiles { .. } => Span::empty(),
|
||||
AlterTableOperation::RemoveFiles { .. } => Span::empty(),
|
||||
AlterTableOperation::AddPartition { partition, .. } => {
|
||||
union_spans(partition.iter().map(|(k, _)| k.span))
|
||||
}
|
||||
AlterTableOperation::DropPartitionLocation { .. } => Span::empty(),
|
||||
AlterTableOperation::Suspend => Span::empty(),
|
||||
AlterTableOperation::Resume => Span::empty(),
|
||||
AlterTableOperation::Algorithm { .. } => Span::empty(),
|
||||
|
|
|
|||
|
|
@ -657,9 +657,15 @@ fn parse_alter_dynamic_table(parser: &mut Parser) -> Result<Statement, ParserErr
|
|||
/// Parse snowflake alter external table.
|
||||
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
|
||||
fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserError> {
|
||||
let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
||||
// IF EXISTS can appear before the table name for most operations
|
||||
let mut if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
||||
let table_name = parser.parse_object_name(true)?;
|
||||
|
||||
// IF EXISTS can also appear after the table name for ADD/DROP PARTITION operations
|
||||
if !if_exists {
|
||||
if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
|
||||
}
|
||||
|
||||
// Parse the operation
|
||||
let operation = if parser.parse_keyword(Keyword::REFRESH) {
|
||||
// Optional subpath for refreshing specific partitions
|
||||
|
|
@ -683,6 +689,27 @@ fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserEr
|
|||
column_name,
|
||||
data_type,
|
||||
}
|
||||
} else if parser.parse_keywords(&[Keyword::ADD, Keyword::PARTITION]) {
|
||||
// ADD PARTITION ( <col> = '<val>' [, ...] ) LOCATION '<path>'
|
||||
let partition = parse_partition_key_values(parser)?;
|
||||
parser.expect_keyword(Keyword::LOCATION)?;
|
||||
let location = parse_single_quoted_string(parser)?;
|
||||
AlterTableOperation::AddPartition {
|
||||
partition,
|
||||
location,
|
||||
}
|
||||
} else if parser.parse_keywords(&[Keyword::DROP, Keyword::PARTITION, Keyword::LOCATION]) {
|
||||
// DROP PARTITION LOCATION '<path>'
|
||||
let location = parse_single_quoted_string(parser)?;
|
||||
AlterTableOperation::DropPartitionLocation { location }
|
||||
} else if parser.parse_keywords(&[Keyword::ADD, Keyword::FILES]) {
|
||||
// Parse ADD FILES ( '<path>' [, '<path>', ...] )
|
||||
let files = parse_parenthesized_file_list(parser)?;
|
||||
AlterTableOperation::AddFiles { files }
|
||||
} else if parser.parse_keywords(&[Keyword::REMOVE, Keyword::FILES]) {
|
||||
// Parse REMOVE FILES ( '<path>' [, '<path>', ...] )
|
||||
let files = parse_parenthesized_file_list(parser)?;
|
||||
AlterTableOperation::RemoveFiles { files }
|
||||
} else if parser.parse_keyword(Keyword::SET) {
|
||||
// Parse SET key = value options (e.g., SET AUTO_REFRESH = TRUE)
|
||||
let mut options = vec![];
|
||||
|
|
@ -698,7 +725,7 @@ fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserEr
|
|||
AlterTableOperation::SetOptions { options }
|
||||
} else {
|
||||
return parser.expected(
|
||||
"REFRESH, RENAME TO, ADD PARTITION COLUMN, or SET after ALTER EXTERNAL TABLE",
|
||||
"REFRESH, RENAME TO, ADD, DROP, or SET after ALTER EXTERNAL TABLE",
|
||||
parser.peek_token(),
|
||||
);
|
||||
};
|
||||
|
|
@ -721,6 +748,50 @@ fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserEr
|
|||
}))
|
||||
}
|
||||
|
||||
/// Parse a parenthesized list of single-quoted file paths.
|
||||
fn parse_parenthesized_file_list(parser: &mut Parser) -> Result<Vec<String>, ParserError> {
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let mut files = vec![];
|
||||
loop {
|
||||
match parser.next_token().token {
|
||||
Token::SingleQuotedString(s) => files.push(s),
|
||||
_ => {
|
||||
return parser.expected("a single-quoted string", parser.peek_token());
|
||||
}
|
||||
}
|
||||
if !parser.consume_token(&Token::Comma) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
/// Parse partition key-value pairs: ( <col> = '<val>' [, <col> = '<val>', ...] )
|
||||
fn parse_partition_key_values(parser: &mut Parser) -> Result<Vec<(Ident, String)>, ParserError> {
|
||||
parser.expect_token(&Token::LParen)?;
|
||||
let mut pairs = vec![];
|
||||
loop {
|
||||
let key = parser.parse_identifier()?;
|
||||
parser.expect_token(&Token::Eq)?;
|
||||
let value = parse_single_quoted_string(parser)?;
|
||||
pairs.push((key, value));
|
||||
if !parser.consume_token(&Token::Comma) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parser.expect_token(&Token::RParen)?;
|
||||
Ok(pairs)
|
||||
}
|
||||
|
||||
/// Parse a single-quoted string and return its content.
|
||||
fn parse_single_quoted_string(parser: &mut Parser) -> Result<String, ParserError> {
|
||||
match parser.next_token().token {
|
||||
Token::SingleQuotedString(s) => Ok(s),
|
||||
_ => parser.expected("a single-quoted string", parser.peek_token()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse snowflake alter session.
|
||||
/// <https://docs.snowflake.com/en/sql-reference/sql/alter-session>
|
||||
fn parse_alter_session(parser: &mut Parser, set: bool) -> Result<Statement, ParserError> {
|
||||
|
|
|
|||
|
|
@ -4644,4 +4644,30 @@ fn test_alter_external_table() {
|
|||
snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table RENAME TO new_table_name");
|
||||
snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table ADD PARTITION COLUMN column_name VARCHAR");
|
||||
snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table SET AUTO_REFRESH = true");
|
||||
snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table ADD FILES ('file1.parquet')");
|
||||
snowflake().verified_stmt(
|
||||
"ALTER EXTERNAL TABLE some_table ADD FILES ('path/file1.parquet', 'path/file2.parquet')",
|
||||
);
|
||||
snowflake().verified_stmt("ALTER EXTERNAL TABLE some_table REMOVE FILES ('file1.parquet')");
|
||||
snowflake().verified_stmt(
|
||||
"ALTER EXTERNAL TABLE some_table REMOVE FILES ('path/file1.parquet', 'path/file2.parquet')",
|
||||
);
|
||||
// ADD PARTITION with location
|
||||
snowflake()
|
||||
.verified_stmt("ALTER EXTERNAL TABLE some_table ADD PARTITION (year = '2024') LOCATION 's3://bucket/path/'");
|
||||
snowflake().verified_stmt(
|
||||
"ALTER EXTERNAL TABLE some_table ADD PARTITION (year = '2024', month = '12') LOCATION 's3://bucket/path/'",
|
||||
);
|
||||
// DROP PARTITION location
|
||||
snowflake()
|
||||
.verified_stmt("ALTER EXTERNAL TABLE some_table DROP PARTITION LOCATION 's3://bucket/path/'");
|
||||
// Test IF EXISTS (before table name for most operations)
|
||||
snowflake().verified_stmt("ALTER EXTERNAL TABLE IF EXISTS some_table REFRESH");
|
||||
// Test IF EXISTS (after table name for ADD/DROP PARTITION per Snowflake syntax)
|
||||
snowflake().verified_stmt(
|
||||
"ALTER EXTERNAL TABLE some_table IF EXISTS ADD PARTITION (year = '2024') LOCATION 's3://bucket/path/'",
|
||||
);
|
||||
snowflake().verified_stmt(
|
||||
"ALTER EXTERNAL TABLE some_table IF EXISTS DROP PARTITION LOCATION 's3://bucket/path/'",
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue