mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-21 22:44:08 +00:00
Add support for BigQuery table and view options (#1061)
This commit is contained in:
parent
c7d2903c6d
commit
3a6d3ecba2
8 changed files with 465 additions and 54 deletions
|
@ -26,6 +26,7 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
use crate::ast::value::escape_single_quote_string;
|
||||
use crate::ast::{
|
||||
display_comma_separated, display_separated, DataType, Expr, Ident, ObjectName, SequenceOptions,
|
||||
SqlOption,
|
||||
};
|
||||
use crate::tokenizer::Token;
|
||||
|
||||
|
@ -634,6 +635,42 @@ impl fmt::Display for ColumnDef {
|
|||
}
|
||||
}
|
||||
|
||||
/// Column definition specified in a `CREATE VIEW` statement.
|
||||
///
|
||||
/// Syntax
|
||||
/// ```markdown
|
||||
/// <name> [OPTIONS(option, ...)]
|
||||
///
|
||||
/// option: <name> = <value>
|
||||
/// ```
|
||||
///
|
||||
/// Examples:
|
||||
/// ```sql
|
||||
/// name
|
||||
/// age OPTIONS(description = "age column", tag = "prod")
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct ViewColumnDef {
|
||||
pub name: Ident,
|
||||
pub options: Option<Vec<SqlOption>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ViewColumnDef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if let Some(options) = self.options.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
" OPTIONS({})",
|
||||
display_comma_separated(options.as_slice())
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// An optionally-named `ColumnOption`: `[ CONSTRAINT <name> ] <column-option>`.
|
||||
///
|
||||
/// Note that implementations are substantially more permissive than the ANSI
|
||||
|
@ -710,6 +747,14 @@ pub enum ColumnOption {
|
|||
/// false if 'GENERATED ALWAYS' is skipped (option starts with AS)
|
||||
generated_keyword: bool,
|
||||
},
|
||||
/// BigQuery specific: Explicit column options in a view [1] or table [2]
|
||||
/// Syntax
|
||||
/// ```sql
|
||||
/// OPTIONS(description="field desc")
|
||||
/// ```
|
||||
/// [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#view_column_option_list
|
||||
/// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_option_list
|
||||
Options(Vec<SqlOption>),
|
||||
}
|
||||
|
||||
impl fmt::Display for ColumnOption {
|
||||
|
@ -788,6 +833,9 @@ impl fmt::Display for ColumnOption {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
Options(options) => {
|
||||
write!(f, "OPTIONS({})", display_comma_separated(options))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
|
|||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::ast::{
|
||||
ColumnDef, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, Query,
|
||||
SqlOption, Statement, TableConstraint,
|
||||
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
|
||||
Query, SqlOption, Statement, TableConstraint,
|
||||
};
|
||||
use crate::parser::ParserError;
|
||||
|
||||
|
@ -72,6 +72,9 @@ pub struct CreateTableBuilder {
|
|||
pub on_commit: Option<OnCommit>,
|
||||
pub on_cluster: Option<String>,
|
||||
pub order_by: Option<Vec<Ident>>,
|
||||
pub partition_by: Option<Box<Expr>>,
|
||||
pub cluster_by: Option<Vec<Ident>>,
|
||||
pub options: Option<Vec<SqlOption>>,
|
||||
pub strict: bool,
|
||||
}
|
||||
|
||||
|
@ -105,6 +108,9 @@ impl CreateTableBuilder {
|
|||
on_commit: None,
|
||||
on_cluster: None,
|
||||
order_by: None,
|
||||
partition_by: None,
|
||||
cluster_by: None,
|
||||
options: None,
|
||||
strict: false,
|
||||
}
|
||||
}
|
||||
|
@ -236,6 +242,21 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn partition_by(mut self, partition_by: Option<Box<Expr>>) -> Self {
|
||||
self.partition_by = partition_by;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cluster_by(mut self, cluster_by: Option<Vec<Ident>>) -> Self {
|
||||
self.cluster_by = cluster_by;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn options(mut self, options: Option<Vec<SqlOption>>) -> Self {
|
||||
self.options = options;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn strict(mut self, strict: bool) -> Self {
|
||||
self.strict = strict;
|
||||
self
|
||||
|
@ -270,6 +291,9 @@ impl CreateTableBuilder {
|
|||
on_commit: self.on_commit,
|
||||
on_cluster: self.on_cluster,
|
||||
order_by: self.order_by,
|
||||
partition_by: self.partition_by,
|
||||
cluster_by: self.cluster_by,
|
||||
options: self.options,
|
||||
strict: self.strict,
|
||||
}
|
||||
}
|
||||
|
@ -310,6 +334,9 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
on_commit,
|
||||
on_cluster,
|
||||
order_by,
|
||||
partition_by,
|
||||
cluster_by,
|
||||
options,
|
||||
strict,
|
||||
} => Ok(Self {
|
||||
or_replace,
|
||||
|
@ -339,6 +366,9 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
on_commit,
|
||||
on_cluster,
|
||||
order_by,
|
||||
partition_by,
|
||||
cluster_by,
|
||||
options,
|
||||
strict,
|
||||
}),
|
||||
_ => Err(ParserError::ParserError(format!(
|
||||
|
@ -348,6 +378,14 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper return type when parsing configuration for a BigQuery `CREATE TABLE` statement.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct BigQueryTableConfiguration {
|
||||
pub partition_by: Option<Box<Expr>>,
|
||||
pub cluster_by: Option<Vec<Ident>>,
|
||||
pub options: Option<Vec<SqlOption>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||
|
|
|
@ -34,7 +34,7 @@ pub use self::ddl::{
|
|||
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
|
||||
ColumnOptionDef, GeneratedAs, GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition,
|
||||
ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
|
||||
UserDefinedTypeRepresentation,
|
||||
UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
};
|
||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||
pub use self::query::{
|
||||
|
@ -1367,6 +1367,38 @@ pub enum Password {
|
|||
NullPassword,
|
||||
}
|
||||
|
||||
/// Sql options of a `CREATE TABLE` statement.
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum CreateTableOptions {
|
||||
None,
|
||||
/// Options specified using the `WITH` keyword.
|
||||
/// e.g. `WITH (description = "123")`
|
||||
///
|
||||
/// <https://www.postgresql.org/docs/current/sql-createtable.html>
|
||||
With(Vec<SqlOption>),
|
||||
/// Options specified using the `OPTIONS` keyword.
|
||||
/// e.g. `OPTIONS(description = "123")`
|
||||
///
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||
Options(Vec<SqlOption>),
|
||||
}
|
||||
|
||||
impl fmt::Display for CreateTableOptions {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
CreateTableOptions::With(with_options) => {
|
||||
write!(f, "WITH ({})", display_comma_separated(with_options))
|
||||
}
|
||||
CreateTableOptions::Options(options) => {
|
||||
write!(f, "OPTIONS({})", display_comma_separated(options))
|
||||
}
|
||||
CreateTableOptions::None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A top-level statement (SELECT, INSERT, CREATE, etc.)
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
|
@ -1550,9 +1582,9 @@ pub enum Statement {
|
|||
materialized: bool,
|
||||
/// View name
|
||||
name: ObjectName,
|
||||
columns: Vec<Ident>,
|
||||
columns: Vec<ViewColumnDef>,
|
||||
query: Box<Query>,
|
||||
with_options: Vec<SqlOption>,
|
||||
options: CreateTableOptions,
|
||||
cluster_by: Vec<Ident>,
|
||||
/// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html>
|
||||
with_no_schema_binding: bool,
|
||||
|
@ -1600,6 +1632,15 @@ pub enum Statement {
|
|||
/// than empty (represented as ()), the latter meaning "no sorting".
|
||||
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
|
||||
order_by: Option<Vec<Ident>>,
|
||||
/// BigQuery: A partition expression for the table.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression>
|
||||
partition_by: Option<Box<Expr>>,
|
||||
/// BigQuery: Table clustering column list.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||
cluster_by: Option<Vec<Ident>>,
|
||||
/// BigQuery: Table options list.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||
options: Option<Vec<SqlOption>>,
|
||||
/// SQLite "STRICT" clause.
|
||||
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
|
||||
/// then strict typing rules apply to that table.
|
||||
|
@ -2731,7 +2772,7 @@ impl fmt::Display for Statement {
|
|||
columns,
|
||||
query,
|
||||
materialized,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding,
|
||||
if_not_exists,
|
||||
|
@ -2746,8 +2787,8 @@ impl fmt::Display for Statement {
|
|||
temporary = if *temporary { "TEMPORARY " } else { "" },
|
||||
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }
|
||||
)?;
|
||||
if !with_options.is_empty() {
|
||||
write!(f, " WITH ({})", display_comma_separated(with_options))?;
|
||||
if matches!(options, CreateTableOptions::With(_)) {
|
||||
write!(f, " {options}")?;
|
||||
}
|
||||
if !columns.is_empty() {
|
||||
write!(f, " ({})", display_comma_separated(columns))?;
|
||||
|
@ -2755,6 +2796,9 @@ impl fmt::Display for Statement {
|
|||
if !cluster_by.is_empty() {
|
||||
write!(f, " CLUSTER BY ({})", display_comma_separated(cluster_by))?;
|
||||
}
|
||||
if matches!(options, CreateTableOptions::Options(_)) {
|
||||
write!(f, " {options}")?;
|
||||
}
|
||||
write!(f, " AS {query}")?;
|
||||
if *with_no_schema_binding {
|
||||
write!(f, " WITH NO SCHEMA BINDING")?;
|
||||
|
@ -2789,6 +2833,9 @@ impl fmt::Display for Statement {
|
|||
on_commit,
|
||||
on_cluster,
|
||||
order_by,
|
||||
partition_by,
|
||||
cluster_by,
|
||||
options,
|
||||
strict,
|
||||
} => {
|
||||
// We want to allow the following options
|
||||
|
@ -2945,6 +2992,23 @@ impl fmt::Display for Statement {
|
|||
if let Some(order_by) = order_by {
|
||||
write!(f, " ORDER BY ({})", display_comma_separated(order_by))?;
|
||||
}
|
||||
if let Some(partition_by) = partition_by.as_ref() {
|
||||
write!(f, " PARTITION BY {partition_by}")?;
|
||||
}
|
||||
if let Some(cluster_by) = cluster_by.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
" CLUSTER BY {}",
|
||||
display_comma_separated(cluster_by.as_slice())
|
||||
)?;
|
||||
}
|
||||
if let Some(options) = options.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
" OPTIONS({})",
|
||||
display_comma_separated(options.as_slice())
|
||||
)?;
|
||||
}
|
||||
if let Some(query) = query {
|
||||
write!(f, " AS {query}")?;
|
||||
}
|
||||
|
@ -4496,7 +4560,7 @@ pub struct HiveFormat {
|
|||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub struct SqlOption {
|
||||
pub name: Ident,
|
||||
pub value: Value,
|
||||
pub value: Expr,
|
||||
}
|
||||
|
||||
impl fmt::Display for SqlOption {
|
||||
|
|
|
@ -27,7 +27,7 @@ use log::debug;
|
|||
use IsLateral::*;
|
||||
use IsOptional::*;
|
||||
|
||||
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||
use crate::ast::helpers::stmt_create_table::{BigQueryTableConfiguration, CreateTableBuilder};
|
||||
use crate::ast::*;
|
||||
use crate::dialect::*;
|
||||
use crate::keywords::{self, Keyword, ALL_KEYWORDS};
|
||||
|
@ -3463,8 +3463,12 @@ impl<'a> Parser<'a> {
|
|||
// Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet).
|
||||
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
|
||||
let name = self.parse_object_name()?;
|
||||
let columns = self.parse_parenthesized_column_list(Optional, false)?;
|
||||
let columns = self.parse_view_columns()?;
|
||||
let mut options = CreateTableOptions::None;
|
||||
let with_options = self.parse_options(Keyword::WITH)?;
|
||||
if !with_options.is_empty() {
|
||||
options = CreateTableOptions::With(with_options);
|
||||
}
|
||||
|
||||
let cluster_by = if self.parse_keyword(Keyword::CLUSTER) {
|
||||
self.expect_keyword(Keyword::BY)?;
|
||||
|
@ -3473,6 +3477,17 @@ impl<'a> Parser<'a> {
|
|||
vec![]
|
||||
};
|
||||
|
||||
if dialect_of!(self is BigQueryDialect | GenericDialect) {
|
||||
if let Token::Word(word) = self.peek_token().token {
|
||||
if word.keyword == Keyword::OPTIONS {
|
||||
let opts = self.parse_options(Keyword::OPTIONS)?;
|
||||
if !opts.is_empty() {
|
||||
options = CreateTableOptions::Options(opts);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
self.expect_keyword(Keyword::AS)?;
|
||||
let query = Box::new(self.parse_query()?);
|
||||
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
|
||||
|
@ -3491,7 +3506,7 @@ impl<'a> Parser<'a> {
|
|||
query,
|
||||
materialized,
|
||||
or_replace,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding,
|
||||
if_not_exists,
|
||||
|
@ -4177,6 +4192,12 @@ impl<'a> Parser<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
let big_query_config = if dialect_of!(self is BigQueryDialect | GenericDialect) {
|
||||
self.parse_optional_big_query_create_table_config()?
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// Parse optional `AS ( query )`
|
||||
let query = if self.parse_keyword(Keyword::AS) {
|
||||
Some(Box::new(self.parse_query()?))
|
||||
|
@ -4260,10 +4281,42 @@ impl<'a> Parser<'a> {
|
|||
.collation(collation)
|
||||
.on_commit(on_commit)
|
||||
.on_cluster(on_cluster)
|
||||
.partition_by(big_query_config.partition_by)
|
||||
.cluster_by(big_query_config.cluster_by)
|
||||
.options(big_query_config.options)
|
||||
.strict(strict)
|
||||
.build())
|
||||
}
|
||||
|
||||
/// Parse configuration like partitioning, clustering information during big-query table creation.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_2>
|
||||
fn parse_optional_big_query_create_table_config(
|
||||
&mut self,
|
||||
) -> Result<BigQueryTableConfiguration, ParserError> {
|
||||
let mut partition_by = None;
|
||||
if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
|
||||
partition_by = Some(Box::new(self.parse_expr()?));
|
||||
};
|
||||
|
||||
let mut cluster_by = None;
|
||||
if self.parse_keywords(&[Keyword::CLUSTER, Keyword::BY]) {
|
||||
cluster_by = Some(self.parse_comma_separated(Parser::parse_identifier)?);
|
||||
};
|
||||
|
||||
let mut options = None;
|
||||
if let Token::Word(word) = self.peek_token().token {
|
||||
if word.keyword == Keyword::OPTIONS {
|
||||
options = Some(self.parse_options(Keyword::OPTIONS)?);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(BigQueryTableConfiguration {
|
||||
partition_by,
|
||||
cluster_by,
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_optional_procedure_parameters(
|
||||
&mut self,
|
||||
) -> Result<Option<Vec<ProcedureParam>>, ParserError> {
|
||||
|
@ -4453,6 +4506,13 @@ impl<'a> Parser<'a> {
|
|||
Ok(Some(ColumnOption::OnUpdate(expr)))
|
||||
} else if self.parse_keyword(Keyword::GENERATED) {
|
||||
self.parse_optional_column_option_generated()
|
||||
} else if dialect_of!(self is BigQueryDialect | GenericDialect)
|
||||
&& self.parse_keyword(Keyword::OPTIONS)
|
||||
{
|
||||
self.prev_token();
|
||||
Ok(Some(ColumnOption::Options(
|
||||
self.parse_options(Keyword::OPTIONS)?,
|
||||
)))
|
||||
} else if self.parse_keyword(Keyword::AS)
|
||||
&& dialect_of!(self is MySqlDialect | SQLiteDialect | DuckDbDialect | GenericDialect)
|
||||
{
|
||||
|
@ -4731,7 +4791,7 @@ impl<'a> Parser<'a> {
|
|||
pub fn parse_sql_option(&mut self) -> Result<SqlOption, ParserError> {
|
||||
let name = self.parse_identifier()?;
|
||||
self.expect_token(&Token::Eq)?;
|
||||
let value = self.parse_value()?;
|
||||
let value = self.parse_expr()?;
|
||||
Ok(SqlOption { name, value })
|
||||
}
|
||||
|
||||
|
@ -5952,6 +6012,36 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parses a parenthesized, comma-separated list of column definitions within a view.
|
||||
fn parse_view_columns(&mut self) -> Result<Vec<ViewColumnDef>, ParserError> {
|
||||
if self.consume_token(&Token::LParen) {
|
||||
if self.peek_token().token == Token::RParen {
|
||||
self.next_token();
|
||||
Ok(vec![])
|
||||
} else {
|
||||
let cols = self.parse_comma_separated(Parser::parse_view_column)?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
Ok(cols)
|
||||
}
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a column definition within a view.
|
||||
fn parse_view_column(&mut self) -> Result<ViewColumnDef, ParserError> {
|
||||
let name = self.parse_identifier()?;
|
||||
let options = if dialect_of!(self is BigQueryDialect | GenericDialect)
|
||||
&& self.parse_keyword(Keyword::OPTIONS)
|
||||
{
|
||||
self.prev_token();
|
||||
Some(self.parse_options(Keyword::OPTIONS)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(ViewColumnDef { name, options })
|
||||
}
|
||||
|
||||
/// Parse a parenthesized comma-separated list of unqualified, possibly quoted identifiers
|
||||
pub fn parse_parenthesized_column_list(
|
||||
&mut self,
|
||||
|
|
|
@ -86,6 +86,168 @@ fn parse_raw_literal() {
|
|||
panic!("invalid query")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_view_with_options() {
|
||||
let sql = concat!(
|
||||
"CREATE VIEW myproject.mydataset.newview ",
|
||||
r#"(name, age OPTIONS(description = "field age")) "#,
|
||||
r#"OPTIONS(expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 48 HOUR), "#,
|
||||
r#"friendly_name = "newview", description = "a view that expires in 2 days", labels = [("org_unit", "development")]) "#,
|
||||
"AS SELECT column_1, column_2, column_3 FROM myproject.mydataset.mytable",
|
||||
);
|
||||
match bigquery().verified_stmt(sql) {
|
||||
Statement::CreateView {
|
||||
name,
|
||||
query,
|
||||
options,
|
||||
columns,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
name,
|
||||
ObjectName(vec![
|
||||
"myproject".into(),
|
||||
"mydataset".into(),
|
||||
"newview".into()
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
ViewColumnDef {
|
||||
name: Ident::new("name"),
|
||||
options: None,
|
||||
},
|
||||
ViewColumnDef {
|
||||
name: Ident::new("age"),
|
||||
options: Some(vec![SqlOption {
|
||||
name: Ident::new("description"),
|
||||
value: Expr::Value(Value::DoubleQuotedString("field age".to_string())),
|
||||
}])
|
||||
},
|
||||
],
|
||||
columns
|
||||
);
|
||||
assert_eq!(
|
||||
"SELECT column_1, column_2, column_3 FROM myproject.mydataset.mytable",
|
||||
query.to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
r#"OPTIONS(expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 48 HOUR), friendly_name = "newview", description = "a view that expires in 2 days", labels = [("org_unit", "development")])"#,
|
||||
options.to_string()
|
||||
);
|
||||
let CreateTableOptions::Options(options) = options else {
|
||||
unreachable!()
|
||||
};
|
||||
assert_eq!(
|
||||
&SqlOption {
|
||||
name: Ident::new("description"),
|
||||
value: Expr::Value(Value::DoubleQuotedString(
|
||||
"a view that expires in 2 days".to_string()
|
||||
)),
|
||||
},
|
||||
&options[2],
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_create_table_with_options() {
|
||||
let sql = concat!(
|
||||
"CREATE TABLE mydataset.newtable ",
|
||||
r#"(x INT64 NOT NULL OPTIONS(description = "field x"), "#,
|
||||
r#"y BOOL OPTIONS(description = "field y")) "#,
|
||||
"PARTITION BY _PARTITIONDATE ",
|
||||
"CLUSTER BY userid, age ",
|
||||
r#"OPTIONS(partition_expiration_days = 1, description = "table option description")"#
|
||||
);
|
||||
match bigquery().verified_stmt(sql) {
|
||||
Statement::CreateTable {
|
||||
name,
|
||||
columns,
|
||||
partition_by,
|
||||
cluster_by,
|
||||
options,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(
|
||||
name,
|
||||
ObjectName(vec!["mydataset".into(), "newtable".into()])
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
ColumnDef {
|
||||
name: Ident::new("x"),
|
||||
data_type: DataType::Int64,
|
||||
collation: None,
|
||||
options: vec![
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::NotNull,
|
||||
},
|
||||
ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Options(vec![SqlOption {
|
||||
name: Ident::new("description"),
|
||||
value: Expr::Value(Value::DoubleQuotedString(
|
||||
"field x".to_string()
|
||||
)),
|
||||
},])
|
||||
},
|
||||
]
|
||||
},
|
||||
ColumnDef {
|
||||
name: Ident::new("y"),
|
||||
data_type: DataType::Bool,
|
||||
collation: None,
|
||||
options: vec![ColumnOptionDef {
|
||||
name: None,
|
||||
option: ColumnOption::Options(vec![SqlOption {
|
||||
name: Ident::new("description"),
|
||||
value: Expr::Value(Value::DoubleQuotedString(
|
||||
"field y".to_string()
|
||||
)),
|
||||
},])
|
||||
}]
|
||||
},
|
||||
],
|
||||
columns
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
Some(Box::new(Expr::Identifier(Ident::new("_PARTITIONDATE")))),
|
||||
Some(vec![Ident::new("userid"), Ident::new("age"),]),
|
||||
Some(vec![
|
||||
SqlOption {
|
||||
name: Ident::new("partition_expiration_days"),
|
||||
value: Expr::Value(number("1")),
|
||||
},
|
||||
SqlOption {
|
||||
name: Ident::new("description"),
|
||||
value: Expr::Value(Value::DoubleQuotedString(
|
||||
"table option description".to_string()
|
||||
)),
|
||||
},
|
||||
])
|
||||
),
|
||||
(partition_by, cluster_by, options)
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let sql = concat!(
|
||||
"CREATE TABLE mydataset.newtable ",
|
||||
r#"(x INT64 NOT NULL OPTIONS(description = "field x"), "#,
|
||||
r#"y BOOL OPTIONS(description = "field y")) "#,
|
||||
"CLUSTER BY userid ",
|
||||
r#"OPTIONS(partition_expiration_days = 1, "#,
|
||||
r#"description = "table option description")"#
|
||||
);
|
||||
bigquery().verified_stmt(sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_nested_data_types() {
|
||||
let sql = "CREATE TABLE table (x STRUCT<a ARRAY<INT64>, b BYTES(42)>, y ARRAY<STRUCT<INT64>>)";
|
||||
|
|
|
@ -2997,11 +2997,11 @@ fn parse_create_table_with_options() {
|
|||
vec![
|
||||
SqlOption {
|
||||
name: "foo".into(),
|
||||
value: Value::SingleQuotedString("bar".into()),
|
||||
value: Expr::Value(Value::SingleQuotedString("bar".into())),
|
||||
},
|
||||
SqlOption {
|
||||
name: "a".into(),
|
||||
value: number("123"),
|
||||
value: Expr::Value(number("123")),
|
||||
},
|
||||
],
|
||||
with_options
|
||||
|
@ -3260,11 +3260,11 @@ fn parse_alter_view_with_options() {
|
|||
vec![
|
||||
SqlOption {
|
||||
name: "foo".into(),
|
||||
value: Value::SingleQuotedString("bar".into()),
|
||||
value: Expr::Value(Value::SingleQuotedString("bar".into())),
|
||||
},
|
||||
SqlOption {
|
||||
name: "a".into(),
|
||||
value: number("123"),
|
||||
value: Expr::Value(number("123")),
|
||||
},
|
||||
],
|
||||
with_options
|
||||
|
@ -5589,18 +5589,18 @@ fn parse_create_view() {
|
|||
query,
|
||||
or_replace,
|
||||
materialized,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding: late_binding,
|
||||
if_not_exists,
|
||||
temporary,
|
||||
} => {
|
||||
assert_eq!("myschema.myview", name.to_string());
|
||||
assert_eq!(Vec::<Ident>::new(), columns);
|
||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||
assert!(!materialized);
|
||||
assert!(!or_replace);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert_eq!(cluster_by, vec![]);
|
||||
assert!(!late_binding);
|
||||
assert!(!if_not_exists);
|
||||
|
@ -5614,19 +5614,19 @@ fn parse_create_view() {
|
|||
fn parse_create_view_with_options() {
|
||||
let sql = "CREATE VIEW v WITH (foo = 'bar', a = 123) AS SELECT 1";
|
||||
match verified_stmt(sql) {
|
||||
Statement::CreateView { with_options, .. } => {
|
||||
Statement::CreateView { options, .. } => {
|
||||
assert_eq!(
|
||||
vec![
|
||||
CreateTableOptions::With(vec![
|
||||
SqlOption {
|
||||
name: "foo".into(),
|
||||
value: Value::SingleQuotedString("bar".into()),
|
||||
value: Expr::Value(Value::SingleQuotedString("bar".into())),
|
||||
},
|
||||
SqlOption {
|
||||
name: "a".into(),
|
||||
value: number("123"),
|
||||
value: Expr::Value(number("123")),
|
||||
},
|
||||
],
|
||||
with_options
|
||||
]),
|
||||
options
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -5641,7 +5641,7 @@ fn parse_create_view_with_columns() {
|
|||
name,
|
||||
columns,
|
||||
or_replace,
|
||||
with_options,
|
||||
options,
|
||||
query,
|
||||
materialized,
|
||||
cluster_by,
|
||||
|
@ -5650,8 +5650,17 @@ fn parse_create_view_with_columns() {
|
|||
temporary,
|
||||
} => {
|
||||
assert_eq!("v", name.to_string());
|
||||
assert_eq!(columns, vec![Ident::new("has"), Ident::new("cols")]);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(
|
||||
columns,
|
||||
vec![Ident::new("has"), Ident::new("cols"),]
|
||||
.into_iter()
|
||||
.map(|name| ViewColumnDef {
|
||||
name,
|
||||
options: None
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert_eq!("SELECT 1, 2", query.to_string());
|
||||
assert!(!materialized);
|
||||
assert!(!or_replace);
|
||||
|
@ -5674,18 +5683,18 @@ fn parse_create_view_temporary() {
|
|||
query,
|
||||
or_replace,
|
||||
materialized,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding: late_binding,
|
||||
if_not_exists,
|
||||
temporary,
|
||||
} => {
|
||||
assert_eq!("myschema.myview", name.to_string());
|
||||
assert_eq!(Vec::<Ident>::new(), columns);
|
||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||
assert!(!materialized);
|
||||
assert!(!or_replace);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert_eq!(cluster_by, vec![]);
|
||||
assert!(!late_binding);
|
||||
assert!(!if_not_exists);
|
||||
|
@ -5703,7 +5712,7 @@ fn parse_create_or_replace_view() {
|
|||
name,
|
||||
columns,
|
||||
or_replace,
|
||||
with_options,
|
||||
options,
|
||||
query,
|
||||
materialized,
|
||||
cluster_by,
|
||||
|
@ -5713,7 +5722,7 @@ fn parse_create_or_replace_view() {
|
|||
} => {
|
||||
assert_eq!("v", name.to_string());
|
||||
assert_eq!(columns, vec![]);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert_eq!("SELECT 1", query.to_string());
|
||||
assert!(!materialized);
|
||||
assert!(or_replace);
|
||||
|
@ -5738,7 +5747,7 @@ fn parse_create_or_replace_materialized_view() {
|
|||
name,
|
||||
columns,
|
||||
or_replace,
|
||||
with_options,
|
||||
options,
|
||||
query,
|
||||
materialized,
|
||||
cluster_by,
|
||||
|
@ -5748,7 +5757,7 @@ fn parse_create_or_replace_materialized_view() {
|
|||
} => {
|
||||
assert_eq!("v", name.to_string());
|
||||
assert_eq!(columns, vec![]);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert_eq!("SELECT 1", query.to_string());
|
||||
assert!(materialized);
|
||||
assert!(or_replace);
|
||||
|
@ -5771,17 +5780,17 @@ fn parse_create_materialized_view() {
|
|||
columns,
|
||||
query,
|
||||
materialized,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding: late_binding,
|
||||
if_not_exists,
|
||||
temporary,
|
||||
} => {
|
||||
assert_eq!("myschema.myview", name.to_string());
|
||||
assert_eq!(Vec::<Ident>::new(), columns);
|
||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||
assert!(materialized);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert!(!or_replace);
|
||||
assert_eq!(cluster_by, vec![]);
|
||||
assert!(!late_binding);
|
||||
|
@ -5802,17 +5811,17 @@ fn parse_create_materialized_view_with_cluster_by() {
|
|||
columns,
|
||||
query,
|
||||
materialized,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding: late_binding,
|
||||
if_not_exists,
|
||||
temporary,
|
||||
} => {
|
||||
assert_eq!("myschema.myview", name.to_string());
|
||||
assert_eq!(Vec::<Ident>::new(), columns);
|
||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||
assert!(materialized);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert!(!or_replace);
|
||||
assert_eq!(cluster_by, vec![Ident::new("foo")]);
|
||||
assert!(!late_binding);
|
||||
|
@ -7439,11 +7448,11 @@ fn parse_cache_table() {
|
|||
options: vec![
|
||||
SqlOption {
|
||||
name: Ident::with_quote('\'', "K1"),
|
||||
value: Value::SingleQuotedString("V1".into()),
|
||||
value: Expr::Value(Value::SingleQuotedString("V1".into())),
|
||||
},
|
||||
SqlOption {
|
||||
name: Ident::with_quote('\'', "K2"),
|
||||
value: number("0.88"),
|
||||
value: Expr::Value(number("0.88")),
|
||||
},
|
||||
],
|
||||
query: None,
|
||||
|
@ -7464,11 +7473,11 @@ fn parse_cache_table() {
|
|||
options: vec![
|
||||
SqlOption {
|
||||
name: Ident::with_quote('\'', "K1"),
|
||||
value: Value::SingleQuotedString("V1".into()),
|
||||
value: Expr::Value(Value::SingleQuotedString("V1".into())),
|
||||
},
|
||||
SqlOption {
|
||||
name: Ident::with_quote('\'', "K2"),
|
||||
value: number("0.88"),
|
||||
value: Expr::Value(number("0.88")),
|
||||
},
|
||||
],
|
||||
query: Some(query.clone()),
|
||||
|
@ -7489,11 +7498,11 @@ fn parse_cache_table() {
|
|||
options: vec![
|
||||
SqlOption {
|
||||
name: Ident::with_quote('\'', "K1"),
|
||||
value: Value::SingleQuotedString("V1".into()),
|
||||
value: Expr::Value(Value::SingleQuotedString("V1".into())),
|
||||
},
|
||||
SqlOption {
|
||||
name: Ident::with_quote('\'', "K2"),
|
||||
value: number("0.88"),
|
||||
value: Expr::Value(number("0.88")),
|
||||
},
|
||||
],
|
||||
query: Some(query.clone()),
|
||||
|
|
|
@ -459,15 +459,15 @@ fn parse_create_table_with_defaults() {
|
|||
vec![
|
||||
SqlOption {
|
||||
name: "fillfactor".into(),
|
||||
value: number("20")
|
||||
value: Expr::Value(number("20"))
|
||||
},
|
||||
SqlOption {
|
||||
name: "user_catalog_table".into(),
|
||||
value: Value::Boolean(true)
|
||||
value: Expr::Value(Value::Boolean(true))
|
||||
},
|
||||
SqlOption {
|
||||
name: "autovacuum_vacuum_threshold".into(),
|
||||
value: number("100")
|
||||
value: Expr::Value(number("100"))
|
||||
},
|
||||
]
|
||||
);
|
||||
|
|
|
@ -165,18 +165,18 @@ fn parse_create_view_temporary_if_not_exists() {
|
|||
query,
|
||||
or_replace,
|
||||
materialized,
|
||||
with_options,
|
||||
options,
|
||||
cluster_by,
|
||||
with_no_schema_binding: late_binding,
|
||||
if_not_exists,
|
||||
temporary,
|
||||
} => {
|
||||
assert_eq!("myschema.myview", name.to_string());
|
||||
assert_eq!(Vec::<Ident>::new(), columns);
|
||||
assert_eq!(Vec::<ViewColumnDef>::new(), columns);
|
||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||
assert!(!materialized);
|
||||
assert!(!or_replace);
|
||||
assert_eq!(with_options, vec![]);
|
||||
assert_eq!(options, CreateTableOptions::None);
|
||||
assert_eq!(cluster_by, vec![]);
|
||||
assert!(!late_binding);
|
||||
assert!(if_not_exists);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue