Skip to content

Commit e1ce7c4

Browse files
committed
feat(snowflake): accept COPY GRANTS after CREATE VIEW column list
Snowflake documents `COPY GRANTS` as appearing after the column list on `CREATE VIEW`. The parser already accepted it before the column list; also accept it after, normalizing Display to the pre-columns form.
1 parent 9833c03 commit e1ce7c4

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

src/parser/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6533,10 +6533,16 @@ impl<'a> Parser<'a> {
65336533
let name_before_not_exists = !if_not_exists_first
65346534
&& self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
65356535
let if_not_exists = if_not_exists_first || name_before_not_exists;
6536-
let copy_grants = self.parse_keywords(&[Keyword::COPY, Keyword::GRANTS]);
6536+
let mut copy_grants = self.parse_keywords(&[Keyword::COPY, Keyword::GRANTS]);
65376537
// Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet).
65386538
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
65396539
let columns = self.parse_view_columns()?;
6540+
// Snowflake also documents `COPY GRANTS` *after* the column list; accept
6541+
// either position, but not both.
6542+
// <https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax>
6543+
if !copy_grants {
6544+
copy_grants = self.parse_keywords(&[Keyword::COPY, Keyword::GRANTS]);
6545+
}
65406546
let mut options = CreateTableOptions::None;
65416547
let with_options = self.parse_options(Keyword::WITH)?;
65426548
if !with_options.is_empty() {

tests/sqlparser_snowflake.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4764,6 +4764,52 @@ fn test_snowflake_create_view_copy_grants() {
47644764
);
47654765
}
47664766

4767+
#[test]
4768+
fn test_snowflake_create_view_copy_grants_after_columns() {
4769+
// Snowflake's documented placement for `COPY GRANTS` on `CREATE VIEW` is
4770+
// *after* the column list. Display normalizes to the pre-columns form
4771+
// already supported, so use `one_statement_parses_to` to assert the
4772+
// post-columns input is accepted and the AST flag is set.
4773+
// <https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax>
4774+
let cases = [
4775+
(
4776+
"CREATE OR REPLACE VIEW v (a, b) COPY GRANTS AS SELECT a, b FROM t",
4777+
"CREATE OR REPLACE VIEW v COPY GRANTS (a, b) AS SELECT a, b FROM t",
4778+
),
4779+
(
4780+
"CREATE OR REPLACE SECURE VIEW v (a, b) COPY GRANTS AS SELECT a, b FROM t",
4781+
"CREATE OR REPLACE SECURE VIEW v COPY GRANTS (a, b) AS SELECT a, b FROM t",
4782+
),
4783+
(
4784+
"CREATE MATERIALIZED VIEW v (a) COPY GRANTS AS SELECT a FROM t",
4785+
"CREATE MATERIALIZED VIEW v COPY GRANTS (a) AS SELECT a FROM t",
4786+
),
4787+
];
4788+
for (sql, parsed) in cases {
4789+
match snowflake().one_statement_parses_to(sql, parsed) {
4790+
Statement::CreateView(CreateView {
4791+
name,
4792+
copy_grants,
4793+
columns,
4794+
..
4795+
}) => {
4796+
assert_eq!("v", name.to_string());
4797+
assert!(copy_grants, "copy_grants should be true for {sql:?}");
4798+
assert!(!columns.is_empty(), "columns should be set for {sql:?}");
4799+
}
4800+
_ => unreachable!(),
4801+
}
4802+
}
4803+
4804+
// Baseline: the same query without COPY GRANTS must not flip the flag.
4805+
match snowflake().verified_stmt("CREATE OR REPLACE VIEW v (a) AS SELECT a FROM t") {
4806+
Statement::CreateView(CreateView { copy_grants, .. }) => {
4807+
assert!(!copy_grants);
4808+
}
4809+
_ => unreachable!(),
4810+
}
4811+
}
4812+
47674813
#[test]
47684814
fn test_snowflake_identifier_function() {
47694815
// Using IDENTIFIER to reference a column

0 commit comments

Comments
 (0)