diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 62a8a71a6..0a93f5b7e 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -3020,6 +3020,9 @@ pub struct CreateTable { /// Snowflake "EXTERNAL_VOLUME" clause for Iceberg tables /// pub external_volume: Option, + /// `WITH CONNECTION` clause. + /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_external_table_statement) + pub with_connection: Option, /// Snowflake "BASE_LOCATION" clause for Iceberg tables /// pub base_location: Option, @@ -3269,6 +3272,9 @@ impl fmt::Display for CreateTable { if let Some(cluster_by) = self.cluster_by.as_ref() { write!(f, " CLUSTER BY {cluster_by}")?; } + if let Some(with_connection) = &self.with_connection { + write!(f, " WITH CONNECTION {with_connection}")?; + } if let options @ CreateTableOptions::Options(_) = &self.table_options { write!(f, " {options}")?; } diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index fc81d3b86..9ec9ab28c 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs @@ -157,6 +157,9 @@ pub struct CreateTableBuilder { pub base_location: Option, /// Optional external volume identifier. pub external_volume: Option, + /// `WITH CONNECTION` clause. + /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_external_table_statement) + pub with_connection: Option, /// Optional catalog name. pub catalog: Option, /// Optional catalog synchronization option. @@ -241,6 +244,7 @@ impl CreateTableBuilder { with_tags: None, base_location: None, external_volume: None, + with_connection: None, catalog: None, catalog_sync: None, storage_serialization_policy: None, @@ -497,6 +501,11 @@ impl CreateTableBuilder { self.external_volume = external_volume; self } + /// Set the `WITH CONNECTION` clause. + pub fn with_connection(mut self, with_connection: Option) -> Self { + self.with_connection = with_connection; + self + } /// Set the catalog name for the table. pub fn catalog(mut self, catalog: Option) -> Self { self.catalog = catalog; @@ -630,6 +639,7 @@ impl CreateTableBuilder { with_tags: self.with_tags, base_location: self.base_location, external_volume: self.external_volume, + with_connection: self.with_connection, catalog: self.catalog, catalog_sync: self.catalog_sync, storage_serialization_policy: self.storage_serialization_policy, @@ -714,6 +724,7 @@ impl From for CreateTableBuilder { with_tags: table.with_tags, base_location: table.base_location, external_volume: table.external_volume, + with_connection: table.with_connection, catalog: table.catalog, catalog_sync: table.catalog_sync, storage_serialization_policy: table.storage_serialization_policy, diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 343d99e73..f6ba89547 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -589,6 +589,7 @@ impl Spanned for CreateTable { with_storage_lifecycle_policy: _, // todo, Snowflake specific with_tags: _, // todo, Snowflake specific external_volume: _, // todo, Snowflake specific + with_connection: _, // todo, BigQuery external table connection base_location: _, // todo, Snowflake specific catalog: _, // todo, Snowflake specific catalog_sync: _, // todo, Snowflake specific diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3d4b30958..07497b04f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6418,6 +6418,12 @@ impl<'a> Parser<'a> { None }; let location = hive_formats.as_ref().and_then(|hf| hf.location.clone()); + + let with_connection = if self.parse_keywords(&[Keyword::WITH, Keyword::CONNECTION]) { + Some(self.parse_object_name(false)?) + } else { + None + }; let table_properties = self.parse_options(Keyword::TBLPROPERTIES)?; let table_options = if !table_properties.is_empty() { CreateTableOptions::TableProperties(table_properties) @@ -6432,6 +6438,7 @@ impl<'a> Parser<'a> { .hive_distribution(hive_distribution) .hive_formats(hive_formats) .table_options(table_options) + .with_connection(with_connection) .or_replace(or_replace) .if_not_exists(if_not_exists) .external(true) diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 212607e0c..f6d4483c2 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -2203,6 +2203,31 @@ fn parse_big_query_declare() { ); } +#[test] +fn parse_bigquery_create_external_table_with_connection() { + bigquery().one_statement_parses_to( + concat!( + "CREATE OR REPLACE EXTERNAL TABLE `proj.ds.tbl` ", + "WITH CONNECTION `projects/proj/locations/us/connections/c` ", + r#"OPTIONS(format = "ICEBERG", uris = ["gs://b/m.json"])"#, + ), + concat!( + "CREATE OR REPLACE EXTERNAL TABLE `proj`.`ds`.`tbl` () ", + "WITH CONNECTION `projects/proj/locations/us/connections/c` ", + r#"OPTIONS(format = "ICEBERG", uris = ["gs://b/m.json"])"#, + ), + ); + bigquery().one_statement_parses_to( + "CREATE EXTERNAL TABLE t WITH CONNECTION c", + "CREATE EXTERNAL TABLE t () WITH CONNECTION c", + ); + bigquery().verified_stmt(concat!( + "CREATE EXTERNAL TABLE t (a INT64, b STRING) ", + r#"WITH CONNECTION c OPTIONS(uris = ["gs://x"])"#, + )); + bigquery().verified_stmt(r#"CREATE EXTERNAL TABLE t (a INT64) OPTIONS(uris = ["gs://x"])"#); +} + fn bigquery() -> TestedDialects { TestedDialects::new(vec![Box::new(BigQueryDialect {})]) } diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 548ad27cf..86c765813 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -780,6 +780,7 @@ fn test_duckdb_union_datatype() { with_tags: Default::default(), base_location: Default::default(), external_volume: Default::default(), + with_connection: Default::default(), catalog: Default::default(), catalog_sync: Default::default(), storage_serialization_policy: Default::default(), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index d784c74ae..6e866746d 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1999,6 +1999,7 @@ fn parse_create_table_with_valid_options() { with_tags: None, base_location: None, external_volume: None, + with_connection: None, catalog: None, catalog_sync: None, storage_serialization_policy: None, @@ -2176,6 +2177,7 @@ fn parse_create_table_with_identity_column() { with_tags: None, base_location: None, external_volume: None, + with_connection: None, catalog: None, catalog_sync: None, storage_serialization_policy: None, diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 369e7fc5d..11e76dbc7 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -6702,6 +6702,7 @@ fn parse_trigger_related_functions() { with_tags: None, base_location: None, external_volume: None, + with_connection: None, catalog: None, catalog_sync: None, storage_serialization_policy: None,