From f1c44cd90a036419afb02c8c4f96498ac470e1bf Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Mon, 18 Aug 2025 16:17:08 +0300 Subject: [PATCH 1/2] Add SECURE keyword for views in Snowflake --- src/ast/mod.rs | 7 ++++++- src/ast/spans.rs | 1 + src/keywords.rs | 1 + src/parser/mod.rs | 7 ++++++- tests/sqlparser_common.rs | 7 +++++++ tests/sqlparser_snowflake.rs | 27 +++++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index a30e24239c..a59b555adb 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3255,6 +3255,9 @@ pub enum Statement { or_alter: bool, or_replace: bool, materialized: bool, + /// Snowflake: SECURE view modifier + /// + secure: bool, /// View name name: ObjectName, /// If `if_not_exists` is true, this flag is set to true if the view name comes before the `IF NOT EXISTS` clause. @@ -5105,6 +5108,7 @@ impl fmt::Display for Statement { columns, query, materialized, + secure, options, cluster_by, comment, @@ -5126,7 +5130,7 @@ impl fmt::Display for Statement { } write!( f, - "{materialized}{temporary}VIEW {if_not_and_name}{to}", + "{secure}{materialized}{temporary}VIEW {if_not_and_name}{to}", if_not_and_name = if *if_not_exists { if *name_before_not_exists { format!("{name} IF NOT EXISTS") @@ -5136,6 +5140,7 @@ impl fmt::Display for Statement { } else { format!("{name}") }, + secure = if *secure { "SECURE " } else { "" }, materialized = if *materialized { "MATERIALIZED " } else { "" }, temporary = if *temporary { "TEMPORARY " } else { "" }, to = to diff --git a/src/ast/spans.rs b/src/ast/spans.rs index da47b0f85b..c4d2790518 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -391,6 +391,7 @@ impl Spanned for Statement { or_alter: _, or_replace: _, materialized: _, + secure: _, name, columns, query, diff --git a/src/keywords.rs b/src/keywords.rs index 659bc04399..03cd3f3f49 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -814,6 +814,7 @@ define_keywords!( SECONDARY_ENGINE_ATTRIBUTE, SECONDS, SECRET, + SECURE, SECURITY, SEED, SELECT, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9cddf8272d..90df948c29 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4713,7 +4713,10 @@ impl<'a> Parser<'a> { let create_view_params = self.parse_create_view_params()?; if self.parse_keyword(Keyword::TABLE) { self.parse_create_table(or_replace, temporary, global, transient) - } else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) { + } else if self.parse_keyword(Keyword::MATERIALIZED) + || self.parse_keyword(Keyword::VIEW) + || (dialect_of!(self is SnowflakeDialect) && self.parse_keyword(Keyword::SECURE)) + { self.prev_token(); self.parse_create_view(or_alter, or_replace, temporary, create_view_params) } else if self.parse_keyword(Keyword::POLICY) { @@ -5834,6 +5837,7 @@ impl<'a> Parser<'a> { temporary: bool, create_view_params: Option, ) -> Result { + let secure = self.parse_keyword(Keyword::SECURE); let materialized = self.parse_keyword(Keyword::MATERIALIZED); self.expect_keyword_is(Keyword::VIEW)?; let allow_unquoted_hyphen = dialect_of!(self is BigQueryDialect); @@ -5904,6 +5908,7 @@ impl<'a> Parser<'a> { columns, query, materialized, + secure, or_replace, options, cluster_by, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 53b0d20365..7b4291f55b 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8078,6 +8078,7 @@ fn parse_create_view() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("myschema.myview", name.to_string()); @@ -8147,6 +8148,7 @@ fn parse_create_view_with_columns() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("v", name.to_string()); @@ -8197,6 +8199,7 @@ fn parse_create_view_temporary() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("myschema.myview", name.to_string()); @@ -8237,6 +8240,7 @@ fn parse_create_or_replace_view() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("v", name.to_string()); @@ -8281,6 +8285,7 @@ fn parse_create_or_replace_materialized_view() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("v", name.to_string()); @@ -8321,6 +8326,7 @@ fn parse_create_materialized_view() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("myschema.myview", name.to_string()); @@ -8361,6 +8367,7 @@ fn parse_create_materialized_view_with_cluster_by() { to, params, name_before_not_exists: _, + secure: _, } => { assert_eq!(or_alter, false); assert_eq!("myschema.myview", name.to_string()); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index bd8a6d30a4..87369cef43 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -44,6 +44,33 @@ fn test_snowflake_create_table() { } } +#[test] +fn parse_sf_create_secure_view_and_materialized_view() { + for sql in [ + "CREATE SECURE VIEW v AS SELECT 1", + "CREATE SECURE MATERIALIZED VIEW v AS SELECT 1", + "CREATE OR REPLACE SECURE VIEW v AS SELECT 1", + "CREATE OR REPLACE SECURE MATERIALIZED VIEW v AS SELECT 1", + ] { + match snowflake().verified_stmt(sql) { + Statement::CreateView { + secure, + materialized, + .. + } => { + assert!(secure); + if sql.contains("MATERIALIZED") { + assert!(materialized); + } else { + assert!(!materialized); + } + } + _ => unreachable!(), + } + assert_eq!(snowflake().verified_stmt(sql).to_string(), sql); + } +} + #[test] fn test_snowflake_create_or_replace_table() { let sql = "CREATE OR REPLACE TABLE my_table (a number)"; From 187309c90303f229da72d33ffddbc719903117fe Mon Sep 17 00:00:00 2001 From: Denys Tsomenko Date: Mon, 25 Aug 2025 17:17:57 +0300 Subject: [PATCH 2/2] Change for parse_keywords to peek_keywords before create view --- src/parser/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 90df948c29..9e3efccc71 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4713,11 +4713,11 @@ impl<'a> Parser<'a> { let create_view_params = self.parse_create_view_params()?; if self.parse_keyword(Keyword::TABLE) { self.parse_create_table(or_replace, temporary, global, transient) - } else if self.parse_keyword(Keyword::MATERIALIZED) - || self.parse_keyword(Keyword::VIEW) - || (dialect_of!(self is SnowflakeDialect) && self.parse_keyword(Keyword::SECURE)) + } else if self.peek_keyword(Keyword::MATERIALIZED) + || self.peek_keyword(Keyword::VIEW) + || self.peek_keywords(&[Keyword::SECURE, Keyword::MATERIALIZED, Keyword::VIEW]) + || self.peek_keywords(&[Keyword::SECURE, Keyword::VIEW]) { - self.prev_token(); self.parse_create_view(or_alter, or_replace, temporary, create_view_params) } else if self.parse_keyword(Keyword::POLICY) { self.parse_create_policy()