Skip to content

Commit 738f12d

Browse files
authored
Fixed create snapshot table for bigquery (#2269)
1 parent 47b6aac commit 738f12d

File tree

8 files changed

+79
-2
lines changed

8 files changed

+79
-2
lines changed

src/ast/ddl.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2913,6 +2913,9 @@ pub struct CreateTable {
29132913
pub volatile: bool,
29142914
/// `ICEBERG` clause
29152915
pub iceberg: bool,
2916+
/// `SNAPSHOT` clause
2917+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_snapshot_table_statement>
2918+
pub snapshot: bool,
29162919
/// Table name
29172920
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
29182921
pub name: ObjectName,
@@ -3064,9 +3067,10 @@ impl fmt::Display for CreateTable {
30643067
// `CREATE TABLE t (a INT) AS SELECT a from t2`
30653068
write!(
30663069
f,
3067-
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}TABLE {if_not_exists}{name}",
3070+
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
30683071
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
30693072
external = if self.external { "EXTERNAL " } else { "" },
3073+
snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
30703074
global = self.global
30713075
.map(|global| {
30723076
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+
/// `SNAPSHOT` table flag.
85+
pub snapshot: bool,
8486
/// Whether `DYNAMIC` table option is set.
8587
pub dynamic: bool,
8688
/// The table name.
@@ -191,6 +193,7 @@ impl CreateTableBuilder {
191193
transient: false,
192194
volatile: false,
193195
iceberg: false,
196+
snapshot: false,
194197
dynamic: false,
195198
name,
196199
columns: vec![],
@@ -281,6 +284,11 @@ impl CreateTableBuilder {
281284
self.iceberg = iceberg;
282285
self
283286
}
287+
/// Set `SNAPSHOT` table flag (BigQuery).
288+
pub fn snapshot(mut self, snapshot: bool) -> Self {
289+
self.snapshot = snapshot;
290+
self
291+
}
284292
/// Set `DYNAMIC` table option.
285293
pub fn dynamic(mut self, dynamic: bool) -> Self {
286294
self.dynamic = dynamic;
@@ -540,6 +548,7 @@ impl CreateTableBuilder {
540548
transient: self.transient,
541549
volatile: self.volatile,
542550
iceberg: self.iceberg,
551+
snapshot: self.snapshot,
543552
dynamic: self.dynamic,
544553
name: self.name,
545554
columns: self.columns,
@@ -618,6 +627,7 @@ impl From<CreateTable> for CreateTableBuilder {
618627
transient: table.transient,
619628
volatile: table.volatile,
620629
iceberg: table.iceberg,
630+
snapshot: table.snapshot,
621631
dynamic: table.dynamic,
622632
name: table.name,
623633
columns: table.columns,

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ impl Spanned for CreateTable {
540540
transient: _, // bool
541541
volatile: _, // bool
542542
iceberg: _, // bool, Snowflake specific
543+
snapshot: _, // bool, BigQuery specific
543544
name,
544545
columns,
545546
constraints,

src/parser/mod.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5110,7 +5110,9 @@ impl<'a> Parser<'a> {
51105110
let persistent = dialect_of!(self is DuckDbDialect)
51115111
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
51125112
let create_view_params = self.parse_create_view_params()?;
5113-
if self.parse_keyword(Keyword::TABLE) {
5113+
if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
5114+
self.parse_create_snapshot_table().map(Into::into)
5115+
} else if self.parse_keyword(Keyword::TABLE) {
51145116
self.parse_create_table(or_replace, temporary, global, transient)
51155117
.map(Into::into)
51165118
} else if self.peek_keyword(Keyword::MATERIALIZED)
@@ -6327,6 +6329,40 @@ impl<'a> Parser<'a> {
63276329
.build())
63286330
}
63296331

6332+
/// Parse `CREATE SNAPSHOT TABLE` statement.
6333+
///
6334+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_snapshot_table_statement>
6335+
pub fn parse_create_snapshot_table(&mut self) -> Result<CreateTable, ParserError> {
6336+
self.expect_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE])?;
6337+
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
6338+
let table_name = self.parse_object_name(true)?;
6339+
6340+
self.expect_keyword_is(Keyword::CLONE)?;
6341+
let clone = Some(self.parse_object_name(true)?);
6342+
6343+
let version =
6344+
if self.parse_keywords(&[Keyword::FOR, Keyword::SYSTEM_TIME, Keyword::AS, Keyword::OF])
6345+
{
6346+
Some(TableVersion::ForSystemTimeAsOf(self.parse_expr()?))
6347+
} else {
6348+
None
6349+
};
6350+
6351+
let table_options = if let Some(options) = self.maybe_parse_options(Keyword::OPTIONS)? {
6352+
CreateTableOptions::Options(options)
6353+
} else {
6354+
CreateTableOptions::None
6355+
};
6356+
6357+
Ok(CreateTableBuilder::new(table_name)
6358+
.snapshot(true)
6359+
.if_not_exists(if_not_exists)
6360+
.clone_clause(clone)
6361+
.version(version)
6362+
.table_options(table_options)
6363+
.build())
6364+
}
6365+
63306366
/// Parse a file format for external tables.
63316367
pub fn parse_file_format(&mut self) -> Result<FileFormat, ParserError> {
63326368
let next_token = self.next_token();

tests/sqlparser_bigquery.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2900,3 +2900,25 @@ fn test_alter_schema() {
29002900
bigquery_and_generic()
29012901
.verified_stmt("ALTER SCHEMA IF EXISTS mydataset SET OPTIONS (location = 'us')");
29022902
}
2903+
2904+
#[test]
2905+
fn test_create_snapshot_table() {
2906+
bigquery_and_generic()
2907+
.verified_stmt("CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2");
2908+
2909+
bigquery().verified_stmt(
2910+
"CREATE SNAPSHOT TABLE IF NOT EXISTS dataset_id.table1 CLONE dataset_id.table2",
2911+
);
2912+
2913+
bigquery().verified_stmt(
2914+
"CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2 FOR SYSTEM_TIME AS OF TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)",
2915+
);
2916+
2917+
bigquery().verified_stmt(
2918+
"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')",
2919+
);
2920+
2921+
bigquery().verified_stmt(
2922+
"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')",
2923+
);
2924+
}

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,
@@ -2120,6 +2121,7 @@ fn parse_create_table_with_identity_column() {
21202121
transient: false,
21212122
volatile: false,
21222123
iceberg: false,
2124+
snapshot: false,
21232125
name: ObjectName::from(vec![Ident {
21242126
value: "mytable".to_string(),
21252127
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)