mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-10 05:52:13 +00:00
Add support for snowflake exclusive create table options (#1233)
Co-authored-by: Ilson Roberto Balliego Junior <ilson@validio.io>
This commit is contained in:
parent
3c33ac15bd
commit
be77ce50ca
10 changed files with 1029 additions and 31 deletions
114
src/ast/dml.rs
114
src/ast/dml.rs
|
@ -22,10 +22,11 @@ use sqlparser_derive::{Visit, VisitMut};
|
||||||
pub use super::ddl::{ColumnDef, TableConstraint};
|
pub use super::ddl::{ColumnDef, TableConstraint};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
display_comma_separated, display_separated, Expr, FileFormat, FromTable, HiveDistributionStyle,
|
display_comma_separated, display_separated, CommentDef, Expr, FileFormat, FromTable,
|
||||||
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases, MysqlInsertPriority, ObjectName,
|
HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases,
|
||||||
OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, SelectItem, SqlOption,
|
MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query,
|
||||||
SqliteOnConflict, TableEngine, TableWithJoins,
|
RowAccessPolicy, SelectItem, SqlOption, SqliteOnConflict, TableEngine, TableWithJoins, Tag,
|
||||||
|
WrappedCollection,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// CREATE INDEX statement.
|
/// CREATE INDEX statement.
|
||||||
|
@ -57,6 +58,7 @@ pub struct CreateTable {
|
||||||
pub global: Option<bool>,
|
pub global: Option<bool>,
|
||||||
pub if_not_exists: bool,
|
pub if_not_exists: bool,
|
||||||
pub transient: bool,
|
pub transient: bool,
|
||||||
|
pub volatile: bool,
|
||||||
/// Table name
|
/// Table name
|
||||||
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
||||||
pub name: ObjectName,
|
pub name: ObjectName,
|
||||||
|
@ -74,7 +76,7 @@ pub struct CreateTable {
|
||||||
pub like: Option<ObjectName>,
|
pub like: Option<ObjectName>,
|
||||||
pub clone: Option<ObjectName>,
|
pub clone: Option<ObjectName>,
|
||||||
pub engine: Option<TableEngine>,
|
pub engine: Option<TableEngine>,
|
||||||
pub comment: Option<String>,
|
pub comment: Option<CommentDef>,
|
||||||
pub auto_increment_offset: Option<u32>,
|
pub auto_increment_offset: Option<u32>,
|
||||||
pub default_charset: Option<String>,
|
pub default_charset: Option<String>,
|
||||||
pub collation: Option<String>,
|
pub collation: Option<String>,
|
||||||
|
@ -94,7 +96,7 @@ pub struct CreateTable {
|
||||||
pub partition_by: Option<Box<Expr>>,
|
pub partition_by: Option<Box<Expr>>,
|
||||||
/// BigQuery: Table clustering column list.
|
/// BigQuery: Table clustering column list.
|
||||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||||
pub cluster_by: Option<Vec<Ident>>,
|
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||||
/// BigQuery: Table options list.
|
/// BigQuery: Table options list.
|
||||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||||
pub options: Option<Vec<SqlOption>>,
|
pub options: Option<Vec<SqlOption>>,
|
||||||
|
@ -102,6 +104,33 @@ pub struct CreateTable {
|
||||||
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
|
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
|
||||||
/// then strict typing rules apply to that table.
|
/// then strict typing rules apply to that table.
|
||||||
pub strict: bool,
|
pub strict: bool,
|
||||||
|
/// Snowflake "COPY GRANTS" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub copy_grants: bool,
|
||||||
|
/// Snowflake "ENABLE_SCHEMA_EVOLUTION" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub enable_schema_evolution: Option<bool>,
|
||||||
|
/// Snowflake "CHANGE_TRACKING" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub change_tracking: Option<bool>,
|
||||||
|
/// Snowflake "DATA_RETENTION_TIME_IN_DAYS" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub data_retention_time_in_days: Option<u64>,
|
||||||
|
/// Snowflake "MAX_DATA_EXTENSION_TIME_IN_DAYS" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub max_data_extension_time_in_days: Option<u64>,
|
||||||
|
/// Snowflake "DEFAULT_DDL_COLLATION" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub default_ddl_collation: Option<String>,
|
||||||
|
/// Snowflake "WITH AGGREGATION POLICY" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub with_aggregation_policy: Option<ObjectName>,
|
||||||
|
/// Snowflake "WITH ROW ACCESS POLICY" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub with_row_access_policy: Option<RowAccessPolicy>,
|
||||||
|
/// Snowflake "WITH TAG" clause
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub with_tags: Option<Vec<Tag>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for CreateTable {
|
impl Display for CreateTable {
|
||||||
|
@ -115,7 +144,7 @@ impl Display for CreateTable {
|
||||||
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"CREATE {or_replace}{external}{global}{temporary}{transient}TABLE {if_not_exists}{name}",
|
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}TABLE {if_not_exists}{name}",
|
||||||
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
|
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
|
||||||
external = if self.external { "EXTERNAL " } else { "" },
|
external = if self.external { "EXTERNAL " } else { "" },
|
||||||
global = self.global
|
global = self.global
|
||||||
|
@ -130,6 +159,7 @@ impl Display for CreateTable {
|
||||||
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
|
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||||
temporary = if self.temporary { "TEMPORARY " } else { "" },
|
temporary = if self.temporary { "TEMPORARY " } else { "" },
|
||||||
transient = if self.transient { "TRANSIENT " } else { "" },
|
transient = if self.transient { "TRANSIENT " } else { "" },
|
||||||
|
volatile = if self.volatile { "VOLATILE " } else { "" },
|
||||||
name = self.name,
|
name = self.name,
|
||||||
)?;
|
)?;
|
||||||
if let Some(on_cluster) = &self.on_cluster {
|
if let Some(on_cluster) = &self.on_cluster {
|
||||||
|
@ -260,9 +290,17 @@ impl Display for CreateTable {
|
||||||
if let Some(engine) = &self.engine {
|
if let Some(engine) = &self.engine {
|
||||||
write!(f, " ENGINE={engine}")?;
|
write!(f, " ENGINE={engine}")?;
|
||||||
}
|
}
|
||||||
if let Some(comment) = &self.comment {
|
if let Some(comment_def) = &self.comment {
|
||||||
write!(f, " COMMENT '{comment}'")?;
|
match comment_def {
|
||||||
|
CommentDef::WithEq(comment) => {
|
||||||
|
write!(f, " COMMENT = '{comment}'")?;
|
||||||
|
}
|
||||||
|
CommentDef::WithoutEq(comment) => {
|
||||||
|
write!(f, " COMMENT '{comment}'")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(auto_increment_offset) = self.auto_increment_offset {
|
if let Some(auto_increment_offset) = self.auto_increment_offset {
|
||||||
write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
|
write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
|
||||||
}
|
}
|
||||||
|
@ -276,12 +314,9 @@ impl Display for CreateTable {
|
||||||
write!(f, " PARTITION BY {partition_by}")?;
|
write!(f, " PARTITION BY {partition_by}")?;
|
||||||
}
|
}
|
||||||
if let Some(cluster_by) = self.cluster_by.as_ref() {
|
if let Some(cluster_by) = self.cluster_by.as_ref() {
|
||||||
write!(
|
write!(f, " CLUSTER BY {cluster_by}")?;
|
||||||
f,
|
|
||||||
" CLUSTER BY {}",
|
|
||||||
display_comma_separated(cluster_by.as_slice())
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(options) = self.options.as_ref() {
|
if let Some(options) = self.options.as_ref() {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
@ -289,6 +324,57 @@ impl Display for CreateTable {
|
||||||
display_comma_separated(options.as_slice())
|
display_comma_separated(options.as_slice())
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.copy_grants {
|
||||||
|
write!(f, " COPY GRANTS")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(is_enabled) = self.enable_schema_evolution {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" ENABLE_SCHEMA_EVOLUTION={}",
|
||||||
|
if is_enabled { "TRUE" } else { "FALSE" }
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(is_enabled) = self.change_tracking {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" CHANGE_TRACKING={}",
|
||||||
|
if is_enabled { "TRUE" } else { "FALSE" }
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(data_retention_time_in_days) = self.data_retention_time_in_days {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" DATA_RETENTION_TIME_IN_DAYS={data_retention_time_in_days}",
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(max_data_extension_time_in_days) = self.max_data_extension_time_in_days {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" MAX_DATA_EXTENSION_TIME_IN_DAYS={max_data_extension_time_in_days}",
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default_ddl_collation) = &self.default_ddl_collation {
|
||||||
|
write!(f, " DEFAULT_DDL_COLLATION='{default_ddl_collation}'",)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(with_aggregation_policy) = &self.with_aggregation_policy {
|
||||||
|
write!(f, " WITH AGGREGATION POLICY {with_aggregation_policy}",)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(row_access_policy) = &self.with_row_access_policy {
|
||||||
|
write!(f, " {row_access_policy}",)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tag) = &self.with_tags {
|
||||||
|
write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(query) = &self.query {
|
if let Some(query) = &self.query {
|
||||||
write!(f, " AS {query}")?;
|
write!(f, " AS {query}")?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,9 @@ use sqlparser_derive::{Visit, VisitMut};
|
||||||
|
|
||||||
use super::super::dml::CreateTable;
|
use super::super::dml::CreateTable;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
|
ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName,
|
||||||
OneOrManyWithParens, Query, SqlOption, Statement, TableConstraint, TableEngine,
|
OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement, TableConstraint,
|
||||||
|
TableEngine, Tag, WrappedCollection,
|
||||||
};
|
};
|
||||||
use crate::parser::ParserError;
|
use crate::parser::ParserError;
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ pub struct CreateTableBuilder {
|
||||||
pub global: Option<bool>,
|
pub global: Option<bool>,
|
||||||
pub if_not_exists: bool,
|
pub if_not_exists: bool,
|
||||||
pub transient: bool,
|
pub transient: bool,
|
||||||
|
pub volatile: bool,
|
||||||
pub name: ObjectName,
|
pub name: ObjectName,
|
||||||
pub columns: Vec<ColumnDef>,
|
pub columns: Vec<ColumnDef>,
|
||||||
pub constraints: Vec<TableConstraint>,
|
pub constraints: Vec<TableConstraint>,
|
||||||
|
@ -66,7 +68,7 @@ pub struct CreateTableBuilder {
|
||||||
pub like: Option<ObjectName>,
|
pub like: Option<ObjectName>,
|
||||||
pub clone: Option<ObjectName>,
|
pub clone: Option<ObjectName>,
|
||||||
pub engine: Option<TableEngine>,
|
pub engine: Option<TableEngine>,
|
||||||
pub comment: Option<String>,
|
pub comment: Option<CommentDef>,
|
||||||
pub auto_increment_offset: Option<u32>,
|
pub auto_increment_offset: Option<u32>,
|
||||||
pub default_charset: Option<String>,
|
pub default_charset: Option<String>,
|
||||||
pub collation: Option<String>,
|
pub collation: Option<String>,
|
||||||
|
@ -75,9 +77,18 @@ pub struct CreateTableBuilder {
|
||||||
pub primary_key: Option<Box<Expr>>,
|
pub primary_key: Option<Box<Expr>>,
|
||||||
pub order_by: Option<OneOrManyWithParens<Expr>>,
|
pub order_by: Option<OneOrManyWithParens<Expr>>,
|
||||||
pub partition_by: Option<Box<Expr>>,
|
pub partition_by: Option<Box<Expr>>,
|
||||||
pub cluster_by: Option<Vec<Ident>>,
|
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||||
pub options: Option<Vec<SqlOption>>,
|
pub options: Option<Vec<SqlOption>>,
|
||||||
pub strict: bool,
|
pub strict: bool,
|
||||||
|
pub copy_grants: bool,
|
||||||
|
pub enable_schema_evolution: Option<bool>,
|
||||||
|
pub change_tracking: Option<bool>,
|
||||||
|
pub data_retention_time_in_days: Option<u64>,
|
||||||
|
pub max_data_extension_time_in_days: Option<u64>,
|
||||||
|
pub default_ddl_collation: Option<String>,
|
||||||
|
pub with_aggregation_policy: Option<ObjectName>,
|
||||||
|
pub with_row_access_policy: Option<RowAccessPolicy>,
|
||||||
|
pub with_tags: Option<Vec<Tag>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateTableBuilder {
|
impl CreateTableBuilder {
|
||||||
|
@ -89,6 +100,7 @@ impl CreateTableBuilder {
|
||||||
global: None,
|
global: None,
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
transient: false,
|
transient: false,
|
||||||
|
volatile: false,
|
||||||
name,
|
name,
|
||||||
columns: vec![],
|
columns: vec![],
|
||||||
constraints: vec![],
|
constraints: vec![],
|
||||||
|
@ -115,6 +127,15 @@ impl CreateTableBuilder {
|
||||||
cluster_by: None,
|
cluster_by: None,
|
||||||
options: None,
|
options: None,
|
||||||
strict: false,
|
strict: false,
|
||||||
|
copy_grants: false,
|
||||||
|
enable_schema_evolution: None,
|
||||||
|
change_tracking: None,
|
||||||
|
data_retention_time_in_days: None,
|
||||||
|
max_data_extension_time_in_days: None,
|
||||||
|
default_ddl_collation: None,
|
||||||
|
with_aggregation_policy: None,
|
||||||
|
with_row_access_policy: None,
|
||||||
|
with_tags: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn or_replace(mut self, or_replace: bool) -> Self {
|
pub fn or_replace(mut self, or_replace: bool) -> Self {
|
||||||
|
@ -147,6 +168,11 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn volatile(mut self, volatile: bool) -> Self {
|
||||||
|
self.volatile = volatile;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn columns(mut self, columns: Vec<ColumnDef>) -> Self {
|
pub fn columns(mut self, columns: Vec<ColumnDef>) -> Self {
|
||||||
self.columns = columns;
|
self.columns = columns;
|
||||||
self
|
self
|
||||||
|
@ -210,7 +236,7 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn comment(mut self, comment: Option<String>) -> Self {
|
pub fn comment(mut self, comment: Option<CommentDef>) -> Self {
|
||||||
self.comment = comment;
|
self.comment = comment;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -255,7 +281,7 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cluster_by(mut self, cluster_by: Option<Vec<Ident>>) -> Self {
|
pub fn cluster_by(mut self, cluster_by: Option<WrappedCollection<Vec<Ident>>>) -> Self {
|
||||||
self.cluster_by = cluster_by;
|
self.cluster_by = cluster_by;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -270,6 +296,57 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn copy_grants(mut self, copy_grants: bool) -> Self {
|
||||||
|
self.copy_grants = copy_grants;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_schema_evolution(mut self, enable_schema_evolution: Option<bool>) -> Self {
|
||||||
|
self.enable_schema_evolution = enable_schema_evolution;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_tracking(mut self, change_tracking: Option<bool>) -> Self {
|
||||||
|
self.change_tracking = change_tracking;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data_retention_time_in_days(mut self, data_retention_time_in_days: Option<u64>) -> Self {
|
||||||
|
self.data_retention_time_in_days = data_retention_time_in_days;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_data_extension_time_in_days(
|
||||||
|
mut self,
|
||||||
|
max_data_extension_time_in_days: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
self.max_data_extension_time_in_days = max_data_extension_time_in_days;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_ddl_collation(mut self, default_ddl_collation: Option<String>) -> Self {
|
||||||
|
self.default_ddl_collation = default_ddl_collation;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_aggregation_policy(mut self, with_aggregation_policy: Option<ObjectName>) -> Self {
|
||||||
|
self.with_aggregation_policy = with_aggregation_policy;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_row_access_policy(
|
||||||
|
mut self,
|
||||||
|
with_row_access_policy: Option<RowAccessPolicy>,
|
||||||
|
) -> Self {
|
||||||
|
self.with_row_access_policy = with_row_access_policy;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_tags(mut self, with_tags: Option<Vec<Tag>>) -> Self {
|
||||||
|
self.with_tags = with_tags;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Statement {
|
pub fn build(self) -> Statement {
|
||||||
Statement::CreateTable(CreateTable {
|
Statement::CreateTable(CreateTable {
|
||||||
or_replace: self.or_replace,
|
or_replace: self.or_replace,
|
||||||
|
@ -278,6 +355,7 @@ impl CreateTableBuilder {
|
||||||
global: self.global,
|
global: self.global,
|
||||||
if_not_exists: self.if_not_exists,
|
if_not_exists: self.if_not_exists,
|
||||||
transient: self.transient,
|
transient: self.transient,
|
||||||
|
volatile: self.volatile,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
columns: self.columns,
|
columns: self.columns,
|
||||||
constraints: self.constraints,
|
constraints: self.constraints,
|
||||||
|
@ -304,6 +382,15 @@ impl CreateTableBuilder {
|
||||||
cluster_by: self.cluster_by,
|
cluster_by: self.cluster_by,
|
||||||
options: self.options,
|
options: self.options,
|
||||||
strict: self.strict,
|
strict: self.strict,
|
||||||
|
copy_grants: self.copy_grants,
|
||||||
|
enable_schema_evolution: self.enable_schema_evolution,
|
||||||
|
change_tracking: self.change_tracking,
|
||||||
|
data_retention_time_in_days: self.data_retention_time_in_days,
|
||||||
|
max_data_extension_time_in_days: self.max_data_extension_time_in_days,
|
||||||
|
default_ddl_collation: self.default_ddl_collation,
|
||||||
|
with_aggregation_policy: self.with_aggregation_policy,
|
||||||
|
with_row_access_policy: self.with_row_access_policy,
|
||||||
|
with_tags: self.with_tags,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,6 +409,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
global,
|
global,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
transient,
|
transient,
|
||||||
|
volatile,
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
@ -348,6 +436,15 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
cluster_by,
|
cluster_by,
|
||||||
options,
|
options,
|
||||||
strict,
|
strict,
|
||||||
|
copy_grants,
|
||||||
|
enable_schema_evolution,
|
||||||
|
change_tracking,
|
||||||
|
data_retention_time_in_days,
|
||||||
|
max_data_extension_time_in_days,
|
||||||
|
default_ddl_collation,
|
||||||
|
with_aggregation_policy,
|
||||||
|
with_row_access_policy,
|
||||||
|
with_tags,
|
||||||
}) => Ok(Self {
|
}) => Ok(Self {
|
||||||
or_replace,
|
or_replace,
|
||||||
temporary,
|
temporary,
|
||||||
|
@ -381,6 +478,16 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
cluster_by,
|
cluster_by,
|
||||||
options,
|
options,
|
||||||
strict,
|
strict,
|
||||||
|
copy_grants,
|
||||||
|
enable_schema_evolution,
|
||||||
|
change_tracking,
|
||||||
|
data_retention_time_in_days,
|
||||||
|
max_data_extension_time_in_days,
|
||||||
|
default_ddl_collation,
|
||||||
|
with_aggregation_policy,
|
||||||
|
with_row_access_policy,
|
||||||
|
with_tags,
|
||||||
|
volatile,
|
||||||
}),
|
}),
|
||||||
_ => Err(ParserError::ParserError(format!(
|
_ => Err(ParserError::ParserError(format!(
|
||||||
"Expected create table statement, but received: {stmt}"
|
"Expected create table statement, but received: {stmt}"
|
||||||
|
@ -393,7 +500,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct BigQueryTableConfiguration {
|
pub(crate) struct BigQueryTableConfiguration {
|
||||||
pub partition_by: Option<Box<Expr>>,
|
pub partition_by: Option<Box<Expr>>,
|
||||||
pub cluster_by: Option<Vec<Ident>>,
|
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||||
pub options: Option<Vec<SqlOption>>,
|
pub options: Option<Vec<SqlOption>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
111
src/ast/mod.rs
111
src/ast/mod.rs
|
@ -6338,6 +6338,117 @@ impl Display for TableEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Snowflake `WITH ROW ACCESS POLICY policy_name ON (identifier, ...)`
|
||||||
|
///
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
/// <https://docs.snowflake.com/en/user-guide/security-row-intro>
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct RowAccessPolicy {
|
||||||
|
pub policy: ObjectName,
|
||||||
|
pub on: Vec<Ident>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RowAccessPolicy {
|
||||||
|
pub fn new(policy: ObjectName, on: Vec<Ident>) -> Self {
|
||||||
|
Self { policy, on }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for RowAccessPolicy {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"WITH ROW ACCESS POLICY {} ON ({})",
|
||||||
|
self.policy,
|
||||||
|
display_comma_separated(self.on.as_slice())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snowflake `WITH TAG ( tag_name = '<tag_value>', ...)`
|
||||||
|
///
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct Tag {
|
||||||
|
pub key: Ident,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
pub fn new(key: Ident, value: String) -> Self {
|
||||||
|
Self { key, value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Tag {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}='{}'", self.key, self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to indicate if a comment includes the `=` in the display form
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum CommentDef {
|
||||||
|
/// Includes `=` when printing the comment, as `COMMENT = 'comment'`
|
||||||
|
/// Does not include `=` when printing the comment, as `COMMENT 'comment'`
|
||||||
|
WithEq(String),
|
||||||
|
WithoutEq(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CommentDef {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
CommentDef::WithEq(comment) | CommentDef::WithoutEq(comment) => write!(f, "{comment}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to indicate if a collection should be wrapped by a symbol in the display form
|
||||||
|
///
|
||||||
|
/// [`Display`] is implemented for every [`Vec<T>`] where `T: Display`.
|
||||||
|
/// The string output is a comma separated list for the vec items
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use sqlparser::ast::WrappedCollection;
|
||||||
|
/// let items = WrappedCollection::Parentheses(vec!["one", "two", "three"]);
|
||||||
|
/// assert_eq!("(one, two, three)", items.to_string());
|
||||||
|
///
|
||||||
|
/// let items = WrappedCollection::NoWrapping(vec!["one", "two", "three"]);
|
||||||
|
/// assert_eq!("one, two, three", items.to_string());
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum WrappedCollection<T> {
|
||||||
|
/// Print the collection without wrapping symbols, as `item, item, item`
|
||||||
|
NoWrapping(T),
|
||||||
|
/// Wraps the collection in Parentheses, as `(item, item, item)`
|
||||||
|
Parentheses(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Display for WrappedCollection<Vec<T>>
|
||||||
|
where
|
||||||
|
T: Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
WrappedCollection::NoWrapping(inner) => {
|
||||||
|
write!(f, "{}", display_comma_separated(inner.as_slice()))
|
||||||
|
}
|
||||||
|
WrappedCollection::Parentheses(inner) => {
|
||||||
|
write!(f, "({})", display_comma_separated(inner.as_slice()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -12,11 +12,14 @@
|
||||||
|
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use crate::alloc::string::ToString;
|
use crate::alloc::string::ToString;
|
||||||
|
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||||
use crate::ast::helpers::stmt_data_loading::{
|
use crate::ast::helpers::stmt_data_loading::{
|
||||||
DataLoadingOption, DataLoadingOptionType, DataLoadingOptions, StageLoadSelectItem,
|
DataLoadingOption, DataLoadingOptionType, DataLoadingOptions, StageLoadSelectItem,
|
||||||
StageParamsObject,
|
StageParamsObject,
|
||||||
};
|
};
|
||||||
use crate::ast::{Ident, ObjectName, Statement};
|
use crate::ast::{
|
||||||
|
CommentDef, Ident, ObjectName, RowAccessPolicy, Statement, Tag, WrappedCollection,
|
||||||
|
};
|
||||||
use crate::dialect::Dialect;
|
use crate::dialect::Dialect;
|
||||||
use crate::keywords::Keyword;
|
use crate::keywords::Keyword;
|
||||||
use crate::parser::{Parser, ParserError};
|
use crate::parser::{Parser, ParserError};
|
||||||
|
@ -91,12 +94,36 @@ impl Dialect for SnowflakeDialect {
|
||||||
// possibly CREATE STAGE
|
// possibly CREATE STAGE
|
||||||
//[ OR REPLACE ]
|
//[ OR REPLACE ]
|
||||||
let or_replace = parser.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
|
let or_replace = parser.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
|
||||||
//[ TEMPORARY ]
|
// LOCAL | GLOBAL
|
||||||
let temporary = parser.parse_keyword(Keyword::TEMPORARY);
|
let global = match parser.parse_one_of_keywords(&[Keyword::LOCAL, Keyword::GLOBAL]) {
|
||||||
|
Some(Keyword::LOCAL) => Some(false),
|
||||||
|
Some(Keyword::GLOBAL) => Some(true),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut temporary = false;
|
||||||
|
let mut volatile = false;
|
||||||
|
let mut transient = false;
|
||||||
|
|
||||||
|
match parser.parse_one_of_keywords(&[
|
||||||
|
Keyword::TEMP,
|
||||||
|
Keyword::TEMPORARY,
|
||||||
|
Keyword::VOLATILE,
|
||||||
|
Keyword::TRANSIENT,
|
||||||
|
]) {
|
||||||
|
Some(Keyword::TEMP | Keyword::TEMPORARY) => temporary = true,
|
||||||
|
Some(Keyword::VOLATILE) => volatile = true,
|
||||||
|
Some(Keyword::TRANSIENT) => transient = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
if parser.parse_keyword(Keyword::STAGE) {
|
if parser.parse_keyword(Keyword::STAGE) {
|
||||||
// OK - this is CREATE STAGE statement
|
// OK - this is CREATE STAGE statement
|
||||||
return Some(parse_create_stage(or_replace, temporary, parser));
|
return Some(parse_create_stage(or_replace, temporary, parser));
|
||||||
|
} else if parser.parse_keyword(Keyword::TABLE) {
|
||||||
|
return Some(parse_create_table(
|
||||||
|
or_replace, global, temporary, volatile, transient, parser,
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
// need to go back with the cursor
|
// need to go back with the cursor
|
||||||
let mut back = 1;
|
let mut back = 1;
|
||||||
|
@ -120,6 +147,196 @@ impl Dialect for SnowflakeDialect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse snowflake create table statement.
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
|
pub fn parse_create_table(
|
||||||
|
or_replace: bool,
|
||||||
|
global: Option<bool>,
|
||||||
|
temporary: bool,
|
||||||
|
volatile: bool,
|
||||||
|
transient: bool,
|
||||||
|
parser: &mut Parser,
|
||||||
|
) -> Result<Statement, ParserError> {
|
||||||
|
let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
|
let table_name = parser.parse_object_name(false)?;
|
||||||
|
|
||||||
|
let mut builder = CreateTableBuilder::new(table_name)
|
||||||
|
.or_replace(or_replace)
|
||||||
|
.if_not_exists(if_not_exists)
|
||||||
|
.temporary(temporary)
|
||||||
|
.transient(transient)
|
||||||
|
.volatile(volatile)
|
||||||
|
.global(global)
|
||||||
|
.hive_formats(Some(Default::default()));
|
||||||
|
|
||||||
|
// Snowflake does not enforce order of the parameters in the statement. The parser needs to
|
||||||
|
// parse the statement in a loop.
|
||||||
|
//
|
||||||
|
// "CREATE TABLE x COPY GRANTS (c INT)" and "CREATE TABLE x (c INT) COPY GRANTS" are both
|
||||||
|
// accepted by Snowflake
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let next_token = parser.next_token();
|
||||||
|
match &next_token.token {
|
||||||
|
Token::Word(word) => match word.keyword {
|
||||||
|
Keyword::COPY => {
|
||||||
|
parser.expect_keyword(Keyword::GRANTS)?;
|
||||||
|
builder = builder.copy_grants(true);
|
||||||
|
}
|
||||||
|
Keyword::COMMENT => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let next_token = parser.next_token();
|
||||||
|
let comment = match next_token.token {
|
||||||
|
Token::SingleQuotedString(str) => Some(CommentDef::WithEq(str)),
|
||||||
|
_ => parser.expected("comment", next_token)?,
|
||||||
|
};
|
||||||
|
builder = builder.comment(comment);
|
||||||
|
}
|
||||||
|
Keyword::AS => {
|
||||||
|
let query = parser.parse_boxed_query()?;
|
||||||
|
builder = builder.query(Some(query));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Keyword::CLONE => {
|
||||||
|
let clone = parser.parse_object_name(false).ok();
|
||||||
|
builder = builder.clone_clause(clone);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Keyword::LIKE => {
|
||||||
|
let like = parser.parse_object_name(false).ok();
|
||||||
|
builder = builder.like(like);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Keyword::CLUSTER => {
|
||||||
|
parser.expect_keyword(Keyword::BY)?;
|
||||||
|
parser.expect_token(&Token::LParen)?;
|
||||||
|
let cluster_by = Some(WrappedCollection::Parentheses(
|
||||||
|
parser.parse_comma_separated(|p| p.parse_identifier(false))?,
|
||||||
|
));
|
||||||
|
parser.expect_token(&Token::RParen)?;
|
||||||
|
|
||||||
|
builder = builder.cluster_by(cluster_by)
|
||||||
|
}
|
||||||
|
Keyword::ENABLE_SCHEMA_EVOLUTION => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let enable_schema_evolution =
|
||||||
|
match parser.parse_one_of_keywords(&[Keyword::TRUE, Keyword::FALSE]) {
|
||||||
|
Some(Keyword::TRUE) => true,
|
||||||
|
Some(Keyword::FALSE) => false,
|
||||||
|
_ => {
|
||||||
|
return parser.expected("TRUE or FALSE", next_token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
builder = builder.enable_schema_evolution(Some(enable_schema_evolution));
|
||||||
|
}
|
||||||
|
Keyword::CHANGE_TRACKING => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let change_tracking =
|
||||||
|
match parser.parse_one_of_keywords(&[Keyword::TRUE, Keyword::FALSE]) {
|
||||||
|
Some(Keyword::TRUE) => true,
|
||||||
|
Some(Keyword::FALSE) => false,
|
||||||
|
_ => {
|
||||||
|
return parser.expected("TRUE or FALSE", next_token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
builder = builder.change_tracking(Some(change_tracking));
|
||||||
|
}
|
||||||
|
Keyword::DATA_RETENTION_TIME_IN_DAYS => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let data_retention_time_in_days = parser.parse_literal_uint()?;
|
||||||
|
builder =
|
||||||
|
builder.data_retention_time_in_days(Some(data_retention_time_in_days));
|
||||||
|
}
|
||||||
|
Keyword::MAX_DATA_EXTENSION_TIME_IN_DAYS => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let max_data_extension_time_in_days = parser.parse_literal_uint()?;
|
||||||
|
builder = builder
|
||||||
|
.max_data_extension_time_in_days(Some(max_data_extension_time_in_days));
|
||||||
|
}
|
||||||
|
Keyword::DEFAULT_DDL_COLLATION => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let default_ddl_collation = parser.parse_literal_string()?;
|
||||||
|
builder = builder.default_ddl_collation(Some(default_ddl_collation));
|
||||||
|
}
|
||||||
|
// WITH is optional, we just verify that next token is one of the expected ones and
|
||||||
|
// fallback to the default match statement
|
||||||
|
Keyword::WITH => {
|
||||||
|
parser.expect_one_of_keywords(&[
|
||||||
|
Keyword::AGGREGATION,
|
||||||
|
Keyword::TAG,
|
||||||
|
Keyword::ROW,
|
||||||
|
])?;
|
||||||
|
parser.prev_token();
|
||||||
|
}
|
||||||
|
Keyword::AGGREGATION => {
|
||||||
|
parser.expect_keyword(Keyword::POLICY)?;
|
||||||
|
let aggregation_policy = parser.parse_object_name(false)?;
|
||||||
|
builder = builder.with_aggregation_policy(Some(aggregation_policy));
|
||||||
|
}
|
||||||
|
Keyword::ROW => {
|
||||||
|
parser.expect_keywords(&[Keyword::ACCESS, Keyword::POLICY])?;
|
||||||
|
let policy = parser.parse_object_name(false)?;
|
||||||
|
parser.expect_keyword(Keyword::ON)?;
|
||||||
|
parser.expect_token(&Token::LParen)?;
|
||||||
|
let columns = parser.parse_comma_separated(|p| p.parse_identifier(false))?;
|
||||||
|
parser.expect_token(&Token::RParen)?;
|
||||||
|
|
||||||
|
builder =
|
||||||
|
builder.with_row_access_policy(Some(RowAccessPolicy::new(policy, columns)))
|
||||||
|
}
|
||||||
|
Keyword::TAG => {
|
||||||
|
fn parse_tag(parser: &mut Parser) -> Result<Tag, ParserError> {
|
||||||
|
let name = parser.parse_identifier(false)?;
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let value = parser.parse_literal_string()?;
|
||||||
|
|
||||||
|
Ok(Tag::new(name, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.expect_token(&Token::LParen)?;
|
||||||
|
let tags = parser.parse_comma_separated(parse_tag)?;
|
||||||
|
parser.expect_token(&Token::RParen)?;
|
||||||
|
builder = builder.with_tags(Some(tags));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return parser.expected("end of statement", next_token);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Token::LParen => {
|
||||||
|
parser.prev_token();
|
||||||
|
let (columns, constraints) = parser.parse_columns()?;
|
||||||
|
builder = builder.columns(columns).constraints(constraints);
|
||||||
|
}
|
||||||
|
Token::EOF => {
|
||||||
|
if builder.columns.is_empty() {
|
||||||
|
return Err(ParserError::ParserError(
|
||||||
|
"unexpected end of input".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Token::SemiColon => {
|
||||||
|
if builder.columns.is_empty() {
|
||||||
|
return Err(ParserError::ParserError(
|
||||||
|
"unexpected end of input".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.prev_token();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return parser.expected("end of statement", next_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_create_stage(
|
pub fn parse_create_stage(
|
||||||
or_replace: bool,
|
or_replace: bool,
|
||||||
temporary: bool,
|
temporary: bool,
|
||||||
|
|
|
@ -70,11 +70,13 @@ define_keywords!(
|
||||||
ABORT,
|
ABORT,
|
||||||
ABS,
|
ABS,
|
||||||
ABSOLUTE,
|
ABSOLUTE,
|
||||||
|
ACCESS,
|
||||||
ACTION,
|
ACTION,
|
||||||
ADD,
|
ADD,
|
||||||
ADMIN,
|
ADMIN,
|
||||||
AFTER,
|
AFTER,
|
||||||
AGAINST,
|
AGAINST,
|
||||||
|
AGGREGATION,
|
||||||
ALL,
|
ALL,
|
||||||
ALLOCATE,
|
ALLOCATE,
|
||||||
ALTER,
|
ALTER,
|
||||||
|
@ -138,6 +140,7 @@ define_keywords!(
|
||||||
CENTURY,
|
CENTURY,
|
||||||
CHAIN,
|
CHAIN,
|
||||||
CHANGE,
|
CHANGE,
|
||||||
|
CHANGE_TRACKING,
|
||||||
CHANNEL,
|
CHANNEL,
|
||||||
CHAR,
|
CHAR,
|
||||||
CHARACTER,
|
CHARACTER,
|
||||||
|
@ -201,6 +204,7 @@ define_keywords!(
|
||||||
CYCLE,
|
CYCLE,
|
||||||
DATA,
|
DATA,
|
||||||
DATABASE,
|
DATABASE,
|
||||||
|
DATA_RETENTION_TIME_IN_DAYS,
|
||||||
DATE,
|
DATE,
|
||||||
DATE32,
|
DATE32,
|
||||||
DATETIME,
|
DATETIME,
|
||||||
|
@ -214,6 +218,7 @@ define_keywords!(
|
||||||
DECIMAL,
|
DECIMAL,
|
||||||
DECLARE,
|
DECLARE,
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
DEFAULT_DDL_COLLATION,
|
||||||
DEFERRABLE,
|
DEFERRABLE,
|
||||||
DEFERRED,
|
DEFERRED,
|
||||||
DEFINE,
|
DEFINE,
|
||||||
|
@ -251,6 +256,7 @@ define_keywords!(
|
||||||
ELSE,
|
ELSE,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
ENABLE,
|
ENABLE,
|
||||||
|
ENABLE_SCHEMA_EVOLUTION,
|
||||||
ENCODING,
|
ENCODING,
|
||||||
ENCRYPTION,
|
ENCRYPTION,
|
||||||
END,
|
END,
|
||||||
|
@ -330,6 +336,7 @@ define_keywords!(
|
||||||
GLOBAL,
|
GLOBAL,
|
||||||
GRANT,
|
GRANT,
|
||||||
GRANTED,
|
GRANTED,
|
||||||
|
GRANTS,
|
||||||
GRAPHVIZ,
|
GRAPHVIZ,
|
||||||
GROUP,
|
GROUP,
|
||||||
GROUPING,
|
GROUPING,
|
||||||
|
@ -433,6 +440,7 @@ define_keywords!(
|
||||||
MATERIALIZED,
|
MATERIALIZED,
|
||||||
MAX,
|
MAX,
|
||||||
MAXVALUE,
|
MAXVALUE,
|
||||||
|
MAX_DATA_EXTENSION_TIME_IN_DAYS,
|
||||||
MEASURES,
|
MEASURES,
|
||||||
MEDIUMINT,
|
MEDIUMINT,
|
||||||
MEMBER,
|
MEMBER,
|
||||||
|
@ -539,6 +547,7 @@ define_keywords!(
|
||||||
PIVOT,
|
PIVOT,
|
||||||
PLACING,
|
PLACING,
|
||||||
PLANS,
|
PLANS,
|
||||||
|
POLICY,
|
||||||
PORTION,
|
PORTION,
|
||||||
POSITION,
|
POSITION,
|
||||||
POSITION_REGEX,
|
POSITION_REGEX,
|
||||||
|
@ -690,6 +699,7 @@ define_keywords!(
|
||||||
TABLE,
|
TABLE,
|
||||||
TABLES,
|
TABLES,
|
||||||
TABLESAMPLE,
|
TABLESAMPLE,
|
||||||
|
TAG,
|
||||||
TARGET,
|
TARGET,
|
||||||
TBLPROPERTIES,
|
TBLPROPERTIES,
|
||||||
TEMP,
|
TEMP,
|
||||||
|
|
|
@ -5372,7 +5372,7 @@ impl<'a> Parser<'a> {
|
||||||
let _ = self.consume_token(&Token::Eq);
|
let _ = self.consume_token(&Token::Eq);
|
||||||
let next_token = self.next_token();
|
let next_token = self.next_token();
|
||||||
match next_token.token {
|
match next_token.token {
|
||||||
Token::SingleQuotedString(str) => Some(str),
|
Token::SingleQuotedString(str) => Some(CommentDef::WithoutEq(str)),
|
||||||
_ => self.expected("comment", next_token)?,
|
_ => self.expected("comment", next_token)?,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -5423,7 +5423,9 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
let mut cluster_by = None;
|
let mut cluster_by = None;
|
||||||
if self.parse_keywords(&[Keyword::CLUSTER, Keyword::BY]) {
|
if self.parse_keywords(&[Keyword::CLUSTER, Keyword::BY]) {
|
||||||
cluster_by = Some(self.parse_comma_separated(|p| p.parse_identifier(false))?);
|
cluster_by = Some(WrappedCollection::NoWrapping(
|
||||||
|
self.parse_comma_separated(|p| p.parse_identifier(false))?,
|
||||||
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut options = None;
|
let mut options = None;
|
||||||
|
@ -7783,7 +7785,7 @@ impl<'a> Parser<'a> {
|
||||||
/// This function can be used to reduce the stack size required in debug
|
/// This function can be used to reduce the stack size required in debug
|
||||||
/// builds. Instead of `sizeof(Query)` only a pointer (`Box<Query>`)
|
/// builds. Instead of `sizeof(Query)` only a pointer (`Box<Query>`)
|
||||||
/// is used.
|
/// is used.
|
||||||
fn parse_boxed_query(&mut self) -> Result<Box<Query>, ParserError> {
|
pub fn parse_boxed_query(&mut self) -> Result<Box<Query>, ParserError> {
|
||||||
self.parse_query().map(Box::new)
|
self.parse_query().map(Box::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,7 +442,10 @@ fn parse_create_table_with_options() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(
|
(
|
||||||
Some(Box::new(Expr::Identifier(Ident::new("_PARTITIONDATE")))),
|
Some(Box::new(Expr::Identifier(Ident::new("_PARTITIONDATE")))),
|
||||||
Some(vec![Ident::new("userid"), Ident::new("age"),]),
|
Some(WrappedCollection::NoWrapping(vec![
|
||||||
|
Ident::new("userid"),
|
||||||
|
Ident::new("age"),
|
||||||
|
])),
|
||||||
Some(vec![
|
Some(vec![
|
||||||
SqlOption {
|
SqlOption {
|
||||||
name: Ident::new("partition_expiration_days"),
|
name: Ident::new("partition_expiration_days"),
|
||||||
|
|
|
@ -3453,9 +3453,14 @@ fn parse_create_table_as_table() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_on_cluster() {
|
fn parse_create_table_on_cluster() {
|
||||||
|
let generic = TestedDialects {
|
||||||
|
dialects: vec![Box::new(GenericDialect {})],
|
||||||
|
options: None,
|
||||||
|
};
|
||||||
|
|
||||||
// Using single-quote literal to define current cluster
|
// Using single-quote literal to define current cluster
|
||||||
let sql = "CREATE TABLE t ON CLUSTER '{cluster}' (a INT, b INT)";
|
let sql = "CREATE TABLE t ON CLUSTER '{cluster}' (a INT, b INT)";
|
||||||
match verified_stmt(sql) {
|
match generic.verified_stmt(sql) {
|
||||||
Statement::CreateTable(CreateTable { on_cluster, .. }) => {
|
Statement::CreateTable(CreateTable { on_cluster, .. }) => {
|
||||||
assert_eq!(on_cluster.unwrap(), "{cluster}".to_string());
|
assert_eq!(on_cluster.unwrap(), "{cluster}".to_string());
|
||||||
}
|
}
|
||||||
|
@ -3464,7 +3469,7 @@ fn parse_create_table_on_cluster() {
|
||||||
|
|
||||||
// Using explicitly declared cluster name
|
// Using explicitly declared cluster name
|
||||||
let sql = "CREATE TABLE t ON CLUSTER my_cluster (a INT, b INT)";
|
let sql = "CREATE TABLE t ON CLUSTER my_cluster (a INT, b INT)";
|
||||||
match verified_stmt(sql) {
|
match generic.verified_stmt(sql) {
|
||||||
Statement::CreateTable(CreateTable { on_cluster, .. }) => {
|
Statement::CreateTable(CreateTable { on_cluster, .. }) => {
|
||||||
assert_eq!(on_cluster.unwrap(), "my_cluster".to_string());
|
assert_eq!(on_cluster.unwrap(), "my_cluster".to_string());
|
||||||
}
|
}
|
||||||
|
@ -3517,8 +3522,13 @@ fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), Par
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_with_options() {
|
fn parse_create_table_with_options() {
|
||||||
|
let generic = TestedDialects {
|
||||||
|
dialects: vec![Box::new(GenericDialect {})],
|
||||||
|
options: None,
|
||||||
|
};
|
||||||
|
|
||||||
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
|
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
|
||||||
match verified_stmt(sql) {
|
match generic.verified_stmt(sql) {
|
||||||
Statement::CreateTable(CreateTable { with_options, .. }) => {
|
Statement::CreateTable(CreateTable { with_options, .. }) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
|
|
|
@ -4136,3 +4136,26 @@ fn parse_at_time_zone() {
|
||||||
expr
|
expr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_with_options() {
|
||||||
|
let sql = "CREATE TABLE t (c INT) WITH (foo = 'bar', a = 123)";
|
||||||
|
match pg().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable { with_options, .. }) => {
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
SqlOption {
|
||||||
|
name: "foo".into(),
|
||||||
|
value: Expr::Value(Value::SingleQuotedString("bar".into())),
|
||||||
|
},
|
||||||
|
SqlOption {
|
||||||
|
name: "a".into(),
|
||||||
|
value: Expr::Value(number("123")),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
with_options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,279 @@ fn test_snowflake_create_table() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_or_replace_table() {
|
||||||
|
let sql = "CREATE OR REPLACE TABLE my_table (a number)";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name, or_replace, ..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert!(or_replace);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_or_replace_table_copy_grants() {
|
||||||
|
let sql = "CREATE OR REPLACE TABLE my_table (a number) COPY GRANTS";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
or_replace,
|
||||||
|
copy_grants,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert!(or_replace);
|
||||||
|
assert!(copy_grants);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_or_replace_table_copy_grants_at_end() {
|
||||||
|
let sql = "CREATE OR REPLACE TABLE my_table COPY GRANTS (a number) ";
|
||||||
|
let parsed = "CREATE OR REPLACE TABLE my_table (a number) COPY GRANTS";
|
||||||
|
match snowflake().one_statement_parses_to(sql, parsed) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
or_replace,
|
||||||
|
copy_grants,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert!(or_replace);
|
||||||
|
assert!(copy_grants);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_or_replace_table_copy_grants_cta() {
|
||||||
|
let sql = "CREATE OR REPLACE TABLE my_table COPY GRANTS AS SELECT 1 AS a";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
or_replace,
|
||||||
|
copy_grants,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert!(or_replace);
|
||||||
|
assert!(copy_grants);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_enable_schema_evolution() {
|
||||||
|
let sql = "CREATE TABLE my_table (a number) ENABLE_SCHEMA_EVOLUTION=TRUE";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
enable_schema_evolution,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some(true), enable_schema_evolution);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_change_tracking() {
|
||||||
|
let sql = "CREATE TABLE my_table (a number) CHANGE_TRACKING=TRUE";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
change_tracking,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some(true), change_tracking);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_data_retention_time_in_days() {
|
||||||
|
let sql = "CREATE TABLE my_table (a number) DATA_RETENTION_TIME_IN_DAYS=5";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
data_retention_time_in_days,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some(5), data_retention_time_in_days);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_max_data_extension_time_in_days() {
|
||||||
|
let sql = "CREATE TABLE my_table (a number) MAX_DATA_EXTENSION_TIME_IN_DAYS=5";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
max_data_extension_time_in_days,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some(5), max_data_extension_time_in_days);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_with_aggregation_policy() {
|
||||||
|
match snowflake()
|
||||||
|
.verified_stmt("CREATE TABLE my_table (a number) WITH AGGREGATION POLICY policy_name")
|
||||||
|
{
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
with_aggregation_policy,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some("policy_name".to_string()),
|
||||||
|
with_aggregation_policy.map(|name| name.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
match snowflake()
|
||||||
|
.parse_sql_statements("CREATE TABLE my_table (a number) AGGREGATION POLICY policy_name")
|
||||||
|
.unwrap()
|
||||||
|
.pop()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
with_aggregation_policy,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some("policy_name".to_string()),
|
||||||
|
with_aggregation_policy.map(|name| name.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_with_row_access_policy() {
|
||||||
|
match snowflake().verified_stmt(
|
||||||
|
"CREATE TABLE my_table (a number, b number) WITH ROW ACCESS POLICY policy_name ON (a)",
|
||||||
|
) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
with_row_access_policy,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some("WITH ROW ACCESS POLICY policy_name ON (a)".to_string()),
|
||||||
|
with_row_access_policy.map(|policy| policy.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
match snowflake()
|
||||||
|
.parse_sql_statements(
|
||||||
|
"CREATE TABLE my_table (a number, b number) ROW ACCESS POLICY policy_name ON (a)",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.pop()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
with_row_access_policy,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some("WITH ROW ACCESS POLICY policy_name ON (a)".to_string()),
|
||||||
|
with_row_access_policy.map(|policy| policy.to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_with_tag() {
|
||||||
|
match snowflake()
|
||||||
|
.verified_stmt("CREATE TABLE my_table (a number) WITH TAG (A='TAG A', B='TAG B')")
|
||||||
|
{
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name, with_tags, ..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some(vec![
|
||||||
|
Tag::new("A".into(), "TAG A".to_string()),
|
||||||
|
Tag::new("B".into(), "TAG B".to_string())
|
||||||
|
]),
|
||||||
|
with_tags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
match snowflake()
|
||||||
|
.parse_sql_statements("CREATE TABLE my_table (a number) TAG (A='TAG A', B='TAG B')")
|
||||||
|
.unwrap()
|
||||||
|
.pop()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name, with_tags, ..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some(vec![
|
||||||
|
Tag::new("A".into(), "TAG A".to_string()),
|
||||||
|
Tag::new("B".into(), "TAG B".to_string())
|
||||||
|
]),
|
||||||
|
with_tags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_default_ddl_collation() {
|
||||||
|
let sql = "CREATE TABLE my_table (a number) DEFAULT_DDL_COLLATION='de'";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
default_ddl_collation,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some("de".to_string()), default_ddl_collation);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snowflake_create_transient_table() {
|
fn test_snowflake_create_transient_table() {
|
||||||
let sql = "CREATE TRANSIENT TABLE CUSTOMER (id INT, name VARCHAR(255))";
|
let sql = "CREATE TRANSIENT TABLE CUSTOMER (id INT, name VARCHAR(255))";
|
||||||
|
@ -54,6 +327,162 @@ fn test_snowflake_create_transient_table() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_column_comment() {
|
||||||
|
let sql = "CREATE TABLE my_table (a STRING COMMENT 'some comment')";
|
||||||
|
match snowflake().verified_stmt(sql) {
|
||||||
|
Statement::CreateTable(CreateTable { name, columns, .. }) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
vec![ColumnDef {
|
||||||
|
name: "a".into(),
|
||||||
|
data_type: DataType::String(None),
|
||||||
|
options: vec![ColumnOptionDef {
|
||||||
|
name: None,
|
||||||
|
option: ColumnOption::Comment("some comment".to_string())
|
||||||
|
}],
|
||||||
|
collation: None
|
||||||
|
}],
|
||||||
|
columns
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_local_table() {
|
||||||
|
match snowflake().verified_stmt("CREATE TABLE my_table (a INT)") {
|
||||||
|
Statement::CreateTable(CreateTable { name, global, .. }) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert!(global.is_none())
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
match snowflake().verified_stmt("CREATE LOCAL TABLE my_table (a INT)") {
|
||||||
|
Statement::CreateTable(CreateTable { name, global, .. }) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some(false), global)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_global_table() {
|
||||||
|
match snowflake().verified_stmt("CREATE GLOBAL TABLE my_table (a INT)") {
|
||||||
|
Statement::CreateTable(CreateTable { name, global, .. }) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(Some(true), global)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_invalid_local_global_table() {
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE LOCAL GLOBAL TABLE my_table (a INT)"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"Expected an SQL statement, found: LOCAL".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE GLOBAL LOCAL TABLE my_table (a INT)"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"Expected an SQL statement, found: GLOBAL".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_invalid_temporal_table() {
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE TEMP TEMPORARY TABLE my_table (a INT)"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"Expected an object type after CREATE, found: TEMPORARY".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE TEMP VOLATILE TABLE my_table (a INT)"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"Expected an object type after CREATE, found: VOLATILE".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE TEMP TRANSIENT TABLE my_table (a INT)"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"Expected an object type after CREATE, found: TRANSIENT".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_if_not_exists() {
|
||||||
|
match snowflake().verified_stmt("CREATE TABLE IF NOT EXISTS my_table (a INT)") {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name,
|
||||||
|
if_not_exists,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert!(if_not_exists)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_cluster_by() {
|
||||||
|
match snowflake().verified_stmt("CREATE TABLE my_table (a INT) CLUSTER BY (a, b)") {
|
||||||
|
Statement::CreateTable(CreateTable {
|
||||||
|
name, cluster_by, ..
|
||||||
|
}) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
Some(WrappedCollection::Parentheses(vec![
|
||||||
|
Ident::new("a"),
|
||||||
|
Ident::new("b"),
|
||||||
|
])),
|
||||||
|
cluster_by
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_comment() {
|
||||||
|
match snowflake().verified_stmt("CREATE TABLE my_table (a INT) COMMENT = 'some comment'") {
|
||||||
|
Statement::CreateTable(CreateTable { name, comment, .. }) => {
|
||||||
|
assert_eq!("my_table", name.to_string());
|
||||||
|
assert_eq!("some comment", comment.unwrap().to_string());
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_snowflake_create_table_incomplete_statement() {
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE TABLE my_table"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"unexpected end of input".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
snowflake().parse_sql_statements("CREATE TABLE my_table; (c int)"),
|
||||||
|
Err(ParserError::ParserError(
|
||||||
|
"unexpected end of input".to_string()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snowflake_single_line_tokenize() {
|
fn test_snowflake_single_line_tokenize() {
|
||||||
let sql = "CREATE TABLE# this is a comment \ntable_1";
|
let sql = "CREATE TABLE# this is a comment \ntable_1";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue