From 8e65a5351c63e28cc092374fb1ed793bed88da33 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 11 Jun 2025 15:59:04 +0200 Subject: [PATCH 1/3] Allow `IF NOT EXISTS` after table name for Snowflake Although not documented under https://docs.snowflake.com/en/sql-reference/sql/create-table, it is valid to put the `IF NOT EXIST` _after_ the table name when creating a table for Snowflake. This adds support to check for `IF NOT EXIST` again after parsing the table name if it wasn't found before the name. --- src/dialect/snowflake.rs | 5 +++++ tests/sqlparser_snowflake.rs | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index ccce16198..88f4020b4 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -401,6 +401,11 @@ pub fn parse_create_table( let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let table_name = parser.parse_object_name(false)?; + // Snowflake allows `IF NOT EXIST` both before and after the table name so if we haven't + // already seen that before the table name, try to parse it again. + let if_not_exists = + if_not_exists || parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); + let mut builder = CreateTableBuilder::new(table_name) .or_replace(or_replace) .if_not_exists(if_not_exists) diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index b4d62506d..dc3f2f1bc 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -446,6 +446,18 @@ fn test_snowflake_create_table_if_not_exists() { } _ => unreachable!(), } + + for sql in [ + r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + r#"CREATE TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#, + r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + r#"CREATE TRANSIENT TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#, + ] { + // We don't use `verified_stmt` here because the parser won't produce a 1:1 result of the + // query. We will always put the `IF NOT EXIST` before the column name when displaying the + // query. + assert!(snowflake().parse_sql_statements(sql).is_ok()); + } } #[test] From 06c6b35e9739221b7e5c7bf28e77fb6e23246875 Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 11 Jun 2025 16:11:24 +0200 Subject: [PATCH 2/3] Use existing loop to handle unordered parameters --- src/dialect/snowflake.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 88f4020b4..990e2ea29 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -401,11 +401,6 @@ pub fn parse_create_table( let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let table_name = parser.parse_object_name(false)?; - // Snowflake allows `IF NOT EXIST` both before and after the table name so if we haven't - // already seen that before the table name, try to parse it again. - let if_not_exists = - if_not_exists || parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); - let mut builder = CreateTableBuilder::new(table_name) .or_replace(or_replace) .if_not_exists(if_not_exists) @@ -565,6 +560,9 @@ pub fn parse_create_table( builder.storage_serialization_policy = Some(parse_storage_serialization_policy(parser)?); } + Keyword::IF if parser.parse_keywords(&[Keyword::NOT, Keyword::EXISTS]) => { + builder = builder.if_not_exists(true); + } _ => { return parser.expected("end of statement", next_token); } From 14dda9f2045e593bc0e9c5b0c39d3a592b263bfb Mon Sep 17 00:00:00 2001 From: Simon Sawert Date: Wed, 11 Jun 2025 17:16:02 +0200 Subject: [PATCH 3/3] Use `one_statement_parses_to` --- tests/sqlparser_snowflake.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index dc3f2f1bc..7a164c248 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -447,16 +447,25 @@ fn test_snowflake_create_table_if_not_exists() { _ => unreachable!(), } - for sql in [ - r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, - r#"CREATE TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#, - r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, - r#"CREATE TRANSIENT TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#, + for (sql, parse_to) in [ + ( + r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + ), + ( + r#"CREATE TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#, + r#"CREATE TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + ), + ( + r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + ), + ( + r#"CREATE TRANSIENT TABLE "A"."B"."C" IF NOT EXISTS (v VARIANT)"#, + r#"CREATE TRANSIENT TABLE IF NOT EXISTS "A"."B"."C" (v VARIANT)"#, + ), ] { - // We don't use `verified_stmt` here because the parser won't produce a 1:1 result of the - // query. We will always put the `IF NOT EXIST` before the column name when displaying the - // query. - assert!(snowflake().parse_sql_statements(sql).is_ok()); + snowflake().one_statement_parses_to(sql, parse_to); } }