Skip to content

Commit f75f0c8

Browse files
authored
Merge branch 'main' into main
2 parents 202cdd8 + 3fa7114 commit f75f0c8

File tree

6 files changed

+69
-0
lines changed

6 files changed

+69
-0
lines changed

src/ast/ddl.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4293,6 +4293,9 @@ pub struct CreateView {
42934293
pub if_not_exists: bool,
42944294
/// if true, has SQLite `TEMP` or `TEMPORARY` clause <https://www.sqlite.org/lang_createview.html>
42954295
pub temporary: bool,
4296+
/// Snowflake: `COPY GRANTS` clause
4297+
/// <https://docs.snowflake.com/en/sql-reference/sql/create-view>
4298+
pub copy_grants: bool,
42964299
/// if not None, has Clickhouse `TO` clause, specify the table into which to insert results
42974300
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/view#materialized-view>
42984301
pub to: Option<ObjectName>,
@@ -4336,6 +4339,9 @@ impl fmt::Display for CreateView {
43364339
.map(|to| format!(" TO {to}"))
43374340
.unwrap_or_default()
43384341
)?;
4342+
if self.copy_grants {
4343+
write!(f, " COPY GRANTS")?;
4344+
}
43394345
if !self.columns.is_empty() {
43404346
write!(f, " ({})", display_comma_separated(&self.columns))?;
43414347
}

src/dialect/snowflake.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,17 @@ impl Dialect for SnowflakeDialect {
247247

248248
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
249249
if parser.parse_keyword(Keyword::BEGIN) {
250+
// Snowflake supports both `BEGIN TRANSACTION` and `BEGIN ... END` blocks.
251+
// If the next keyword indicates a transaction statement, let the
252+
// standard parse_begin() handle it.
253+
if parser
254+
.peek_one_of_keywords(&[Keyword::TRANSACTION, Keyword::WORK, Keyword::NAME])
255+
.is_some()
256+
|| matches!(parser.peek_token_ref().token, Token::SemiColon | Token::EOF)
257+
{
258+
parser.prev_token();
259+
return None;
260+
}
250261
return Some(parser.parse_begin_exception_end());
251262
}
252263

src/parser/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6301,6 +6301,8 @@ impl<'a> Parser<'a> {
63016301
let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?;
63026302
let table_options = if !table_properties.is_empty() {
63036303
CreateTableOptions::TableProperties(table_properties)
6304+
} else if let Some(options) = self.maybe_parse_options(Keyword::OPTIONS)? {
6305+
CreateTableOptions::Options(options)
63046306
} else {
63056307
CreateTableOptions::None
63066308
};
@@ -6379,6 +6381,7 @@ impl<'a> Parser<'a> {
63796381
let name_before_not_exists = !if_not_exists_first
63806382
&& self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
63816383
let if_not_exists = if_not_exists_first || name_before_not_exists;
6384+
let copy_grants = self.parse_keywords(&[Keyword::COPY, Keyword::GRANTS]);
63826385
// Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet).
63836386
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
63846387
let columns = self.parse_view_columns()?;
@@ -6446,6 +6449,7 @@ impl<'a> Parser<'a> {
64466449
with_no_schema_binding,
64476450
if_not_exists,
64486451
temporary,
6452+
copy_grants,
64496453
to,
64506454
params: create_view_params,
64516455
name_before_not_exists,

tests/sqlparser_bigquery.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,16 @@ fn parse_create_table_with_options() {
591591
bigquery().verified_stmt(sql);
592592
}
593593

594+
#[test]
595+
fn parse_create_external_table_with_options() {
596+
bigquery().verified_stmt(
597+
"CREATE EXTERNAL TABLE dataset_id.table1 (hvr_tx_seq STRING) OPTIONS(format = 'CSV')",
598+
);
599+
bigquery().verified_stmt(
600+
"CREATE EXTERNAL TABLE dataset_id.table1 (hvr_tx_seq STRING) OPTIONS(format = 'CSV', allow_quoted_newlines = true, encoding = 'UTF8')",
601+
);
602+
}
603+
594604
#[test]
595605
fn parse_nested_data_types() {
596606
let sql = "CREATE TABLE table (x STRUCT<a ARRAY<INT64>, b BYTES(42)>, y ARRAY<STRUCT<INT64>>)";

tests/sqlparser_common.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8317,6 +8317,7 @@ fn parse_create_view() {
83178317
params,
83188318
name_before_not_exists: _,
83198319
secure: _,
8320+
copy_grants: _,
83208321
}) => {
83218322
assert_eq!(or_alter, false);
83228323
assert_eq!("myschema.myview", name.to_string());
@@ -8435,6 +8436,7 @@ fn parse_create_view_temporary() {
84358436
params,
84368437
name_before_not_exists: _,
84378438
secure: _,
8439+
copy_grants: _,
84388440
}) => {
84398441
assert_eq!(or_alter, false);
84408442
assert_eq!("myschema.myview", name.to_string());
@@ -8476,6 +8478,7 @@ fn parse_create_or_replace_view() {
84768478
params,
84778479
name_before_not_exists: _,
84788480
secure: _,
8481+
copy_grants: _,
84798482
}) => {
84808483
assert_eq!(or_alter, false);
84818484
assert_eq!("v", name.to_string());
@@ -8521,6 +8524,7 @@ fn parse_create_or_replace_materialized_view() {
85218524
params,
85228525
name_before_not_exists: _,
85238526
secure: _,
8527+
copy_grants: _,
85248528
}) => {
85258529
assert_eq!(or_alter, false);
85268530
assert_eq!("v", name.to_string());
@@ -8562,6 +8566,7 @@ fn parse_create_materialized_view() {
85628566
params,
85638567
name_before_not_exists: _,
85648568
secure: _,
8569+
copy_grants: _,
85658570
}) => {
85668571
assert_eq!(or_alter, false);
85678572
assert_eq!("myschema.myview", name.to_string());
@@ -8603,6 +8608,7 @@ fn parse_create_materialized_view_with_cluster_by() {
86038608
params,
86048609
name_before_not_exists: _,
86058610
secure: _,
8611+
copy_grants: _,
86068612
}) => {
86078613
assert_eq!(or_alter, false);
86088614
assert_eq!("myschema.myview", name.to_string());

tests/sqlparser_snowflake.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4610,6 +4610,27 @@ END
46104610
assert_eq!(2, exception[1].statements.len());
46114611
}
46124612

4613+
#[test]
4614+
fn test_begin_transaction() {
4615+
snowflake().verified_stmt("BEGIN TRANSACTION");
4616+
snowflake().verified_stmt("BEGIN WORK");
4617+
4618+
// BEGIN TRANSACTION with statements
4619+
let stmts = snowflake()
4620+
.parse_sql_statements("BEGIN TRANSACTION; DROP TABLE IF EXISTS bla; COMMIT")
4621+
.unwrap();
4622+
assert_eq!(3, stmts.len());
4623+
4624+
// Bare BEGIN (no TRANSACTION keyword) with statements
4625+
let stmts = snowflake()
4626+
.parse_sql_statements("BEGIN; DROP TABLE IF EXISTS bla; COMMIT")
4627+
.unwrap();
4628+
assert_eq!(3, stmts.len());
4629+
4630+
// Bare BEGIN at EOF (no semicolon, no TRANSACTION keyword)
4631+
snowflake().verified_stmt("BEGIN");
4632+
}
4633+
46134634
#[test]
46144635
fn test_snowflake_fetch_clause_syntax() {
46154636
let canonical = "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS ONLY";
@@ -4650,6 +4671,17 @@ fn test_snowflake_create_view_with_composite_policy_name() {
46504671
snowflake().verified_stmt(create_view_with_tag);
46514672
}
46524673

4674+
#[test]
4675+
fn test_snowflake_create_view_copy_grants() {
4676+
snowflake().verified_stmt("CREATE OR REPLACE VIEW bla COPY GRANTS AS (SELECT * FROM source)");
4677+
snowflake()
4678+
.verified_stmt("CREATE OR REPLACE SECURE VIEW bla COPY GRANTS AS (SELECT * FROM source)");
4679+
// COPY GRANTS with column list
4680+
snowflake().verified_stmt(
4681+
"CREATE OR REPLACE VIEW bla COPY GRANTS (a, b) AS (SELECT a, b FROM source)",
4682+
);
4683+
}
4684+
46534685
#[test]
46544686
fn test_snowflake_identifier_function() {
46554687
// Using IDENTIFIER to reference a column

0 commit comments

Comments
 (0)