Skip to content

Snowflake: support for extended column options in CREATE TABLE #1454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 233 additions & 11 deletions src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use sqlparser_derive::{Visit, VisitMut};
use crate::ast::value::escape_single_quote_string;
use crate::ast::{
display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition,
ObjectName, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Value,
ObjectName, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
};
use crate::keywords::Keyword;
use crate::tokenizer::Token;
Expand Down Expand Up @@ -1096,17 +1096,221 @@ impl fmt::Display for ColumnOptionDef {
}
}

/// Identity is a column option for defining an identity or autoincrement column in a `CREATE TABLE` statement.
/// Syntax
/// ```sql
/// { IDENTITY | AUTOINCREMENT } [ (seed , increment) | START num INCREMENT num ] [ ORDER | NOORDER ]
/// ```
/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
/// [Snowflake]: 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 enum IdentityPropertyKind {
/// An identity property declared via the `AUTOINCREMENT` key word
/// Example:
/// ```sql
/// AUTOINCREMENT(100, 1) NOORDER
/// AUTOINCREMENT START 100 INCREMENT 1 ORDER
/// ```
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
Autoincrement(IdentityProperty),
/// An identity property declared via the `IDENTITY` key word
/// Example, [MS SQL Server] or [Snowflake]:
/// ```sql
/// IDENTITY(100, 1)
/// ```
/// [Snowflake]
/// ```sql
/// IDENTITY(100, 1) ORDER
/// IDENTITY START 100 INCREMENT 1 NOORDER
/// ```
/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
Identity(IdentityProperty),
}

impl fmt::Display for IdentityPropertyKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let (command, property) = match self {
IdentityPropertyKind::Identity(property) => ("IDENTITY", property),
IdentityPropertyKind::Autoincrement(property) => ("AUTOINCREMENT", property),
};
write!(f, "{command}")?;
if let Some(parameters) = &property.parameters {
write!(f, "{parameters}")?;
}
if let Some(order) = &property.order {
write!(f, "{order}")?;
}
Ok(())
}
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct IdentityProperty {
pub parameters: Option<IdentityPropertyFormatKind>,
pub order: Option<IdentityPropertyOrder>,
}

/// A format of parameters of identity column.
///
/// It is [Snowflake] specific.
/// Syntax
/// ```sql
/// (seed , increment) | START num INCREMENT num
/// ```
/// [MS SQL Server] uses one way of representing these parameters.
/// Syntax
/// ```sql
/// (seed , increment)
/// ```
/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
/// [Snowflake]: 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 enum IdentityPropertyFormatKind {
/// A parameters of identity column declared like parameters of function call
/// Example:
/// ```sql
/// (100, 1)
/// ```
/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
FunctionCall(IdentityParameters),
/// A parameters of identity column declared with keywords `START` and `INCREMENT`
/// Example:
/// ```sql
/// START 100 INCREMENT 1
/// ```
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
StartAndIncrement(IdentityParameters),
}

impl fmt::Display for IdentityPropertyFormatKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
IdentityPropertyFormatKind::FunctionCall(parameters) => {
write!(f, "({}, {})", parameters.seed, parameters.increment)
}
IdentityPropertyFormatKind::StartAndIncrement(parameters) => {
write!(
f,
" START {} INCREMENT {}",
parameters.seed, parameters.increment
)
}
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct IdentityParameters {
pub seed: Expr,
pub increment: Expr,
}

impl fmt::Display for IdentityProperty {
/// The identity column option specifies how values are generated for the auto-incremented column, either in increasing or decreasing order.
/// Syntax
/// ```sql
/// ORDER | NOORDER
/// ```
/// [Snowflake]: 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 enum IdentityPropertyOrder {
Order,
NoOrder,
}

impl fmt::Display for IdentityPropertyOrder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
IdentityPropertyOrder::Order => write!(f, " ORDER"),
IdentityPropertyOrder::NoOrder => write!(f, " NOORDER"),
}
}
}

/// Column policy that identify a security policy of access to a column.
/// Syntax
/// ```sql
/// [ WITH ] MASKING POLICY <policy_name> [ USING ( <col_name> , <cond_col1> , ... ) ]
/// [ WITH ] PROJECTION POLICY <policy_name>
/// ```
/// [Snowflake]: 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 enum ColumnPolicy {
MaskingPolicy(ColumnPolicyProperty),
ProjectionPolicy(ColumnPolicyProperty),
}

impl fmt::Display for ColumnPolicy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}, {}", self.seed, self.increment)
let (command, property) = match self {
ColumnPolicy::MaskingPolicy(property) => ("MASKING POLICY", property),
ColumnPolicy::ProjectionPolicy(property) => ("PROJECTION POLICY", property),
};
if property.with {
write!(f, "WITH ")?;
}
write!(f, "{command} {}", property.policy_name)?;
if let Some(using_columns) = &property.using_columns {
write!(f, " USING ({})", display_comma_separated(using_columns))?;
}
Ok(())
}
}

#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct ColumnPolicyProperty {
/// This flag indicates that the column policy option is declared using the `WITH` prefix.
/// Example
/// ```sql
/// WITH PROJECTION POLICY sample_policy
/// ```
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
pub with: bool,
pub policy_name: Ident,
pub using_columns: Option<Vec<Ident>>,
}

/// Tags option of column
/// Syntax
/// ```sql
/// [ WITH ] TAG ( <tag_name> = '<tag_value>' [ , <tag_name> = '<tag_value>' , ... ] )
/// ```
/// [Snowflake]: 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 TagsColumnOption {
/// This flag indicates that the tags option is declared using the `WITH` prefix.
/// Example:
/// ```sql
/// WITH TAG (A = 'Tag A')
/// ```
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
pub with: bool,
pub tags: Vec<Tag>,
}

impl fmt::Display for TagsColumnOption {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.with {
write!(f, "WITH ")?;
}
write!(f, "TAG ({})", display_comma_separated(&self.tags))?;
Ok(())
}
}

Expand Down Expand Up @@ -1180,16 +1384,32 @@ pub enum ColumnOption {
/// [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#view_column_option_list
/// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_option_list
Options(Vec<SqlOption>),
/// MS SQL Server specific: Creates an identity column in a table.
/// Creates an identity or an autoincrement column in a table.
/// Syntax
/// ```sql
/// IDENTITY [ (seed , increment) ]
/// { IDENTITY | AUTOINCREMENT } [ (seed , increment) | START num INCREMENT num ] [ ORDER | NOORDER ]
/// ```
/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
Identity(Option<IdentityProperty>),
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
Identity(IdentityPropertyKind),
/// SQLite specific: ON CONFLICT option on column definition
/// <https://www.sqlite.org/lang_conflict.html>
OnConflict(Keyword),
/// Snowflake specific: an option of specifying security masking or projection policy to set on a column.
/// Syntax:
/// ```sql
/// [ WITH ] MASKING POLICY <policy_name> [ USING ( <col_name> , <cond_col1> , ... ) ]
/// [ WITH ] PROJECTION POLICY <policy_name>
/// ```
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
Policy(ColumnPolicy),
/// Snowflake specific: Specifies the tag name and the tag string value.
/// Syntax:
/// ```sql
/// [ WITH ] TAG ( <tag_name> = '<tag_value>' [ , <tag_name> = '<tag_value>' , ... ] )
/// ```
/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
Tags(TagsColumnOption),
}

impl fmt::Display for ColumnOption {
Expand Down Expand Up @@ -1292,16 +1512,18 @@ impl fmt::Display for ColumnOption {
write!(f, "OPTIONS({})", display_comma_separated(options))
}
Identity(parameters) => {
write!(f, "IDENTITY")?;
if let Some(parameters) = parameters {
write!(f, "({parameters})")?;
}
Ok(())
write!(f, "{parameters}")
}
OnConflict(keyword) => {
write!(f, "ON CONFLICT {:?}", keyword)?;
Ok(())
}
Policy(parameters) => {
write!(f, "{parameters}")
}
Tags(tags) => {
write!(f, "{tags}")
}
}
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ pub use self::data_type::{
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use};
pub use self::ddl::{
AlterColumnOperation, AlterIndexOperation, AlterPolicyOperation, AlterTableOperation,
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ConstraintCharacteristics, Deduplicate,
DeferrableInitial, GeneratedAs, GeneratedExpressionMode, IdentityProperty, IndexOption,
IndexType, KeyOrIndexDisplay, Owner, Partition, ProcedureParam, ReferentialAction,
TableConstraint, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
ViewColumnDef,
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty,
ConstraintCharacteristics, Deduplicate, DeferrableInitial, GeneratedAs,
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, Owner,
Partition, ProcedureParam, ReferentialAction, TableConstraint, TagsColumnOption,
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
};
pub use self::dml::{CreateIndex, CreateTable, Delete, Insert};
pub use self::operator::{BinaryOperator, UnaryOperator};
Expand Down
15 changes: 14 additions & 1 deletion src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub use self::postgresql::PostgreSqlDialect;
pub use self::redshift::RedshiftSqlDialect;
pub use self::snowflake::SnowflakeDialect;
pub use self::sqlite::SQLiteDialect;
use crate::ast::{Expr, Statement};
use crate::ast::{ColumnOption, Expr, Statement};
pub use crate::keywords;
use crate::keywords::Keyword;
use crate::parser::{Parser, ParserError};
Expand Down Expand Up @@ -478,6 +478,19 @@ pub trait Dialect: Debug + Any {
None
}

/// Dialect-specific column option parser override
///
/// This method is called to parse the next column option.
///
/// If `None` is returned, falls back to the default behavior.
fn parse_column_option(
&self,
_parser: &mut Parser,
) -> Option<Result<Option<ColumnOption>, ParserError>> {
// return None to fall back to the default behavior
None
}

/// Decide the lexical Precedence of operators.
///
/// Uses (APPROXIMATELY) <https://www.postgresql.org/docs/7.0/operators.htm#AEN2026> as a reference
Expand Down
Loading