mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-22 06:54:07 +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};
|
||||
|
||||
use super::{
|
||||
display_comma_separated, display_separated, Expr, FileFormat, FromTable, HiveDistributionStyle,
|
||||
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases, MysqlInsertPriority, ObjectName,
|
||||
OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, SelectItem, SqlOption,
|
||||
SqliteOnConflict, TableEngine, TableWithJoins,
|
||||
display_comma_separated, display_separated, CommentDef, Expr, FileFormat, FromTable,
|
||||
HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases,
|
||||
MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query,
|
||||
RowAccessPolicy, SelectItem, SqlOption, SqliteOnConflict, TableEngine, TableWithJoins, Tag,
|
||||
WrappedCollection,
|
||||
};
|
||||
|
||||
/// CREATE INDEX statement.
|
||||
|
@ -57,6 +58,7 @@ pub struct CreateTable {
|
|||
pub global: Option<bool>,
|
||||
pub if_not_exists: bool,
|
||||
pub transient: bool,
|
||||
pub volatile: bool,
|
||||
/// Table name
|
||||
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
||||
pub name: ObjectName,
|
||||
|
@ -74,7 +76,7 @@ pub struct CreateTable {
|
|||
pub like: Option<ObjectName>,
|
||||
pub clone: Option<ObjectName>,
|
||||
pub engine: Option<TableEngine>,
|
||||
pub comment: Option<String>,
|
||||
pub comment: Option<CommentDef>,
|
||||
pub auto_increment_offset: Option<u32>,
|
||||
pub default_charset: Option<String>,
|
||||
pub collation: Option<String>,
|
||||
|
@ -94,7 +96,7 @@ pub struct CreateTable {
|
|||
pub 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>
|
||||
pub cluster_by: Option<Vec<Ident>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||
/// BigQuery: Table options list.
|
||||
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
|
||||
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 ")",
|
||||
/// then strict typing rules apply to that table.
|
||||
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 {
|
||||
|
@ -115,7 +144,7 @@ impl Display for CreateTable {
|
|||
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
||||
write!(
|
||||
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 { "" },
|
||||
external = if self.external { "EXTERNAL " } else { "" },
|
||||
global = self.global
|
||||
|
@ -130,6 +159,7 @@ impl Display for CreateTable {
|
|||
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||
temporary = if self.temporary { "TEMPORARY " } else { "" },
|
||||
transient = if self.transient { "TRANSIENT " } else { "" },
|
||||
volatile = if self.volatile { "VOLATILE " } else { "" },
|
||||
name = self.name,
|
||||
)?;
|
||||
if let Some(on_cluster) = &self.on_cluster {
|
||||
|
@ -260,9 +290,17 @@ impl Display for CreateTable {
|
|||
if let Some(engine) = &self.engine {
|
||||
write!(f, " ENGINE={engine}")?;
|
||||
}
|
||||
if let Some(comment) = &self.comment {
|
||||
write!(f, " COMMENT '{comment}'")?;
|
||||
if let Some(comment_def) = &self.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 {
|
||||
write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
|
||||
}
|
||||
|
@ -276,12 +314,9 @@ impl Display for CreateTable {
|
|||
write!(f, " PARTITION BY {partition_by}")?;
|
||||
}
|
||||
if let Some(cluster_by) = self.cluster_by.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
" CLUSTER BY {}",
|
||||
display_comma_separated(cluster_by.as_slice())
|
||||
)?;
|
||||
write!(f, " CLUSTER BY {cluster_by}")?;
|
||||
}
|
||||
|
||||
if let Some(options) = self.options.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
|
@ -289,6 +324,57 @@ impl Display for CreateTable {
|
|||
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 {
|
||||
write!(f, " AS {query}")?;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
|
||||
use super::super::dml::CreateTable;
|
||||
use crate::ast::{
|
||||
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
|
||||
OneOrManyWithParens, Query, SqlOption, Statement, TableConstraint, TableEngine,
|
||||
ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName,
|
||||
OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement, TableConstraint,
|
||||
TableEngine, Tag, WrappedCollection,
|
||||
};
|
||||
use crate::parser::ParserError;
|
||||
|
||||
|
@ -52,6 +53,7 @@ pub struct CreateTableBuilder {
|
|||
pub global: Option<bool>,
|
||||
pub if_not_exists: bool,
|
||||
pub transient: bool,
|
||||
pub volatile: bool,
|
||||
pub name: ObjectName,
|
||||
pub columns: Vec<ColumnDef>,
|
||||
pub constraints: Vec<TableConstraint>,
|
||||
|
@ -66,7 +68,7 @@ pub struct CreateTableBuilder {
|
|||
pub like: Option<ObjectName>,
|
||||
pub clone: Option<ObjectName>,
|
||||
pub engine: Option<TableEngine>,
|
||||
pub comment: Option<String>,
|
||||
pub comment: Option<CommentDef>,
|
||||
pub auto_increment_offset: Option<u32>,
|
||||
pub default_charset: Option<String>,
|
||||
pub collation: Option<String>,
|
||||
|
@ -75,9 +77,18 @@ pub struct CreateTableBuilder {
|
|||
pub primary_key: Option<Box<Expr>>,
|
||||
pub order_by: Option<OneOrManyWithParens<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 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 {
|
||||
|
@ -89,6 +100,7 @@ impl CreateTableBuilder {
|
|||
global: None,
|
||||
if_not_exists: false,
|
||||
transient: false,
|
||||
volatile: false,
|
||||
name,
|
||||
columns: vec![],
|
||||
constraints: vec![],
|
||||
|
@ -115,6 +127,15 @@ impl CreateTableBuilder {
|
|||
cluster_by: None,
|
||||
options: None,
|
||||
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 {
|
||||
|
@ -147,6 +168,11 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn volatile(mut self, volatile: bool) -> Self {
|
||||
self.volatile = volatile;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn columns(mut self, columns: Vec<ColumnDef>) -> Self {
|
||||
self.columns = columns;
|
||||
self
|
||||
|
@ -210,7 +236,7 @@ impl CreateTableBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn comment(mut self, comment: Option<String>) -> Self {
|
||||
pub fn comment(mut self, comment: Option<CommentDef>) -> Self {
|
||||
self.comment = comment;
|
||||
self
|
||||
}
|
||||
|
@ -255,7 +281,7 @@ impl CreateTableBuilder {
|
|||
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
|
||||
}
|
||||
|
@ -270,6 +296,57 @@ impl CreateTableBuilder {
|
|||
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 {
|
||||
Statement::CreateTable(CreateTable {
|
||||
or_replace: self.or_replace,
|
||||
|
@ -278,6 +355,7 @@ impl CreateTableBuilder {
|
|||
global: self.global,
|
||||
if_not_exists: self.if_not_exists,
|
||||
transient: self.transient,
|
||||
volatile: self.volatile,
|
||||
name: self.name,
|
||||
columns: self.columns,
|
||||
constraints: self.constraints,
|
||||
|
@ -304,6 +382,15 @@ impl CreateTableBuilder {
|
|||
cluster_by: self.cluster_by,
|
||||
options: self.options,
|
||||
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,
|
||||
if_not_exists,
|
||||
transient,
|
||||
volatile,
|
||||
name,
|
||||
columns,
|
||||
constraints,
|
||||
|
@ -348,6 +436,15 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
cluster_by,
|
||||
options,
|
||||
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 {
|
||||
or_replace,
|
||||
temporary,
|
||||
|
@ -381,6 +478,16 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
cluster_by,
|
||||
options,
|
||||
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!(
|
||||
"Expected create table statement, but received: {stmt}"
|
||||
|
@ -393,7 +500,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
|||
#[derive(Default)]
|
||||
pub(crate) struct BigQueryTableConfiguration {
|
||||
pub partition_by: Option<Box<Expr>>,
|
||||
pub cluster_by: Option<Vec<Ident>>,
|
||||
pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -12,11 +12,14 @@
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use crate::alloc::string::ToString;
|
||||
use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
|
||||
use crate::ast::helpers::stmt_data_loading::{
|
||||
DataLoadingOption, DataLoadingOptionType, DataLoadingOptions, StageLoadSelectItem,
|
||||
StageParamsObject,
|
||||
};
|
||||
use crate::ast::{Ident, ObjectName, Statement};
|
||||
use crate::ast::{
|
||||
CommentDef, Ident, ObjectName, RowAccessPolicy, Statement, Tag, WrappedCollection,
|
||||
};
|
||||
use crate::dialect::Dialect;
|
||||
use crate::keywords::Keyword;
|
||||
use crate::parser::{Parser, ParserError};
|
||||
|
@ -91,12 +94,36 @@ impl Dialect for SnowflakeDialect {
|
|||
// possibly CREATE STAGE
|
||||
//[ OR REPLACE ]
|
||||
let or_replace = parser.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
|
||||
//[ TEMPORARY ]
|
||||
let temporary = parser.parse_keyword(Keyword::TEMPORARY);
|
||||
// LOCAL | GLOBAL
|
||||
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) {
|
||||
// OK - this is CREATE STAGE statement
|
||||
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 {
|
||||
// need to go back with the cursor
|
||||
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(
|
||||
or_replace: bool,
|
||||
temporary: bool,
|
||||
|
|
|
@ -70,11 +70,13 @@ define_keywords!(
|
|||
ABORT,
|
||||
ABS,
|
||||
ABSOLUTE,
|
||||
ACCESS,
|
||||
ACTION,
|
||||
ADD,
|
||||
ADMIN,
|
||||
AFTER,
|
||||
AGAINST,
|
||||
AGGREGATION,
|
||||
ALL,
|
||||
ALLOCATE,
|
||||
ALTER,
|
||||
|
@ -138,6 +140,7 @@ define_keywords!(
|
|||
CENTURY,
|
||||
CHAIN,
|
||||
CHANGE,
|
||||
CHANGE_TRACKING,
|
||||
CHANNEL,
|
||||
CHAR,
|
||||
CHARACTER,
|
||||
|
@ -201,6 +204,7 @@ define_keywords!(
|
|||
CYCLE,
|
||||
DATA,
|
||||
DATABASE,
|
||||
DATA_RETENTION_TIME_IN_DAYS,
|
||||
DATE,
|
||||
DATE32,
|
||||
DATETIME,
|
||||
|
@ -214,6 +218,7 @@ define_keywords!(
|
|||
DECIMAL,
|
||||
DECLARE,
|
||||
DEFAULT,
|
||||
DEFAULT_DDL_COLLATION,
|
||||
DEFERRABLE,
|
||||
DEFERRED,
|
||||
DEFINE,
|
||||
|
@ -251,6 +256,7 @@ define_keywords!(
|
|||
ELSE,
|
||||
EMPTY,
|
||||
ENABLE,
|
||||
ENABLE_SCHEMA_EVOLUTION,
|
||||
ENCODING,
|
||||
ENCRYPTION,
|
||||
END,
|
||||
|
@ -330,6 +336,7 @@ define_keywords!(
|
|||
GLOBAL,
|
||||
GRANT,
|
||||
GRANTED,
|
||||
GRANTS,
|
||||
GRAPHVIZ,
|
||||
GROUP,
|
||||
GROUPING,
|
||||
|
@ -433,6 +440,7 @@ define_keywords!(
|
|||
MATERIALIZED,
|
||||
MAX,
|
||||
MAXVALUE,
|
||||
MAX_DATA_EXTENSION_TIME_IN_DAYS,
|
||||
MEASURES,
|
||||
MEDIUMINT,
|
||||
MEMBER,
|
||||
|
@ -539,6 +547,7 @@ define_keywords!(
|
|||
PIVOT,
|
||||
PLACING,
|
||||
PLANS,
|
||||
POLICY,
|
||||
PORTION,
|
||||
POSITION,
|
||||
POSITION_REGEX,
|
||||
|
@ -690,6 +699,7 @@ define_keywords!(
|
|||
TABLE,
|
||||
TABLES,
|
||||
TABLESAMPLE,
|
||||
TAG,
|
||||
TARGET,
|
||||
TBLPROPERTIES,
|
||||
TEMP,
|
||||
|
|
|
@ -5372,7 +5372,7 @@ impl<'a> Parser<'a> {
|
|||
let _ = self.consume_token(&Token::Eq);
|
||||
let next_token = self.next_token();
|
||||
match next_token.token {
|
||||
Token::SingleQuotedString(str) => Some(str),
|
||||
Token::SingleQuotedString(str) => Some(CommentDef::WithoutEq(str)),
|
||||
_ => self.expected("comment", next_token)?,
|
||||
}
|
||||
} else {
|
||||
|
@ -5423,7 +5423,9 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let mut cluster_by = None;
|
||||
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;
|
||||
|
@ -7783,7 +7785,7 @@ impl<'a> Parser<'a> {
|
|||
/// This function can be used to reduce the stack size required in debug
|
||||
/// builds. Instead of `sizeof(Query)` only a pointer (`Box<Query>`)
|
||||
/// 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)
|
||||
}
|
||||
|
||||
|
|
|
@ -442,7 +442,10 @@ fn parse_create_table_with_options() {
|
|||
assert_eq!(
|
||||
(
|
||||
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![
|
||||
SqlOption {
|
||||
name: Ident::new("partition_expiration_days"),
|
||||
|
|
|
@ -3453,9 +3453,14 @@ fn parse_create_table_as_table() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_table_on_cluster() {
|
||||
let generic = TestedDialects {
|
||||
dialects: vec![Box::new(GenericDialect {})],
|
||||
options: None,
|
||||
};
|
||||
|
||||
// Using single-quote literal to define current cluster
|
||||
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, .. }) => {
|
||||
assert_eq!(on_cluster.unwrap(), "{cluster}".to_string());
|
||||
}
|
||||
|
@ -3464,7 +3469,7 @@ fn parse_create_table_on_cluster() {
|
|||
|
||||
// Using explicitly declared cluster name
|
||||
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, .. }) => {
|
||||
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]
|
||||
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)";
|
||||
match verified_stmt(sql) {
|
||||
match generic.verified_stmt(sql) {
|
||||
Statement::CreateTable(CreateTable { with_options, .. }) => {
|
||||
assert_eq!(
|
||||
vec![
|
||||
|
|
|
@ -4136,3 +4136,26 @@ fn parse_at_time_zone() {
|
|||
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]
|
||||
fn test_snowflake_create_transient_table() {
|
||||
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]
|
||||
fn test_snowflake_single_line_tokenize() {
|
||||
let sql = "CREATE TABLE# this is a comment \ntable_1";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue