mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-25 16:34:04 +00:00
Snowflake: CREATE DYNAMIC TABLE
This commit is contained in:
parent
12c0878a10
commit
1ccd54c6c7
10 changed files with 750 additions and 72 deletions
495
src/ast/dml.rs
495
src/ast/dml.rs
|
@ -37,6 +37,501 @@ use super::{
|
||||||
Setting, SqliteOnConflict, TableObject, TableWithJoins,
|
Setting, SqliteOnConflict, TableObject, TableWithJoins,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Index column type.
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct IndexColumn {
|
||||||
|
pub column: OrderByExpr,
|
||||||
|
pub operator_class: Option<Ident>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for IndexColumn {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.column)?;
|
||||||
|
if let Some(operator_class) = &self.operator_class {
|
||||||
|
write!(f, " {operator_class}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CREATE INDEX statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct CreateIndex {
|
||||||
|
/// index name
|
||||||
|
pub name: Option<ObjectName>,
|
||||||
|
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
||||||
|
pub table_name: ObjectName,
|
||||||
|
pub using: Option<IndexType>,
|
||||||
|
pub columns: Vec<IndexColumn>,
|
||||||
|
pub unique: bool,
|
||||||
|
pub concurrently: bool,
|
||||||
|
pub if_not_exists: bool,
|
||||||
|
pub include: Vec<Ident>,
|
||||||
|
pub nulls_distinct: Option<bool>,
|
||||||
|
/// WITH clause: <https://www.postgresql.org/docs/current/sql-createindex.html>
|
||||||
|
pub with: Vec<Expr>,
|
||||||
|
pub predicate: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CreateIndex {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"CREATE {unique}INDEX {concurrently}{if_not_exists}",
|
||||||
|
unique = if self.unique { "UNIQUE " } else { "" },
|
||||||
|
concurrently = if self.concurrently {
|
||||||
|
"CONCURRENTLY "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
if_not_exists = if self.if_not_exists {
|
||||||
|
"IF NOT EXISTS "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
if let Some(value) = &self.name {
|
||||||
|
write!(f, "{value} ")?;
|
||||||
|
}
|
||||||
|
write!(f, "ON {}", self.table_name)?;
|
||||||
|
if let Some(value) = &self.using {
|
||||||
|
write!(f, " USING {value} ")?;
|
||||||
|
}
|
||||||
|
write!(f, "({})", display_separated(&self.columns, ","))?;
|
||||||
|
if !self.include.is_empty() {
|
||||||
|
write!(f, " INCLUDE ({})", display_separated(&self.include, ","))?;
|
||||||
|
}
|
||||||
|
if let Some(value) = self.nulls_distinct {
|
||||||
|
if value {
|
||||||
|
write!(f, " NULLS DISTINCT")?;
|
||||||
|
} else {
|
||||||
|
write!(f, " NULLS NOT DISTINCT")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.with.is_empty() {
|
||||||
|
write!(f, " WITH ({})", display_comma_separated(&self.with))?;
|
||||||
|
}
|
||||||
|
if let Some(predicate) = &self.predicate {
|
||||||
|
write!(f, " WHERE {predicate}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CREATE TABLE statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub struct CreateTable {
|
||||||
|
pub or_replace: bool,
|
||||||
|
pub temporary: bool,
|
||||||
|
pub external: bool,
|
||||||
|
pub dynamic: bool,
|
||||||
|
pub global: Option<bool>,
|
||||||
|
pub if_not_exists: bool,
|
||||||
|
pub transient: bool,
|
||||||
|
pub volatile: bool,
|
||||||
|
pub iceberg: bool,
|
||||||
|
/// Table name
|
||||||
|
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
|
||||||
|
pub name: ObjectName,
|
||||||
|
/// Optional schema
|
||||||
|
pub columns: Vec<ColumnDef>,
|
||||||
|
pub constraints: Vec<TableConstraint>,
|
||||||
|
pub hive_distribution: HiveDistributionStyle,
|
||||||
|
pub hive_formats: Option<HiveFormat>,
|
||||||
|
pub table_options: CreateTableOptions,
|
||||||
|
pub file_format: Option<FileFormat>,
|
||||||
|
pub location: Option<String>,
|
||||||
|
pub query: Option<Box<Query>>,
|
||||||
|
pub without_rowid: bool,
|
||||||
|
pub like: Option<ObjectName>,
|
||||||
|
pub clone: Option<ObjectName>,
|
||||||
|
pub version: Option<TableVersion>,
|
||||||
|
// For Hive dialect, the table comment is after the column definitions without `=`,
|
||||||
|
// so the `comment` field is optional and different than the comment field in the general options list.
|
||||||
|
// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
|
||||||
|
pub comment: Option<CommentDef>,
|
||||||
|
pub on_commit: Option<OnCommit>,
|
||||||
|
/// ClickHouse "ON CLUSTER" clause:
|
||||||
|
/// <https://clickhouse.com/docs/en/sql-reference/distributed-ddl/>
|
||||||
|
pub on_cluster: Option<Ident>,
|
||||||
|
/// ClickHouse "PRIMARY KEY " clause.
|
||||||
|
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
|
||||||
|
pub primary_key: Option<Box<Expr>>,
|
||||||
|
/// ClickHouse "ORDER BY " clause. Note that omitted ORDER BY is different
|
||||||
|
/// than empty (represented as ()), the latter meaning "no sorting".
|
||||||
|
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
|
||||||
|
pub order_by: Option<OneOrManyWithParens<Expr>>,
|
||||||
|
/// BigQuery: A partition expression for the table.
|
||||||
|
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression>
|
||||||
|
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>
|
||||||
|
/// Snowflake: Table clustering list which contains base column, expressions on base columns.
|
||||||
|
/// <https://docs.snowflake.com/en/user-guide/tables-clustering-keys#defining-a-clustering-key-for-a-table>
|
||||||
|
pub cluster_by: Option<WrappedCollection<Vec<Expr>>>,
|
||||||
|
/// Hive: Table clustering column list.
|
||||||
|
/// <https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable>
|
||||||
|
pub clustered_by: Option<ClusteredBy>,
|
||||||
|
/// Postgres `INHERITs` clause, which contains the list of tables from which
|
||||||
|
/// the new table inherits.
|
||||||
|
/// <https://www.postgresql.org/docs/current/ddl-inherit.html>
|
||||||
|
/// <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-INHERITS>
|
||||||
|
pub inherits: Option<Vec<ObjectName>>,
|
||||||
|
/// SQLite "STRICT" clause.
|
||||||
|
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
|
||||||
|
/// then strict typing rules apply to that table.
|
||||||
|
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>>,
|
||||||
|
/// Snowflake "EXTERNAL_VOLUME" clause for Iceberg tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
||||||
|
pub external_volume: Option<String>,
|
||||||
|
/// Snowflake "BASE_LOCATION" clause for Iceberg tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
||||||
|
pub base_location: Option<String>,
|
||||||
|
/// Snowflake "CATALOG" clause for Iceberg tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
||||||
|
pub catalog: Option<String>,
|
||||||
|
/// Snowflake "CATALOG_SYNC" clause for Iceberg tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
||||||
|
pub catalog_sync: Option<String>,
|
||||||
|
/// Snowflake "STORAGE_SERIALIZATION_POLICY" clause for Iceberg tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
||||||
|
pub storage_serialization_policy: Option<StorageSerializationPolicy>,
|
||||||
|
/// Snowflake "TARGET_LAG" clause for dybamic tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
|
||||||
|
pub target_lag: Option<String>,
|
||||||
|
/// Snowflake "WAREHOUSE" clause for dybamic tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
|
||||||
|
pub warehouse: Option<Ident>,
|
||||||
|
/// Snowflake "REFRESH_MODE" clause for dybamic tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
|
||||||
|
pub refresh_mode: Option<RefreshModeKind>,
|
||||||
|
/// Snowflake "INITIALIZE" clause for dybamic tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
|
||||||
|
pub initialize: Option<InitializeKind>,
|
||||||
|
/// Snowflake "REQUIRE USER" clause for dybamic tables
|
||||||
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table>
|
||||||
|
pub require_user: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CreateTable {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// We want to allow the following options
|
||||||
|
// Empty column list, allowed by PostgreSQL:
|
||||||
|
// `CREATE TABLE t ()`
|
||||||
|
// No columns provided for CREATE TABLE AS:
|
||||||
|
// `CREATE TABLE t AS SELECT a from t2`
|
||||||
|
// Columns provided for CREATE TABLE AS:
|
||||||
|
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{iceberg}{dynamic}TABLE {if_not_exists}{name}",
|
||||||
|
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
|
||||||
|
external = if self.external { "EXTERNAL " } else { "" },
|
||||||
|
global = self.global
|
||||||
|
.map(|global| {
|
||||||
|
if global {
|
||||||
|
"GLOBAL "
|
||||||
|
} else {
|
||||||
|
"LOCAL "
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(""),
|
||||||
|
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 { "" },
|
||||||
|
// Only for Snowflake
|
||||||
|
iceberg = if self.iceberg { "ICEBERG " } else { "" },
|
||||||
|
dynamic = if self.dynamic { "DYNAMIC " } else { "" },
|
||||||
|
name = self.name,
|
||||||
|
)?;
|
||||||
|
if let Some(on_cluster) = &self.on_cluster {
|
||||||
|
write!(f, " ON CLUSTER {on_cluster}")?;
|
||||||
|
}
|
||||||
|
if !self.columns.is_empty() || !self.constraints.is_empty() {
|
||||||
|
f.write_str(" (")?;
|
||||||
|
NewLine.fmt(f)?;
|
||||||
|
Indent(DisplayCommaSeparated(&self.columns)).fmt(f)?;
|
||||||
|
if !self.columns.is_empty() && !self.constraints.is_empty() {
|
||||||
|
f.write_str(",")?;
|
||||||
|
SpaceOrNewline.fmt(f)?;
|
||||||
|
}
|
||||||
|
Indent(DisplayCommaSeparated(&self.constraints)).fmt(f)?;
|
||||||
|
NewLine.fmt(f)?;
|
||||||
|
f.write_str(")")?;
|
||||||
|
} else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
|
||||||
|
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
|
||||||
|
f.write_str(" ()")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hive table comment should be after column definitions, please refer to:
|
||||||
|
// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
|
||||||
|
if let Some(comment) = &self.comment {
|
||||||
|
write!(f, " COMMENT '{comment}'")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for SQLite
|
||||||
|
if self.without_rowid {
|
||||||
|
write!(f, " WITHOUT ROWID")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for Hive
|
||||||
|
if let Some(l) = &self.like {
|
||||||
|
write!(f, " LIKE {l}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(c) = &self.clone {
|
||||||
|
write!(f, " CLONE {c}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.hive_distribution {
|
||||||
|
HiveDistributionStyle::PARTITIONED { columns } => {
|
||||||
|
write!(f, " PARTITIONED BY ({})", display_comma_separated(columns))?;
|
||||||
|
}
|
||||||
|
HiveDistributionStyle::SKEWED {
|
||||||
|
columns,
|
||||||
|
on,
|
||||||
|
stored_as_directories,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" SKEWED BY ({})) ON ({})",
|
||||||
|
display_comma_separated(columns),
|
||||||
|
display_comma_separated(on)
|
||||||
|
)?;
|
||||||
|
if *stored_as_directories {
|
||||||
|
write!(f, " STORED AS DIRECTORIES")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(clustered_by) = &self.clustered_by {
|
||||||
|
write!(f, " {clustered_by}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(HiveFormat {
|
||||||
|
row_format,
|
||||||
|
serde_properties,
|
||||||
|
storage,
|
||||||
|
location,
|
||||||
|
}) = &self.hive_formats
|
||||||
|
{
|
||||||
|
match row_format {
|
||||||
|
Some(HiveRowFormat::SERDE { class }) => write!(f, " ROW FORMAT SERDE '{class}'")?,
|
||||||
|
Some(HiveRowFormat::DELIMITED { delimiters }) => {
|
||||||
|
write!(f, " ROW FORMAT DELIMITED")?;
|
||||||
|
if !delimiters.is_empty() {
|
||||||
|
write!(f, " {}", display_separated(delimiters, " "))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
match storage {
|
||||||
|
Some(HiveIOFormat::IOF {
|
||||||
|
input_format,
|
||||||
|
output_format,
|
||||||
|
}) => write!(
|
||||||
|
f,
|
||||||
|
" STORED AS INPUTFORMAT {input_format} OUTPUTFORMAT {output_format}"
|
||||||
|
)?,
|
||||||
|
Some(HiveIOFormat::FileFormat { format }) if !self.external => {
|
||||||
|
write!(f, " STORED AS {format}")?
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
if let Some(serde_properties) = serde_properties.as_ref() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" WITH SERDEPROPERTIES ({})",
|
||||||
|
display_comma_separated(serde_properties)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
if !self.external {
|
||||||
|
if let Some(loc) = location {
|
||||||
|
write!(f, " LOCATION '{loc}'")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.external {
|
||||||
|
if let Some(file_format) = self.file_format {
|
||||||
|
write!(f, " STORED AS {file_format}")?;
|
||||||
|
}
|
||||||
|
write!(f, " LOCATION '{}'", self.location.as_ref().unwrap())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.table_options {
|
||||||
|
options @ CreateTableOptions::With(_)
|
||||||
|
| options @ CreateTableOptions::Plain(_)
|
||||||
|
| options @ CreateTableOptions::TableProperties(_) => write!(f, " {options}")?,
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(primary_key) = &self.primary_key {
|
||||||
|
write!(f, " PRIMARY KEY {primary_key}")?;
|
||||||
|
}
|
||||||
|
if let Some(order_by) = &self.order_by {
|
||||||
|
write!(f, " ORDER BY {order_by}")?;
|
||||||
|
}
|
||||||
|
if let Some(inherits) = &self.inherits {
|
||||||
|
write!(f, " INHERITS ({})", display_comma_separated(inherits))?;
|
||||||
|
}
|
||||||
|
if let Some(partition_by) = self.partition_by.as_ref() {
|
||||||
|
write!(f, " PARTITION BY {partition_by}")?;
|
||||||
|
}
|
||||||
|
if let Some(cluster_by) = self.cluster_by.as_ref() {
|
||||||
|
write!(f, " CLUSTER BY {cluster_by}")?;
|
||||||
|
}
|
||||||
|
if let options @ CreateTableOptions::Options(_) = &self.table_options {
|
||||||
|
write!(f, " {options}")?;
|
||||||
|
}
|
||||||
|
if let Some(external_volume) = self.external_volume.as_ref() {
|
||||||
|
write!(f, " EXTERNAL_VOLUME = '{external_volume}'")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(catalog) = self.catalog.as_ref() {
|
||||||
|
write!(f, " CATALOG = '{catalog}'")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.iceberg {
|
||||||
|
if let Some(base_location) = self.base_location.as_ref() {
|
||||||
|
write!(f, " BASE_LOCATION = '{base_location}'")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(catalog_sync) = self.catalog_sync.as_ref() {
|
||||||
|
write!(f, " CATALOG_SYNC = '{catalog_sync}'")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(storage_serialization_policy) = self.storage_serialization_policy.as_ref() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" STORAGE_SERIALIZATION_POLICY = {storage_serialization_policy}"
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(target_lag) = &self.target_lag {
|
||||||
|
write!(f, " TARGET_LAG='{target_lag}'")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(warehouse) = &self.warehouse {
|
||||||
|
write!(f, " WAREHOUSE={warehouse}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(refresh_mode) = &self.refresh_mode {
|
||||||
|
write!(f, " REFRESH_MODE={refresh_mode}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(initialize) = &self.initialize {
|
||||||
|
write!(f, " INITIALIZE={initialize}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.require_user {
|
||||||
|
write!(f, " REQUIRE USER")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.on_commit.is_some() {
|
||||||
|
let on_commit = match self.on_commit {
|
||||||
|
Some(OnCommit::DeleteRows) => "ON COMMIT DELETE ROWS",
|
||||||
|
Some(OnCommit::PreserveRows) => "ON COMMIT PRESERVE ROWS",
|
||||||
|
Some(OnCommit::Drop) => "ON COMMIT DROP",
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
write!(f, " {on_commit}")?;
|
||||||
|
}
|
||||||
|
if self.strict {
|
||||||
|
write!(f, " STRICT")?;
|
||||||
|
}
|
||||||
|
if let Some(query) = &self.query {
|
||||||
|
write!(f, " AS {query}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// INSERT statement.
|
/// INSERT statement.
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
|
|
@ -72,6 +72,7 @@ pub struct CreateTableBuilder {
|
||||||
pub transient: bool,
|
pub transient: bool,
|
||||||
pub volatile: bool,
|
pub volatile: bool,
|
||||||
pub iceberg: bool,
|
pub iceberg: bool,
|
||||||
|
pub dynamic: bool,
|
||||||
pub name: ObjectName,
|
pub name: ObjectName,
|
||||||
pub columns: Vec<ColumnDef>,
|
pub columns: Vec<ColumnDef>,
|
||||||
pub constraints: Vec<TableConstraint>,
|
pub constraints: Vec<TableConstraint>,
|
||||||
|
@ -83,6 +84,7 @@ pub struct CreateTableBuilder {
|
||||||
pub without_rowid: bool,
|
pub without_rowid: bool,
|
||||||
pub like: Option<CreateTableLikeKind>,
|
pub like: Option<CreateTableLikeKind>,
|
||||||
pub clone: Option<ObjectName>,
|
pub clone: Option<ObjectName>,
|
||||||
|
pub version: Option<TableVersion>,
|
||||||
pub comment: Option<CommentDef>,
|
pub comment: Option<CommentDef>,
|
||||||
pub on_commit: Option<OnCommit>,
|
pub on_commit: Option<OnCommit>,
|
||||||
pub on_cluster: Option<Ident>,
|
pub on_cluster: Option<Ident>,
|
||||||
|
@ -108,6 +110,11 @@ pub struct CreateTableBuilder {
|
||||||
pub catalog_sync: Option<String>,
|
pub catalog_sync: Option<String>,
|
||||||
pub storage_serialization_policy: Option<StorageSerializationPolicy>,
|
pub storage_serialization_policy: Option<StorageSerializationPolicy>,
|
||||||
pub table_options: CreateTableOptions,
|
pub table_options: CreateTableOptions,
|
||||||
|
pub target_lag: Option<String>,
|
||||||
|
pub warehouse: Option<Ident>,
|
||||||
|
pub refresh_mode: Option<RefreshModeKind>,
|
||||||
|
pub initialize: Option<InitializeKind>,
|
||||||
|
pub require_user: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateTableBuilder {
|
impl CreateTableBuilder {
|
||||||
|
@ -121,6 +128,7 @@ impl CreateTableBuilder {
|
||||||
transient: false,
|
transient: false,
|
||||||
volatile: false,
|
volatile: false,
|
||||||
iceberg: false,
|
iceberg: false,
|
||||||
|
dynamic: false,
|
||||||
name,
|
name,
|
||||||
columns: vec![],
|
columns: vec![],
|
||||||
constraints: vec![],
|
constraints: vec![],
|
||||||
|
@ -132,6 +140,7 @@ impl CreateTableBuilder {
|
||||||
without_rowid: false,
|
without_rowid: false,
|
||||||
like: None,
|
like: None,
|
||||||
clone: None,
|
clone: None,
|
||||||
|
version: None,
|
||||||
comment: None,
|
comment: None,
|
||||||
on_commit: None,
|
on_commit: None,
|
||||||
on_cluster: None,
|
on_cluster: None,
|
||||||
|
@ -157,6 +166,11 @@ impl CreateTableBuilder {
|
||||||
catalog_sync: None,
|
catalog_sync: None,
|
||||||
storage_serialization_policy: None,
|
storage_serialization_policy: None,
|
||||||
table_options: CreateTableOptions::None,
|
table_options: CreateTableOptions::None,
|
||||||
|
target_lag: None,
|
||||||
|
warehouse: None,
|
||||||
|
refresh_mode: None,
|
||||||
|
initialize: None,
|
||||||
|
require_user: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn or_replace(mut self, or_replace: bool) -> Self {
|
pub fn or_replace(mut self, or_replace: bool) -> Self {
|
||||||
|
@ -199,6 +213,11 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dynamic(mut self, dynamic: bool) -> Self {
|
||||||
|
self.dynamic = dynamic;
|
||||||
|
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
|
||||||
|
@ -248,6 +267,11 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn version(mut self, version: Option<TableVersion>) -> Self {
|
||||||
|
self.version = version;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn comment_after_column_def(mut self, comment: Option<CommentDef>) -> Self {
|
pub fn comment_after_column_def(mut self, comment: Option<CommentDef>) -> Self {
|
||||||
self.comment = comment;
|
self.comment = comment;
|
||||||
self
|
self
|
||||||
|
@ -382,24 +406,29 @@ impl CreateTableBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the statement has exactly one source of info on the schema of the new table.
|
pub fn target_lag(mut self, target_lag: Option<String>) -> Self {
|
||||||
/// This is Snowflake-specific, some dialects allow more than one source.
|
self.target_lag = target_lag;
|
||||||
pub(crate) fn validate_schema_info(&self) -> bool {
|
self
|
||||||
let mut sources = 0;
|
}
|
||||||
if !self.columns.is_empty() {
|
|
||||||
sources += 1;
|
|
||||||
}
|
|
||||||
if self.query.is_some() {
|
|
||||||
sources += 1;
|
|
||||||
}
|
|
||||||
if self.like.is_some() {
|
|
||||||
sources += 1;
|
|
||||||
}
|
|
||||||
if self.clone.is_some() {
|
|
||||||
sources += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sources == 1
|
pub fn warehouse(mut self, warehouse: Option<Ident>) -> Self {
|
||||||
|
self.warehouse = warehouse;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_mode(mut self, refresh_mode: Option<RefreshModeKind>) -> Self {
|
||||||
|
self.refresh_mode = refresh_mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize(mut self, initialize: Option<InitializeKind>) -> Self {
|
||||||
|
self.initialize = initialize;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_user(mut self, require_user: bool) -> Self {
|
||||||
|
self.require_user = require_user;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Statement {
|
pub fn build(self) -> Statement {
|
||||||
|
@ -412,6 +441,7 @@ impl CreateTableBuilder {
|
||||||
transient: self.transient,
|
transient: self.transient,
|
||||||
volatile: self.volatile,
|
volatile: self.volatile,
|
||||||
iceberg: self.iceberg,
|
iceberg: self.iceberg,
|
||||||
|
dynamic: self.dynamic,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
columns: self.columns,
|
columns: self.columns,
|
||||||
constraints: self.constraints,
|
constraints: self.constraints,
|
||||||
|
@ -423,6 +453,7 @@ impl CreateTableBuilder {
|
||||||
without_rowid: self.without_rowid,
|
without_rowid: self.without_rowid,
|
||||||
like: self.like,
|
like: self.like,
|
||||||
clone: self.clone,
|
clone: self.clone,
|
||||||
|
version: self.version,
|
||||||
comment: self.comment,
|
comment: self.comment,
|
||||||
on_commit: self.on_commit,
|
on_commit: self.on_commit,
|
||||||
on_cluster: self.on_cluster,
|
on_cluster: self.on_cluster,
|
||||||
|
@ -448,6 +479,11 @@ impl CreateTableBuilder {
|
||||||
catalog_sync: self.catalog_sync,
|
catalog_sync: self.catalog_sync,
|
||||||
storage_serialization_policy: self.storage_serialization_policy,
|
storage_serialization_policy: self.storage_serialization_policy,
|
||||||
table_options: self.table_options,
|
table_options: self.table_options,
|
||||||
|
target_lag: self.target_lag,
|
||||||
|
warehouse: self.warehouse,
|
||||||
|
refresh_mode: self.refresh_mode,
|
||||||
|
initialize: self.initialize,
|
||||||
|
require_user: self.require_user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -468,6 +504,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
transient,
|
transient,
|
||||||
volatile,
|
volatile,
|
||||||
iceberg,
|
iceberg,
|
||||||
|
dynamic,
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
@ -479,6 +516,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
without_rowid,
|
without_rowid,
|
||||||
like,
|
like,
|
||||||
clone,
|
clone,
|
||||||
|
version,
|
||||||
comment,
|
comment,
|
||||||
on_commit,
|
on_commit,
|
||||||
on_cluster,
|
on_cluster,
|
||||||
|
@ -504,6 +542,11 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
catalog_sync,
|
catalog_sync,
|
||||||
storage_serialization_policy,
|
storage_serialization_policy,
|
||||||
table_options,
|
table_options,
|
||||||
|
target_lag,
|
||||||
|
warehouse,
|
||||||
|
refresh_mode,
|
||||||
|
initialize,
|
||||||
|
require_user,
|
||||||
}) => Ok(Self {
|
}) => Ok(Self {
|
||||||
or_replace,
|
or_replace,
|
||||||
temporary,
|
temporary,
|
||||||
|
@ -511,6 +554,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
global,
|
global,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
transient,
|
transient,
|
||||||
|
dynamic,
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
@ -522,6 +566,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
without_rowid,
|
without_rowid,
|
||||||
like,
|
like,
|
||||||
clone,
|
clone,
|
||||||
|
version,
|
||||||
comment,
|
comment,
|
||||||
on_commit,
|
on_commit,
|
||||||
on_cluster,
|
on_cluster,
|
||||||
|
@ -549,6 +594,11 @@ impl TryFrom<Statement> for CreateTableBuilder {
|
||||||
catalog_sync,
|
catalog_sync,
|
||||||
storage_serialization_policy,
|
storage_serialization_policy,
|
||||||
table_options,
|
table_options,
|
||||||
|
target_lag,
|
||||||
|
warehouse,
|
||||||
|
refresh_mode,
|
||||||
|
initialize,
|
||||||
|
require_user,
|
||||||
}),
|
}),
|
||||||
_ => Err(ParserError::ParserError(format!(
|
_ => Err(ParserError::ParserError(format!(
|
||||||
"Expected create table statement, but received: {stmt}"
|
"Expected create table statement, but received: {stmt}"
|
||||||
|
|
|
@ -10521,6 +10521,48 @@ impl fmt::Display for CreateTableLike {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specifies the refresh mode for the dynamic table.
|
||||||
|
///
|
||||||
|
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table)
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum RefreshModeKind {
|
||||||
|
Auto,
|
||||||
|
Full,
|
||||||
|
Incremental,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RefreshModeKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
RefreshModeKind::Auto => write!(f, "AUTO"),
|
||||||
|
RefreshModeKind::Full => write!(f, "FULL"),
|
||||||
|
RefreshModeKind::Incremental => write!(f, "INCREMENTAL"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies the behavior of the initial refresh of the dynamic table.
|
||||||
|
///
|
||||||
|
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table)
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum InitializeKind {
|
||||||
|
OnCreate,
|
||||||
|
OnSchedule,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for InitializeKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
InitializeKind::OnCreate => write!(f, "ON_CREATE"),
|
||||||
|
InitializeKind::OnSchedule => write!(f, "ON_SCHEDULE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tokenizer::Location;
|
use crate::tokenizer::Location;
|
||||||
|
|
|
@ -579,6 +579,7 @@ impl Spanned for CreateTable {
|
||||||
temporary: _, // bool
|
temporary: _, // bool
|
||||||
external: _, // bool
|
external: _, // bool
|
||||||
global: _, // bool
|
global: _, // bool
|
||||||
|
dynamic: _, // bool
|
||||||
if_not_exists: _, // bool
|
if_not_exists: _, // bool
|
||||||
transient: _, // bool
|
transient: _, // bool
|
||||||
volatile: _, // bool
|
volatile: _, // bool
|
||||||
|
@ -619,6 +620,12 @@ impl Spanned for CreateTable {
|
||||||
catalog_sync: _, // todo, Snowflake specific
|
catalog_sync: _, // todo, Snowflake specific
|
||||||
storage_serialization_policy: _,
|
storage_serialization_policy: _,
|
||||||
table_options,
|
table_options,
|
||||||
|
target_lag: _,
|
||||||
|
warehouse: _,
|
||||||
|
version: _,
|
||||||
|
refresh_mode: _,
|
||||||
|
initialize: _,
|
||||||
|
require_user: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
union_spans(
|
union_spans(
|
||||||
|
|
|
@ -235,6 +235,8 @@ impl Dialect for SnowflakeDialect {
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let dynamic = parser.parse_keyword(Keyword::DYNAMIC);
|
||||||
|
|
||||||
let mut temporary = false;
|
let mut temporary = false;
|
||||||
let mut volatile = false;
|
let mut volatile = false;
|
||||||
let mut transient = false;
|
let mut transient = false;
|
||||||
|
@ -259,7 +261,7 @@ impl Dialect for SnowflakeDialect {
|
||||||
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) {
|
} else if parser.parse_keyword(Keyword::TABLE) {
|
||||||
return Some(parse_create_table(
|
return Some(parse_create_table(
|
||||||
or_replace, global, temporary, volatile, transient, iceberg, parser,
|
or_replace, global, temporary, volatile, transient, iceberg, dynamic, parser,
|
||||||
));
|
));
|
||||||
} else if parser.parse_keyword(Keyword::DATABASE) {
|
} else if parser.parse_keyword(Keyword::DATABASE) {
|
||||||
return Some(parse_create_database(or_replace, transient, parser));
|
return Some(parse_create_database(or_replace, transient, parser));
|
||||||
|
@ -614,6 +616,7 @@ fn parse_alter_session(parser: &mut Parser, set: bool) -> Result<Statement, Pars
|
||||||
/// Parse snowflake create table statement.
|
/// Parse snowflake create table statement.
|
||||||
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
|
||||||
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn parse_create_table(
|
pub fn parse_create_table(
|
||||||
or_replace: bool,
|
or_replace: bool,
|
||||||
global: Option<bool>,
|
global: Option<bool>,
|
||||||
|
@ -621,6 +624,7 @@ pub fn parse_create_table(
|
||||||
volatile: bool,
|
volatile: bool,
|
||||||
transient: bool,
|
transient: bool,
|
||||||
iceberg: bool,
|
iceberg: bool,
|
||||||
|
dynamic: bool,
|
||||||
parser: &mut Parser,
|
parser: &mut Parser,
|
||||||
) -> Result<Statement, ParserError> {
|
) -> Result<Statement, ParserError> {
|
||||||
let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
|
@ -634,6 +638,7 @@ pub fn parse_create_table(
|
||||||
.volatile(volatile)
|
.volatile(volatile)
|
||||||
.iceberg(iceberg)
|
.iceberg(iceberg)
|
||||||
.global(global)
|
.global(global)
|
||||||
|
.dynamic(dynamic)
|
||||||
.hive_formats(Some(Default::default()));
|
.hive_formats(Some(Default::default()));
|
||||||
|
|
||||||
// Snowflake does not enforce order of the parameters in the statement. The parser needs to
|
// Snowflake does not enforce order of the parameters in the statement. The parser needs to
|
||||||
|
@ -772,6 +777,49 @@ pub fn parse_create_table(
|
||||||
Keyword::IF if parser.parse_keywords(&[Keyword::NOT, Keyword::EXISTS]) => {
|
Keyword::IF if parser.parse_keywords(&[Keyword::NOT, Keyword::EXISTS]) => {
|
||||||
builder = builder.if_not_exists(true);
|
builder = builder.if_not_exists(true);
|
||||||
}
|
}
|
||||||
|
Keyword::TARGET_LAG => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let target_lag = parser.parse_literal_string()?;
|
||||||
|
builder = builder.target_lag(Some(target_lag));
|
||||||
|
}
|
||||||
|
Keyword::WAREHOUSE => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let warehouse = parser.parse_identifier()?;
|
||||||
|
builder = builder.warehouse(Some(warehouse));
|
||||||
|
}
|
||||||
|
Keyword::AT | Keyword::BEFORE => {
|
||||||
|
parser.prev_token();
|
||||||
|
let version = parser.maybe_parse_table_version()?;
|
||||||
|
builder = builder.version(version);
|
||||||
|
}
|
||||||
|
Keyword::REFRESH_MODE => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let refresh_mode = match parser.parse_one_of_keywords(&[
|
||||||
|
Keyword::AUTO,
|
||||||
|
Keyword::FULL,
|
||||||
|
Keyword::INCREMENTAL,
|
||||||
|
]) {
|
||||||
|
Some(Keyword::AUTO) => Some(RefreshModeKind::Auto),
|
||||||
|
Some(Keyword::FULL) => Some(RefreshModeKind::Full),
|
||||||
|
Some(Keyword::INCREMENTAL) => Some(RefreshModeKind::Incremental),
|
||||||
|
_ => return parser.expected("AUTO, FULL or INCREMENTAL", next_token),
|
||||||
|
};
|
||||||
|
builder = builder.refresh_mode(refresh_mode);
|
||||||
|
}
|
||||||
|
Keyword::INITIALIZE => {
|
||||||
|
parser.expect_token(&Token::Eq)?;
|
||||||
|
let initialize = match parser
|
||||||
|
.parse_one_of_keywords(&[Keyword::ON_CREATE, Keyword::ON_SCHEDULE])
|
||||||
|
{
|
||||||
|
Some(Keyword::ON_CREATE) => Some(InitializeKind::OnCreate),
|
||||||
|
Some(Keyword::ON_SCHEDULE) => Some(InitializeKind::OnSchedule),
|
||||||
|
_ => return parser.expected("ON_CREATE or ON_SCHEDULE", next_token),
|
||||||
|
};
|
||||||
|
builder = builder.initialize(initialize);
|
||||||
|
}
|
||||||
|
Keyword::REQUIRE if parser.parse_keyword(Keyword::USER) => {
|
||||||
|
builder = builder.require_user(true);
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return parser.expected("end of statement", next_token);
|
return parser.expected("end of statement", next_token);
|
||||||
}
|
}
|
||||||
|
@ -782,21 +830,9 @@ pub fn parse_create_table(
|
||||||
builder = builder.columns(columns).constraints(constraints);
|
builder = builder.columns(columns).constraints(constraints);
|
||||||
}
|
}
|
||||||
Token::EOF => {
|
Token::EOF => {
|
||||||
if !builder.validate_schema_info() {
|
|
||||||
return Err(ParserError::ParserError(
|
|
||||||
"unexpected end of input".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Token::SemiColon => {
|
Token::SemiColon => {
|
||||||
if !builder.validate_schema_info() {
|
|
||||||
return Err(ParserError::ParserError(
|
|
||||||
"unexpected end of input".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.prev_token();
|
parser.prev_token();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,6 +300,7 @@ define_keywords!(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
DOUBLE,
|
DOUBLE,
|
||||||
DOW,
|
DOW,
|
||||||
|
DOWNSTREAM,
|
||||||
DOY,
|
DOY,
|
||||||
DROP,
|
DROP,
|
||||||
DRY,
|
DRY,
|
||||||
|
@ -445,10 +446,12 @@ define_keywords!(
|
||||||
INCLUDE_NULL_VALUES,
|
INCLUDE_NULL_VALUES,
|
||||||
INCLUDING,
|
INCLUDING,
|
||||||
INCREMENT,
|
INCREMENT,
|
||||||
|
INCREMENTAL,
|
||||||
INDEX,
|
INDEX,
|
||||||
INDICATOR,
|
INDICATOR,
|
||||||
INHERIT,
|
INHERIT,
|
||||||
INHERITS,
|
INHERITS,
|
||||||
|
INITIALIZE,
|
||||||
INITIALLY,
|
INITIALLY,
|
||||||
INNER,
|
INNER,
|
||||||
INOUT,
|
INOUT,
|
||||||
|
@ -641,6 +644,8 @@ define_keywords!(
|
||||||
ON,
|
ON,
|
||||||
ONE,
|
ONE,
|
||||||
ONLY,
|
ONLY,
|
||||||
|
ON_CREATE,
|
||||||
|
ON_SCHEDULE,
|
||||||
OPEN,
|
OPEN,
|
||||||
OPENJSON,
|
OPENJSON,
|
||||||
OPERATE,
|
OPERATE,
|
||||||
|
@ -742,6 +747,7 @@ define_keywords!(
|
||||||
REF,
|
REF,
|
||||||
REFERENCES,
|
REFERENCES,
|
||||||
REFERENCING,
|
REFERENCING,
|
||||||
|
REFRESH_MODE,
|
||||||
REGCLASS,
|
REGCLASS,
|
||||||
REGEXP,
|
REGEXP,
|
||||||
REGR_AVGX,
|
REGR_AVGX,
|
||||||
|
@ -768,6 +774,7 @@ define_keywords!(
|
||||||
REPLICA,
|
REPLICA,
|
||||||
REPLICATE,
|
REPLICATE,
|
||||||
REPLICATION,
|
REPLICATION,
|
||||||
|
REQUIRE,
|
||||||
RESET,
|
RESET,
|
||||||
RESOLVE,
|
RESOLVE,
|
||||||
RESOURCE,
|
RESOURCE,
|
||||||
|
@ -905,6 +912,7 @@ define_keywords!(
|
||||||
TABLESPACE,
|
TABLESPACE,
|
||||||
TAG,
|
TAG,
|
||||||
TARGET,
|
TARGET,
|
||||||
|
TARGET_LAG,
|
||||||
TASK,
|
TASK,
|
||||||
TBLPROPERTIES,
|
TBLPROPERTIES,
|
||||||
TEMP,
|
TEMP,
|
||||||
|
|
|
@ -700,6 +700,7 @@ fn test_duckdb_union_datatype() {
|
||||||
transient: Default::default(),
|
transient: Default::default(),
|
||||||
volatile: Default::default(),
|
volatile: Default::default(),
|
||||||
iceberg: Default::default(),
|
iceberg: Default::default(),
|
||||||
|
dynamic: Default::default(),
|
||||||
name: ObjectName::from(vec!["tbl1".into()]),
|
name: ObjectName::from(vec!["tbl1".into()]),
|
||||||
columns: vec![
|
columns: vec![
|
||||||
ColumnDef {
|
ColumnDef {
|
||||||
|
@ -774,7 +775,13 @@ fn test_duckdb_union_datatype() {
|
||||||
catalog: Default::default(),
|
catalog: Default::default(),
|
||||||
catalog_sync: Default::default(),
|
catalog_sync: Default::default(),
|
||||||
storage_serialization_policy: Default::default(),
|
storage_serialization_policy: Default::default(),
|
||||||
table_options: CreateTableOptions::None
|
table_options: CreateTableOptions::None,
|
||||||
|
target_lag: None,
|
||||||
|
warehouse: None,
|
||||||
|
version: None,
|
||||||
|
refresh_mode: None,
|
||||||
|
initialize: None,
|
||||||
|
require_user: Default::default(),
|
||||||
}),
|
}),
|
||||||
stmt
|
stmt
|
||||||
);
|
);
|
||||||
|
|
|
@ -1848,6 +1848,7 @@ fn parse_create_table_with_valid_options() {
|
||||||
temporary: false,
|
temporary: false,
|
||||||
external: false,
|
external: false,
|
||||||
global: None,
|
global: None,
|
||||||
|
dynamic: false,
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
transient: false,
|
transient: false,
|
||||||
volatile: false,
|
volatile: false,
|
||||||
|
@ -1924,7 +1925,13 @@ fn parse_create_table_with_valid_options() {
|
||||||
catalog: None,
|
catalog: None,
|
||||||
catalog_sync: None,
|
catalog_sync: None,
|
||||||
storage_serialization_policy: None,
|
storage_serialization_policy: None,
|
||||||
table_options: CreateTableOptions::With(with_options)
|
table_options: CreateTableOptions::With(with_options),
|
||||||
|
target_lag: None,
|
||||||
|
warehouse: None,
|
||||||
|
version: None,
|
||||||
|
refresh_mode: None,
|
||||||
|
initialize: None,
|
||||||
|
require_user: false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2031,6 +2038,7 @@ fn parse_create_table_with_identity_column() {
|
||||||
temporary: false,
|
temporary: false,
|
||||||
external: false,
|
external: false,
|
||||||
global: None,
|
global: None,
|
||||||
|
dynamic: false,
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
transient: false,
|
transient: false,
|
||||||
volatile: false,
|
volatile: false,
|
||||||
|
@ -2088,7 +2096,13 @@ fn parse_create_table_with_identity_column() {
|
||||||
catalog: None,
|
catalog: None,
|
||||||
catalog_sync: None,
|
catalog_sync: None,
|
||||||
storage_serialization_policy: None,
|
storage_serialization_policy: None,
|
||||||
table_options: CreateTableOptions::None
|
table_options: CreateTableOptions::None,
|
||||||
|
target_lag: None,
|
||||||
|
warehouse: None,
|
||||||
|
version: None,
|
||||||
|
refresh_mode: None,
|
||||||
|
initialize: None,
|
||||||
|
require_user: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5932,6 +5932,7 @@ fn parse_trigger_related_functions() {
|
||||||
temporary: false,
|
temporary: false,
|
||||||
external: false,
|
external: false,
|
||||||
global: None,
|
global: None,
|
||||||
|
dynamic: false,
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
transient: false,
|
transient: false,
|
||||||
volatile: false,
|
volatile: false,
|
||||||
|
@ -5997,7 +5998,13 @@ fn parse_trigger_related_functions() {
|
||||||
catalog: None,
|
catalog: None,
|
||||||
catalog_sync: None,
|
catalog_sync: None,
|
||||||
storage_serialization_policy: None,
|
storage_serialization_policy: None,
|
||||||
table_options: CreateTableOptions::None
|
table_options: CreateTableOptions::None,
|
||||||
|
target_lag: None,
|
||||||
|
warehouse: None,
|
||||||
|
version: None,
|
||||||
|
refresh_mode: None,
|
||||||
|
initialize: None,
|
||||||
|
require_user: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -528,23 +528,6 @@ fn test_snowflake_create_table_comment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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";
|
||||||
|
@ -1019,27 +1002,6 @@ fn test_snowflake_create_table_trailing_options() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_snowflake_create_table_valid_schema_info() {
|
|
||||||
// Validate there's exactly one source of information on the schema of the new table
|
|
||||||
assert_eq!(
|
|
||||||
snowflake()
|
|
||||||
.parse_sql_statements("CREATE TABLE dst")
|
|
||||||
.is_err(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
snowflake().parse_sql_statements("CREATE OR REPLACE TEMP TABLE dst LIKE src AS (SELECT * FROM CUSTOMERS) ON COMMIT PRESERVE ROWS").is_err(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
snowflake()
|
|
||||||
.parse_sql_statements("CREATE OR REPLACE TEMP TABLE dst CLONE customers LIKE customer2")
|
|
||||||
.is_err(),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_sf_create_or_replace_view_with_comment_missing_equal() {
|
fn parse_sf_create_or_replace_view_with_comment_missing_equal() {
|
||||||
assert!(snowflake_and_generic()
|
assert!(snowflake_and_generic()
|
||||||
|
@ -1104,6 +1066,56 @@ fn parse_sf_create_table_or_view_with_dollar_quoted_comment() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_dynamic_table() {
|
||||||
|
snowflake().verified_stmt(r#"CREATE OR REPLACE DYNAMIC TABLE my_dynamic_table TARGET_LAG='20 minutes' WAREHOUSE=mywh AS SELECT product_id, product_name FROM staging_table"#);
|
||||||
|
snowflake()
|
||||||
|
.parse_sql_statements(
|
||||||
|
r#"
|
||||||
|
CREATE DYNAMIC ICEBERG TABLE my_dynamic_table (date TIMESTAMP_NTZ, id NUMBER, content STRING)
|
||||||
|
TARGET_LAG = '20 minutes'
|
||||||
|
WAREHOUSE = mywh
|
||||||
|
EXTERNAL_VOLUME = 'my_external_volume'
|
||||||
|
CATALOG = 'SNOWFLAKE'
|
||||||
|
BASE_LOCATION = 'my_iceberg_table'
|
||||||
|
AS
|
||||||
|
SELECT product_id, product_name FROM staging_table;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
snowflake()
|
||||||
|
.parse_sql_statements(
|
||||||
|
r#"
|
||||||
|
CREATE DYNAMIC TABLE my_dynamic_table (date TIMESTAMP_NTZ, id NUMBER, content VARIANT)
|
||||||
|
TARGET_LAG = '20 minutes'
|
||||||
|
WAREHOUSE = mywh
|
||||||
|
CLUSTER BY (date, id)
|
||||||
|
AS
|
||||||
|
SELECT product_id, product_name FROM staging_table;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
snowflake().parse_sql_statements(r#"
|
||||||
|
CREATE DYNAMIC TABLE my_cloned_dynamic_table CLONE my_dynamic_table AT (TIMESTAMP => TO_TIMESTAMP_TZ('04/05/2013 01:02:03', 'mm/dd/yyyy hh24:mi:ss'));
|
||||||
|
"#).unwrap();
|
||||||
|
|
||||||
|
snowflake()
|
||||||
|
.parse_sql_statements(
|
||||||
|
r#"
|
||||||
|
CREATE DYNAMIC TABLE my_dynamic_table
|
||||||
|
TARGET_LAG = 'DOWNSTREAM'
|
||||||
|
WAREHOUSE = mywh
|
||||||
|
INITIALIZE = on_schedule
|
||||||
|
REQUIRE USER
|
||||||
|
AS
|
||||||
|
SELECT product_id, product_name FROM staging_table;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sf_derived_table_in_parenthesis() {
|
fn test_sf_derived_table_in_parenthesis() {
|
||||||
// Nesting a subquery in an extra set of parentheses is non-standard,
|
// Nesting a subquery in an extra set of parentheses is non-standard,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue