Skip to content

Commit 02070c4

Browse files
committed
Fixed create snapshot table for bigquery
1 parent 6691f31 commit 02070c4

File tree

8 files changed

+77
-2
lines changed

8 files changed

+77
-2
lines changed

src/ast/ddl.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2903,6 +2903,9 @@ pub struct CreateTable {
29032903
pub volatile: bool,
29042904
/// `ICEBERG` clause
29052905
pub iceberg: bool,
2906+
/// BigQuery `SNAPSHOT` clause
2907+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_snapshot_table_statement>
2908+
pub snapshot: bool,
29062909
/// Table name
29072910
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
29082911
pub name: ObjectName,
@@ -3051,9 +3054,10 @@ impl fmt::Display for CreateTable {
30513054
// `CREATE TABLE t (a INT) AS SELECT a from t2`
30523055
write!(
30533056
f,
3054-
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}TABLE {if_not_exists}{name}",
3057+
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
30553058
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
30563059
external = if self.external { "EXTERNAL " } else { "" },
3060+
snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
30573061
global = self.global
30583062
.map(|global| {
30593063
if global {

src/ast/helpers/stmt_create_table.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub struct CreateTableBuilder {
8181
pub volatile: bool,
8282
/// Iceberg-specific table flag.
8383
pub iceberg: bool,
84+
/// BigQuery `SNAPSHOT` table flag.
85+
pub snapshot: bool,
8486
/// Whether `DYNAMIC` table option is set.
8587
pub dynamic: bool,
8688
/// The table name.
@@ -189,6 +191,7 @@ impl CreateTableBuilder {
189191
transient: false,
190192
volatile: false,
191193
iceberg: false,
194+
snapshot: false,
192195
dynamic: false,
193196
name,
194197
columns: vec![],
@@ -278,6 +281,11 @@ impl CreateTableBuilder {
278281
self.iceberg = iceberg;
279282
self
280283
}
284+
/// Set `SNAPSHOT` table flag (BigQuery).
285+
pub fn snapshot(mut self, snapshot: bool) -> Self {
286+
self.snapshot = snapshot;
287+
self
288+
}
281289
/// Set `DYNAMIC` table option.
282290
pub fn dynamic(mut self, dynamic: bool) -> Self {
283291
self.dynamic = dynamic;
@@ -532,6 +540,7 @@ impl CreateTableBuilder {
532540
transient: self.transient,
533541
volatile: self.volatile,
534542
iceberg: self.iceberg,
543+
snapshot: self.snapshot,
535544
dynamic: self.dynamic,
536545
name: self.name,
537546
columns: self.columns,
@@ -609,6 +618,7 @@ impl From<CreateTable> for CreateTableBuilder {
609618
transient: table.transient,
610619
volatile: table.volatile,
611620
iceberg: table.iceberg,
621+
snapshot: table.snapshot,
612622
dynamic: table.dynamic,
613623
name: table.name,
614624
columns: table.columns,

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ impl Spanned for CreateTable {
538538
transient: _, // bool
539539
volatile: _, // bool
540540
iceberg: _, // bool, Snowflake specific
541+
snapshot: _, // bool, BigQuery specific
541542
name,
542543
columns,
543544
constraints,

src/parser/mod.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5099,7 +5099,9 @@ impl<'a> Parser<'a> {
50995099
let persistent = dialect_of!(self is DuckDbDialect)
51005100
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
51015101
let create_view_params = self.parse_create_view_params()?;
5102-
if self.parse_keyword(Keyword::TABLE) {
5102+
if self.parse_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
5103+
self.parse_create_snapshot_table().map(Into::into)
5104+
} else if self.parse_keyword(Keyword::TABLE) {
51035105
self.parse_create_table(or_replace, temporary, global, transient)
51045106
.map(Into::into)
51055107
} else if self.peek_keyword(Keyword::MATERIALIZED)
@@ -6314,6 +6316,39 @@ impl<'a> Parser<'a> {
63146316
.build())
63156317
}
63166318

6319+
/// Parse BigQuery `CREATE SNAPSHOT TABLE` statement.
6320+
///
6321+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_snapshot_table_statement>
6322+
pub fn parse_create_snapshot_table(&mut self) -> Result<CreateTable, ParserError> {
6323+
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
6324+
let table_name = self.parse_object_name(true)?;
6325+
6326+
self.expect_keyword_is(Keyword::CLONE)?;
6327+
let clone = Some(self.parse_object_name(true)?);
6328+
6329+
let version =
6330+
if self.parse_keywords(&[Keyword::FOR, Keyword::SYSTEM_TIME, Keyword::AS, Keyword::OF])
6331+
{
6332+
Some(TableVersion::ForSystemTimeAsOf(self.parse_expr()?))
6333+
} else {
6334+
None
6335+
};
6336+
6337+
let table_options = if let Some(options) = self.maybe_parse_options(Keyword::OPTIONS)? {
6338+
CreateTableOptions::Options(options)
6339+
} else {
6340+
CreateTableOptions::None
6341+
};
6342+
6343+
Ok(CreateTableBuilder::new(table_name)
6344+
.snapshot(true)
6345+
.if_not_exists(if_not_exists)
6346+
.clone_clause(clone)
6347+
.version(version)
6348+
.table_options(table_options)
6349+
.build())
6350+
}
6351+
63176352
/// Parse a file format for external tables.
63186353
pub fn parse_file_format(&mut self) -> Result<FileFormat, ParserError> {
63196354
let next_token = self.next_token();

tests/sqlparser_bigquery.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,3 +2890,24 @@ fn test_alter_schema() {
28902890
bigquery_and_generic()
28912891
.verified_stmt("ALTER SCHEMA IF EXISTS mydataset SET OPTIONS (location = 'us')");
28922892
}
2893+
2894+
#[test]
2895+
fn test_create_snapshot_table() {
2896+
bigquery().verified_stmt("CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2");
2897+
2898+
bigquery().verified_stmt(
2899+
"CREATE SNAPSHOT TABLE IF NOT EXISTS dataset_id.table1 CLONE dataset_id.table2",
2900+
);
2901+
2902+
bigquery().verified_stmt(
2903+
"CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2 FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)",
2904+
);
2905+
2906+
bigquery().verified_stmt(
2907+
"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')",
2908+
);
2909+
2910+
bigquery().verified_stmt(
2911+
"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')",
2912+
);
2913+
}

tests/sqlparser_duckdb.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ fn test_duckdb_union_datatype() {
709709
transient: Default::default(),
710710
volatile: Default::default(),
711711
iceberg: Default::default(),
712+
snapshot: false,
712713
dynamic: Default::default(),
713714
name: ObjectName::from(vec!["tbl1".into()]),
714715
columns: vec![

tests/sqlparser_mssql.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,7 @@ fn parse_create_table_with_valid_options() {
19851985
for_values: None,
19861986
strict: false,
19871987
iceberg: false,
1988+
snapshot: false,
19881989
copy_grants: false,
19891990
enable_schema_evolution: None,
19901991
change_tracking: None,
@@ -2119,6 +2120,7 @@ fn parse_create_table_with_identity_column() {
21192120
transient: false,
21202121
volatile: false,
21212122
iceberg: false,
2123+
snapshot: false,
21222124
name: ObjectName::from(vec![Ident {
21232125
value: "mytable".to_string(),
21242126
quote_style: None,

tests/sqlparser_postgres.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6409,6 +6409,7 @@ fn parse_trigger_related_functions() {
64096409
transient: false,
64106410
volatile: false,
64116411
iceberg: false,
6412+
snapshot: false,
64126413
name: ObjectName::from(vec![Ident::new("emp")]),
64136414
columns: vec![
64146415
ColumnDef {

0 commit comments

Comments
 (0)