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]