Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2903,6 +2903,9 @@ pub struct CreateTable {
pub volatile: bool,
/// `ICEBERG` clause
pub iceberg: bool,
/// BigQuery `SNAPSHOT` clause
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// BigQuery `SNAPSHOT` clause
/// `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,
Expand Down Expand Up @@ -3051,9 +3054,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 {
Expand Down
10 changes: 10 additions & 0 deletions src/ast/helpers/stmt_create_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ pub struct CreateTableBuilder {
pub volatile: bool,
/// Iceberg-specific table flag.
pub iceberg: bool,
/// BigQuery `SNAPSHOT` table flag.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// BigQuery `SNAPSHOT` table flag.
/// `SNAPSHOT` table flag.

pub snapshot: bool,
/// Whether `DYNAMIC` table option is set.
pub dynamic: bool,
/// The table name.
Expand Down Expand Up @@ -189,6 +191,7 @@ impl CreateTableBuilder {
transient: false,
volatile: false,
iceberg: false,
snapshot: false,
dynamic: false,
name,
columns: vec![],
Expand Down Expand Up @@ -278,6 +281,11 @@ impl CreateTableBuilder {
self.iceberg = iceberg;
self
}
/// Set `SNAPSHOT` table flag (BigQuery).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Set `SNAPSHOT` table flag (BigQuery).
/// Set `SNAPSHOT` table flag.

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;
Expand Down Expand Up @@ -532,6 +540,7 @@ impl CreateTableBuilder {
transient: self.transient,
volatile: self.volatile,
iceberg: self.iceberg,
snapshot: self.snapshot,
dynamic: self.dynamic,
name: self.name,
columns: self.columns,
Expand Down Expand Up @@ -609,6 +618,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,
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ impl Spanned for CreateTable {
transient: _, // bool
volatile: _, // bool
iceberg: _, // bool, Snowflake specific
snapshot: _, // bool, BigQuery specific
name,
columns,
constraints,
Expand Down
37 changes: 36 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5099,7 +5099,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.parse_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.parse_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {

thinking we can peek here so that the parse_create_snapshot_table() function is standalone

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)
Expand Down Expand Up @@ -6314,6 +6316,39 @@ impl<'a> Parser<'a> {
.build())
}

/// Parse BigQuery `CREATE SNAPSHOT TABLE` statement.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Parse BigQuery `CREATE SNAPSHOT TABLE` statement.
/// Parse `CREATE SNAPSHOT TABLE` statement.

I'm thinking in general we can avoid inlining the dialect in the description (the doc links provide context on which dialects support the syntax). Otherwise it becomes confusing/misleading over time documentation wise as multiple dialects support the same or part of the syntax

///
/// <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> {
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();
Expand Down
21 changes: 21 additions & 0 deletions tests/sqlparser_bigquery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2890,3 +2890,24 @@ 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().verified_stmt("CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bigquery().verified_stmt("CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2");
bigquery_and_generic().verified_stmt("CREATE SNAPSHOT TABLE dataset_id.table1 CLONE dataset_id.table2");

can the tests cover generic as well?


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')",
);
}
1 change: 1 addition & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![
Expand Down
2 changes: 2 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -2119,6 +2120,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,
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down