Skip to content

Commit 3a6d3ec

Browse files
authored
Add support for BigQuery table and view options (#1061)
1 parent c7d2903 commit 3a6d3ec

File tree

8 files changed

+465
-54
lines changed

8 files changed

+465
-54
lines changed

src/ast/ddl.rs

+48
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use sqlparser_derive::{Visit, VisitMut};
2626
use crate::ast::value::escape_single_quote_string;
2727
use crate::ast::{
2828
display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName, SequenceOptions,
29+
SqlOption,
2930
};
3031
use crate::tokenizer::Token;
3132

@@ -634,6 +635,42 @@ impl fmt::Display for ColumnDef {
634635
}
635636
}
636637

638+
/// Column definition specified in a `CREATE VIEW` statement.
639+
///
640+
/// Syntax
641+
/// ```markdown
642+
/// <name> [OPTIONS(option, ...)]
643+
///
644+
/// option: <name> = <value>
645+
/// ```
646+
///
647+
/// Examples:
648+
/// ```sql
649+
/// name
650+
/// age OPTIONS(description = "age column", tag = "prod")
651+
/// ```
652+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
653+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
654+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
655+
pub struct ViewColumnDef {
656+
pub name: Ident,
657+
pub options: Option<Vec<SqlOption>>,
658+
}
659+
660+
impl fmt::Display for ViewColumnDef {
661+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
662+
write!(f, "{}", self.name)?;
663+
if let Some(options) = self.options.as_ref() {
664+
write!(
665+
f,
666+
" OPTIONS({})",
667+
display_comma_separated(options.as_slice())
668+
)?;
669+
}
670+
Ok(())
671+
}
672+
}
673+
637674
/// An optionally-named `ColumnOption`: `[ CONSTRAINT <name> ] <column-option>`.
638675
///
639676
/// Note that implementations are substantially more permissive than the ANSI
@@ -710,6 +747,14 @@ pub enum ColumnOption {
710747
/// false if 'GENERATED ALWAYS' is skipped (option starts with AS)
711748
generated_keyword: bool,
712749
},
750+
/// BigQuery specific: Explicit column options in a view [1] or table [2]
751+
/// Syntax
752+
/// ```sql
753+
/// OPTIONS(description="field desc")
754+
/// ```
755+
/// [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#view_column_option_list
756+
/// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_option_list
757+
Options(Vec<SqlOption>),
713758
}
714759

715760
impl fmt::Display for ColumnOption {
@@ -788,6 +833,9 @@ impl fmt::Display for ColumnOption {
788833
Ok(())
789834
}
790835
}
836+
Options(options) => {
837+
write!(f, "OPTIONS({})", display_comma_separated(options))
838+
}
791839
}
792840
}
793841
}

src/ast/helpers/stmt_create_table.rs

+40-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
88
use sqlparser_derive::{Visit, VisitMut};
99

1010
use crate::ast::{
11-
ColumnDef, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, Query,
12-
SqlOption, Statement, TableConstraint,
11+
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
12+
Query, SqlOption, Statement, TableConstraint,
1313
};
1414
use crate::parser::ParserError;
1515

@@ -72,6 +72,9 @@ pub struct CreateTableBuilder {
7272
pub on_commit: Option<OnCommit>,
7373
pub on_cluster: Option<String>,
7474
pub order_by: Option<Vec<Ident>>,
75+
pub partition_by: Option<Box<Expr>>,
76+
pub cluster_by: Option<Vec<Ident>>,
77+
pub options: Option<Vec<SqlOption>>,
7578
pub strict: bool,
7679
}
7780

@@ -105,6 +108,9 @@ impl CreateTableBuilder {
105108
on_commit: None,
106109
on_cluster: None,
107110
order_by: None,
111+
partition_by: None,
112+
cluster_by: None,
113+
options: None,
108114
strict: false,
109115
}
110116
}
@@ -236,6 +242,21 @@ impl CreateTableBuilder {
236242
self
237243
}
238244

245+
pub fn partition_by(mut self, partition_by: Option<Box<Expr>>) -> Self {
246+
self.partition_by = partition_by;
247+
self
248+
}
249+
250+
pub fn cluster_by(mut self, cluster_by: Option<Vec<Ident>>) -> Self {
251+
self.cluster_by = cluster_by;
252+
self
253+
}
254+
255+
pub fn options(mut self, options: Option<Vec<SqlOption>>) -> Self {
256+
self.options = options;
257+
self
258+
}
259+
239260
pub fn strict(mut self, strict: bool) -> Self {
240261
self.strict = strict;
241262
self
@@ -270,6 +291,9 @@ impl CreateTableBuilder {
270291
on_commit: self.on_commit,
271292
on_cluster: self.on_cluster,
272293
order_by: self.order_by,
294+
partition_by: self.partition_by,
295+
cluster_by: self.cluster_by,
296+
options: self.options,
273297
strict: self.strict,
274298
}
275299
}
@@ -310,6 +334,9 @@ impl TryFrom<Statement> for CreateTableBuilder {
310334
on_commit,
311335
on_cluster,
312336
order_by,
337+
partition_by,
338+
cluster_by,
339+
options,
313340
strict,
314341
} => Ok(Self {
315342
or_replace,
@@ -339,6 +366,9 @@ impl TryFrom<Statement> for CreateTableBuilder {
339366
on_commit,
340367
on_cluster,
341368
order_by,
369+
partition_by,
370+
cluster_by,
371+
options,
342372
strict,
343373
}),
344374
_ => Err(ParserError::ParserError(format!(
@@ -348,6 +378,14 @@ impl TryFrom<Statement> for CreateTableBuilder {
348378
}
349379
}
350380

381+
/// Helper return type when parsing configuration for a BigQuery `CREATE TABLE` statement.
382+
#[derive(Default)]
383+
pub(crate) struct BigQueryTableConfiguration {
384+
pub partition_by: Option<Box<Expr>>,
385+
pub cluster_by: Option<Vec<Ident>>,
386+
pub options: Option<Vec<SqlOption>>,
387+
}
388+
351389
#[cfg(test)]
352390
mod tests {
353391
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;

src/ast/mod.rs

+71-7
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub use self::ddl::{
3434
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
3535
ColumnOptionDef, GeneratedAs, GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition,
3636
ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
37-
UserDefinedTypeRepresentation,
37+
UserDefinedTypeRepresentation, ViewColumnDef,
3838
};
3939
pub use self::operator::{BinaryOperator, UnaryOperator};
4040
pub use self::query::{
@@ -1367,6 +1367,38 @@ pub enum Password {
13671367
NullPassword,
13681368
}
13691369

1370+
/// Sql options of a `CREATE TABLE` statement.
1371+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1372+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1373+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1374+
pub enum CreateTableOptions {
1375+
None,
1376+
/// Options specified using the `WITH` keyword.
1377+
/// e.g. `WITH (description = "123")`
1378+
///
1379+
/// <https://www.postgresql.org/docs/current/sql-createtable.html>
1380+
With(Vec<SqlOption>),
1381+
/// Options specified using the `OPTIONS` keyword.
1382+
/// e.g. `OPTIONS(description = "123")`
1383+
///
1384+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
1385+
Options(Vec<SqlOption>),
1386+
}
1387+
1388+
impl fmt::Display for CreateTableOptions {
1389+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1390+
match self {
1391+
CreateTableOptions::With(with_options) => {
1392+
write!(f, "WITH ({})", display_comma_separated(with_options))
1393+
}
1394+
CreateTableOptions::Options(options) => {
1395+
write!(f, "OPTIONS({})", display_comma_separated(options))
1396+
}
1397+
CreateTableOptions::None => Ok(()),
1398+
}
1399+
}
1400+
}
1401+
13701402
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
13711403
#[allow(clippy::large_enum_variant)]
13721404
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -1550,9 +1582,9 @@ pub enum Statement {
15501582
materialized: bool,
15511583
/// View name
15521584
name: ObjectName,
1553-
columns: Vec<Ident>,
1585+
columns: Vec<ViewColumnDef>,
15541586
query: Box<Query>,
1555-
with_options: Vec<SqlOption>,
1587+
options: CreateTableOptions,
15561588
cluster_by: Vec<Ident>,
15571589
/// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html>
15581590
with_no_schema_binding: bool,
@@ -1600,6 +1632,15 @@ pub enum Statement {
16001632
/// than empty (represented as ()), the latter meaning "no sorting".
16011633
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
16021634
order_by: Option<Vec<Ident>>,
1635+
/// BigQuery: A partition expression for the table.
1636+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression>
1637+
partition_by: Option<Box<Expr>>,
1638+
/// BigQuery: Table clustering column list.
1639+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
1640+
cluster_by: Option<Vec<Ident>>,
1641+
/// BigQuery: Table options list.
1642+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
1643+
options: Option<Vec<SqlOption>>,
16031644
/// SQLite "STRICT" clause.
16041645
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
16051646
/// then strict typing rules apply to that table.
@@ -2731,7 +2772,7 @@ impl fmt::Display for Statement {
27312772
columns,
27322773
query,
27332774
materialized,
2734-
with_options,
2775+
options,
27352776
cluster_by,
27362777
with_no_schema_binding,
27372778
if_not_exists,
@@ -2746,15 +2787,18 @@ impl fmt::Display for Statement {
27462787
temporary = if *temporary { "TEMPORARY " } else { "" },
27472788
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }
27482789
)?;
2749-
if !with_options.is_empty() {
2750-
write!(f, " WITH ({})", display_comma_separated(with_options))?;
2790+
if matches!(options, CreateTableOptions::With(_)) {
2791+
write!(f, " {options}")?;
27512792
}
27522793
if !columns.is_empty() {
27532794
write!(f, " ({})", display_comma_separated(columns))?;
27542795
}
27552796
if !cluster_by.is_empty() {
27562797
write!(f, " CLUSTER BY ({})", display_comma_separated(cluster_by))?;
27572798
}
2799+
if matches!(options, CreateTableOptions::Options(_)) {
2800+
write!(f, " {options}")?;
2801+
}
27582802
write!(f, " AS {query}")?;
27592803
if *with_no_schema_binding {
27602804
write!(f, " WITH NO SCHEMA BINDING")?;
@@ -2789,6 +2833,9 @@ impl fmt::Display for Statement {
27892833
on_commit,
27902834
on_cluster,
27912835
order_by,
2836+
partition_by,
2837+
cluster_by,
2838+
options,
27922839
strict,
27932840
} => {
27942841
// We want to allow the following options
@@ -2945,6 +2992,23 @@ impl fmt::Display for Statement {
29452992
if let Some(order_by) = order_by {
29462993
write!(f, " ORDER BY ({})", display_comma_separated(order_by))?;
29472994
}
2995+
if let Some(partition_by) = partition_by.as_ref() {
2996+
write!(f, " PARTITION BY {partition_by}")?;
2997+
}
2998+
if let Some(cluster_by) = cluster_by.as_ref() {
2999+
write!(
3000+
f,
3001+
" CLUSTER BY {}",
3002+
display_comma_separated(cluster_by.as_slice())
3003+
)?;
3004+
}
3005+
if let Some(options) = options.as_ref() {
3006+
write!(
3007+
f,
3008+
" OPTIONS({})",
3009+
display_comma_separated(options.as_slice())
3010+
)?;
3011+
}
29483012
if let Some(query) = query {
29493013
write!(f, " AS {query}")?;
29503014
}
@@ -4496,7 +4560,7 @@ pub struct HiveFormat {
44964560
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
44974561
pub struct SqlOption {
44984562
pub name: Ident,
4499-
pub value: Value,
4563+
pub value: Expr,
45004564
}
45014565

45024566
impl fmt::Display for SqlOption {

0 commit comments

Comments
 (0)