This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 738f12dd Fixed create snapshot table for bigquery (#2269)
738f12dd is described below

commit 738f12dd4d68836cd1746fb24df1db40de58bfd2
Author: Andriy Romanov <[email protected]>
AuthorDate: Fri Mar 13 02:18:03 2026 -0700

    Fixed create snapshot table for bigquery (#2269)
---
 src/ast/ddl.rs                       |  6 +++++-
 src/ast/helpers/stmt_create_table.rs | 10 ++++++++++
 src/ast/spans.rs                     |  1 +
 src/parser/mod.rs                    | 38 +++++++++++++++++++++++++++++++++++-
 tests/sqlparser_bigquery.rs          | 22 +++++++++++++++++++++
 tests/sqlparser_duckdb.rs            |  1 +
 tests/sqlparser_mssql.rs             |  2 ++
 tests/sqlparser_postgres.rs          |  1 +
 8 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index d6da8368..49dc5202 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -2913,6 +2913,9 @@ pub struct CreateTable {
     pub volatile: bool,
     /// `ICEBERG` clause
     pub iceberg: bool,
+    /// `SNAPSHOT` clause
+    /// 
<https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_snapshot_table_statement>
+    pub snapshot: bool,
     /// Table name
     #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
     pub name: ObjectName,
@@ -3064,9 +3067,10 @@ impl fmt::Display for CreateTable {
         //   `CREATE TABLE t (a INT) AS SELECT a from t2`
         write!(
             f,
-            "CREATE 
{or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}TABLE
 {if_not_exists}{name}",
+            "CREATE 
{or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE
 {if_not_exists}{name}",
             or_replace = if self.or_replace { "OR REPLACE " } else { "" },
             external = if self.external { "EXTERNAL " } else { "" },
+            snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
             global = self.global
                 .map(|global| {
                     if global {
diff --git a/src/ast/helpers/stmt_create_table.rs 
b/src/ast/helpers/stmt_create_table.rs
index 5963e940..6af820e7 100644
--- a/src/ast/helpers/stmt_create_table.rs
+++ b/src/ast/helpers/stmt_create_table.rs
@@ -81,6 +81,8 @@ pub struct CreateTableBuilder {
     pub volatile: bool,
     /// Iceberg-specific table flag.
     pub iceberg: bool,
+    /// `SNAPSHOT` table flag.
+    pub snapshot: bool,
     /// Whether `DYNAMIC` table option is set.
     pub dynamic: bool,
     /// The table name.
@@ -191,6 +193,7 @@ impl CreateTableBuilder {
             transient: false,
             volatile: false,
             iceberg: false,
+            snapshot: false,
             dynamic: false,
             name,
             columns: vec![],
@@ -281,6 +284,11 @@ impl CreateTableBuilder {
         self.iceberg = iceberg;
         self
     }
+    /// Set `SNAPSHOT` table flag (BigQuery).
+    pub fn snapshot(mut self, snapshot: bool) -> Self {
+        self.snapshot = snapshot;
+        self
+    }
     /// Set `DYNAMIC` table option.
     pub fn dynamic(mut self, dynamic: bool) -> Self {
         self.dynamic = dynamic;
@@ -540,6 +548,7 @@ impl CreateTableBuilder {
             transient: self.transient,
             volatile: self.volatile,
             iceberg: self.iceberg,
+            snapshot: self.snapshot,
             dynamic: self.dynamic,
             name: self.name,
             columns: self.columns,
@@ -618,6 +627,7 @@ impl From<CreateTable> for CreateTableBuilder {
             transient: table.transient,
             volatile: table.volatile,
             iceberg: table.iceberg,
+            snapshot: table.snapshot,
             dynamic: table.dynamic,
             name: table.name,
             columns: table.columns,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 8dd8d8c5..5777d289 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -540,6 +540,7 @@ impl Spanned for CreateTable {
             transient: _,     // bool
             volatile: _,      // bool
             iceberg: _,       // bool, Snowflake specific
+            snapshot: _,      // bool, BigQuery specific
             name,
             columns,
             constraints,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index dc47c27b..801f89e6 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -5110,7 +5110,9 @@ impl<'a> Parser<'a> {
         let persistent = dialect_of!(self is DuckDbDialect)
             && self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
         let create_view_params = self.parse_create_view_params()?;
-        if self.parse_keyword(Keyword::TABLE) {
+        if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
+            self.parse_create_snapshot_table().map(Into::into)
+        } else if self.parse_keyword(Keyword::TABLE) {
             self.parse_create_table(or_replace, temporary, global, transient)
                 .map(Into::into)
         } else if self.peek_keyword(Keyword::MATERIALIZED)
@@ -6327,6 +6329,40 @@ impl<'a> Parser<'a> {
             .build())
     }
 
+    /// Parse `CREATE SNAPSHOT TABLE` statement.
+    ///
+    /// 
<https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_snapshot_table_statement>
+    pub fn parse_create_snapshot_table(&mut self) -> Result<CreateTable, 
ParserError> {
+        self.expect_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE])?;
+        let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, 
Keyword::EXISTS]);
+        let table_name = self.parse_object_name(true)?;
+
+        self.expect_keyword_is(Keyword::CLONE)?;
+        let clone = Some(self.parse_object_name(true)?);
+
+        let version =
+            if self.parse_keywords(&[Keyword::FOR, Keyword::SYSTEM_TIME, 
Keyword::AS, Keyword::OF])
+            {
+                Some(TableVersion::ForSystemTimeAsOf(self.parse_expr()?))
+            } else {
+                None
+            };
+
+        let table_options = if let Some(options) = 
self.maybe_parse_options(Keyword::OPTIONS)? {
+            CreateTableOptions::Options(options)
+        } else {
+            CreateTableOptions::None
+        };
+
+        Ok(CreateTableBuilder::new(table_name)
+            .snapshot(true)
+            .if_not_exists(if_not_exists)
+            .clone_clause(clone)
+            .version(version)
+            .table_options(table_options)
+            .build())
+    }
+
     /// Parse a file format for external tables.
     pub fn parse_file_format(&mut self) -> Result<FileFormat, ParserError> {
         let next_token = self.next_token();
diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs
index a6b0906f..d3e47f99 100644
--- a/tests/sqlparser_bigquery.rs
+++ b/tests/sqlparser_bigquery.rs
@@ -2900,3 +2900,25 @@ fn test_alter_schema() {
     bigquery_and_generic()
         .verified_stmt("ALTER SCHEMA IF EXISTS mydataset SET OPTIONS (location 
= 'us')");
 }
+
+#[test]
+fn test_create_snapshot_table() {
+    bigquery_and_generic()
+        .verified_stmt("CREATE SNAPSHOT TABLE dataset_id.table1 CLONE 
dataset_id.table2");
+
+    bigquery().verified_stmt(
+        "CREATE SNAPSHOT TABLE IF NOT EXISTS dataset_id.table1 CLONE 
dataset_id.table2",
+    );
+
+    bigquery().verified_stmt(
+        "CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2 FOR 
SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)",
+    );
+
+    bigquery().verified_stmt(
+        "CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2 
OPTIONS(expiration_timestamp = TIMESTAMP '2025-01-01 00:00:00 UTC', 
friendly_name = 'my_table')",
+    );
+
+    bigquery().verified_stmt(
+        "CREATE SNAPSHOT TABLE IF NOT EXISTS dataset_id.table1 CLONE 
dataset_id.table2 FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), 
INTERVAL 1 HOUR) OPTIONS(expiration_timestamp = TIMESTAMP '2025-01-01 00:00:00 
UTC')",
+    );
+}
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs
index 33a6cb45..b3c40761 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -709,6 +709,7 @@ fn test_duckdb_union_datatype() {
             transient: Default::default(),
             volatile: Default::default(),
             iceberg: Default::default(),
+            snapshot: false,
             dynamic: Default::default(),
             name: ObjectName::from(vec!["tbl1".into()]),
             columns: vec![
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index da6ecace..7fc030ee 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -1985,6 +1985,7 @@ fn parse_create_table_with_valid_options() {
                 for_values: None,
                 strict: false,
                 iceberg: false,
+                snapshot: false,
                 copy_grants: false,
                 enable_schema_evolution: None,
                 change_tracking: None,
@@ -2120,6 +2121,7 @@ fn parse_create_table_with_identity_column() {
                 transient: false,
                 volatile: false,
                 iceberg: false,
+                snapshot: false,
                 name: ObjectName::from(vec![Ident {
                     value: "mytable".to_string(),
                     quote_style: None,
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 35c4d478..2f74d706 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6409,6 +6409,7 @@ fn parse_trigger_related_functions() {
             transient: false,
             volatile: false,
             iceberg: false,
+            snapshot: false,
             name: ObjectName::from(vec![Ident::new("emp")]),
             columns: vec![
                 ColumnDef {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to