Skip to content

Commit 98560cd

Browse files
authored
Merge pull request #23 from Embucket/snowflake-create-file-format
Snowflake: parse CREATE FILE FORMAT
2 parents cc281ff + 176c57b commit 98560cd

4 files changed

Lines changed: 131 additions & 0 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4511,6 +4511,24 @@ pub enum Statement {
45114511
comment: Option<String>,
45124512
},
45134513
/// ```sql
4514+
/// CREATE FILE FORMAT
4515+
/// ```
4516+
/// See <https://docs.snowflake.com/en/sql-reference/sql/create-file-format>
4517+
CreateFileFormat {
4518+
/// `OR REPLACE` flag.
4519+
or_replace: bool,
4520+
/// Whether the file format is temporary or volatile.
4521+
temporary: bool,
4522+
/// `IF NOT EXISTS` flag.
4523+
if_not_exists: bool,
4524+
/// File format name.
4525+
name: ObjectName,
4526+
/// Format type and format-specific options.
4527+
options: KeyValueOptions,
4528+
/// Optional comment.
4529+
comment: Option<String>,
4530+
},
4531+
/// ```sql
45144532
/// ASSERT <condition> [AS <message>]
45154533
/// ```
45164534
Assert {
@@ -6228,6 +6246,29 @@ impl fmt::Display for Statement {
62286246
}
62296247
Ok(())
62306248
}
6249+
Statement::CreateFileFormat {
6250+
or_replace,
6251+
temporary,
6252+
if_not_exists,
6253+
name,
6254+
options,
6255+
comment,
6256+
} => {
6257+
write!(
6258+
f,
6259+
"CREATE {or_replace}{temporary}FILE FORMAT {if_not_exists}{name}",
6260+
or_replace = if *or_replace { "OR REPLACE " } else { "" },
6261+
temporary = if *temporary { "TEMPORARY " } else { "" },
6262+
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
6263+
)?;
6264+
if !options.options.is_empty() {
6265+
write!(f, " {options}")?;
6266+
}
6267+
if let Some(comment) = comment {
6268+
write!(f, " COMMENT='{comment}'")?;
6269+
}
6270+
Ok(())
6271+
}
62316272
Statement::CopyIntoSnowflake {
62326273
kind,
62336274
into,

‎src/ast/spans.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ impl Spanned for Statement {
459459
Statement::CreateProcedure { .. } => Span::empty(),
460460
Statement::CreateMacro { .. } => Span::empty(),
461461
Statement::CreateStage { .. } => Span::empty(),
462+
Statement::CreateFileFormat { .. } => Span::empty(),
462463
Statement::Assert { .. } => Span::empty(),
463464
Statement::Grant { .. } => Span::empty(),
464465
Statement::Deny { .. } => Span::empty(),

‎src/dialect/snowflake.rs‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,12 @@ impl Dialect for SnowflakeDialect {
328328
if parser.parse_keyword(Keyword::STAGE) {
329329
// OK - this is CREATE STAGE statement
330330
return Some(parse_create_stage(or_replace, temporary, parser));
331+
} else if parser.parse_keywords(&[Keyword::FILE, Keyword::FORMAT]) {
332+
return Some(parse_create_file_format(
333+
or_replace,
334+
temporary || volatile,
335+
parser,
336+
));
331337
} else if parser.parse_keyword(Keyword::TABLE) {
332338
return Some(
333339
parse_create_table(
@@ -1426,6 +1432,31 @@ pub fn parse_create_stage(
14261432
})
14271433
}
14281434

1435+
pub fn parse_create_file_format(
1436+
or_replace: bool,
1437+
temporary: bool,
1438+
parser: &mut Parser,
1439+
) -> Result<Statement, ParserError> {
1440+
let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
1441+
let name = parser.parse_object_name(false)?;
1442+
let options = parser.parse_key_value_options(false, &[Keyword::COMMENT])?;
1443+
let comment = if parser.parse_keyword(Keyword::COMMENT) {
1444+
parser.expect_token(&Token::Eq)?;
1445+
Some(parser.parse_comment_value()?)
1446+
} else {
1447+
None
1448+
};
1449+
1450+
Ok(Statement::CreateFileFormat {
1451+
or_replace,
1452+
temporary,
1453+
if_not_exists,
1454+
name,
1455+
options,
1456+
comment,
1457+
})
1458+
}
1459+
14291460
pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserError> {
14301461
let mut ident = String::new();
14311462
while let Some(next_token) = parser.next_token_no_skip() {

‎tests/sqlparser_snowflake.rs‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,64 @@ fn test_create_stage() {
21492149
);
21502150
}
21512151

2152+
#[test]
2153+
fn test_create_file_format() {
2154+
let sql = "CREATE FILE FORMAT analytics.formats.parquet TYPE=PARQUET";
2155+
match snowflake().verified_stmt(sql) {
2156+
Statement::CreateFileFormat {
2157+
or_replace,
2158+
temporary,
2159+
if_not_exists,
2160+
name,
2161+
options,
2162+
comment,
2163+
} => {
2164+
assert!(!or_replace);
2165+
assert!(!temporary);
2166+
assert!(!if_not_exists);
2167+
assert_eq!(name.to_string(), "analytics.formats.parquet");
2168+
assert_eq!(options.options.len(), 1);
2169+
assert!(comment.is_none());
2170+
}
2171+
_ => unreachable!(),
2172+
}
2173+
assert_eq!(snowflake().verified_stmt(sql).to_string(), sql);
2174+
2175+
let sql = "CREATE FILE FORMAT IF NOT EXISTS existing_format TYPE=JSON";
2176+
match snowflake().verified_stmt(sql) {
2177+
Statement::CreateFileFormat { if_not_exists, .. } => assert!(if_not_exists),
2178+
_ => unreachable!(),
2179+
}
2180+
2181+
let sql = concat!(
2182+
"CREATE OR REPLACE TEMPORARY FILE FORMAT csv_format ",
2183+
"TYPE=CSV FIELD_DELIMITER='|' NULL_IF=('NULL', 'null') ",
2184+
"COMMENT='pipe-delimited input'"
2185+
);
2186+
match snowflake().verified_stmt(sql) {
2187+
Statement::CreateFileFormat {
2188+
or_replace,
2189+
temporary,
2190+
if_not_exists,
2191+
name,
2192+
options,
2193+
comment,
2194+
} => {
2195+
assert!(or_replace);
2196+
assert!(temporary);
2197+
assert!(!if_not_exists);
2198+
assert_eq!(name.to_string(), "csv_format");
2199+
assert_eq!(options.options.len(), 3);
2200+
assert_eq!(comment.as_deref(), Some("pipe-delimited input"));
2201+
}
2202+
_ => unreachable!(),
2203+
}
2204+
assert_eq!(snowflake().verified_stmt(sql).to_string(), sql);
2205+
2206+
let sql = "CREATE VOLATILE FILE FORMAT json_format TYPE=JSON";
2207+
snowflake().one_statement_parses_to(sql, "CREATE TEMPORARY FILE FORMAT json_format TYPE=JSON");
2208+
}
2209+
21522210
#[test]
21532211
fn test_create_stage_with_stage_params() {
21542212
let sql = concat!(

0 commit comments

Comments
 (0)