mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-23 15:34:09 +00:00
Improve MySQL option parsing in index definitions (#1997)
This commit is contained in:
parent
18b4a14e0f
commit
67fca82495
9 changed files with 628 additions and 498 deletions
502
src/ast/ddl.rs
502
src/ast/ddl.rs
|
@ -31,14 +31,36 @@ use sqlparser_derive::{Visit, VisitMut};
|
|||
use crate::ast::value::escape_single_quote_string;
|
||||
use crate::ast::{
|
||||
display_comma_separated, display_separated, ArgMode, CommentDef, CreateFunctionBody,
|
||||
CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull,
|
||||
FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexColumn, MySQLColumnPosition,
|
||||
ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, Spanned,
|
||||
SqlOption, Tag, Value, ValueWithSpan,
|
||||
CreateFunctionUsing, CreateTableOptions, DataType, Expr, FileFormat, FunctionBehavior,
|
||||
FunctionCalledOnNull, FunctionDeterminismSpecifier, FunctionParallel, HiveDistributionStyle,
|
||||
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, MySQLColumnPosition, ObjectName, OnCommit,
|
||||
OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RowAccessPolicy,
|
||||
SequenceOptions, Spanned, SqlOption, StorageSerializationPolicy, Tag, Value, ValueWithSpan,
|
||||
WrappedCollection,
|
||||
};
|
||||
use crate::display_utils::{DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
|
||||
use crate::keywords::Keyword;
|
||||
use crate::tokenizer::{Span, Token};
|
||||
|
||||
/// 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 fmt::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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// ALTER TABLE operation REPLICA IDENTITY values
|
||||
/// See [Postgres ALTER TABLE docs](https://www.postgresql.org/docs/current/sql-altertable.html)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
|
@ -1103,6 +1125,8 @@ pub enum TableConstraint {
|
|||
index_type: Option<IndexType>,
|
||||
/// Referred column identifier list.
|
||||
columns: Vec<IndexColumn>,
|
||||
/// Optional index options such as `USING`; see [`IndexOption`].
|
||||
index_options: Vec<IndexOption>,
|
||||
},
|
||||
/// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
|
||||
/// and MySQL displays both the same way, it is part of this definition as well.
|
||||
|
@ -1231,6 +1255,7 @@ impl fmt::Display for TableConstraint {
|
|||
name,
|
||||
index_type,
|
||||
columns,
|
||||
index_options,
|
||||
} => {
|
||||
write!(f, "{}", if *display_as_key { "KEY" } else { "INDEX" })?;
|
||||
if let Some(name) = name {
|
||||
|
@ -1240,7 +1265,9 @@ impl fmt::Display for TableConstraint {
|
|||
write!(f, " USING {index_type}")?;
|
||||
}
|
||||
write!(f, " ({})", display_comma_separated(columns))?;
|
||||
|
||||
if !index_options.is_empty() {
|
||||
write!(f, " {}", display_comma_separated(index_options))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Self::FulltextOrSpatial {
|
||||
|
@ -1355,17 +1382,20 @@ impl fmt::Display for IndexType {
|
|||
}
|
||||
}
|
||||
|
||||
/// MySQLs index option.
|
||||
/// MySQL index option, used in [`CREATE TABLE`], [`CREATE INDEX`], and [`ALTER TABLE`].
|
||||
///
|
||||
/// This structure used here [`MySQL` CREATE TABLE][1], [`MySQL` CREATE INDEX][2].
|
||||
///
|
||||
/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
|
||||
/// [2]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html
|
||||
/// [`CREATE TABLE`]: https://dev.mysql.com/doc/refman/8.4/en/create-table.html
|
||||
/// [`CREATE INDEX`]: https://dev.mysql.com/doc/refman/8.4/en/create-index.html
|
||||
/// [`ALTER TABLE`]: https://dev.mysql.com/doc/refman/8.4/en/alter-table.html
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
pub enum IndexOption {
|
||||
/// `USING { BTREE | HASH }`: Index type to use for the index.
|
||||
///
|
||||
/// Note that we permissively parse non-MySQL index types, like `GIN`.
|
||||
Using(IndexType),
|
||||
/// `COMMENT 'string'`: Specifies a comment for the index.
|
||||
Comment(String),
|
||||
}
|
||||
|
||||
|
@ -2294,6 +2324,458 @@ impl fmt::Display for ClusteredBy {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
pub index_options: Vec<IndexOption>,
|
||||
/// [MySQL] allows a subset of options normally used for `ALTER TABLE`:
|
||||
///
|
||||
/// - `ALGORITHM`
|
||||
/// - `LOCK`
|
||||
///
|
||||
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/create-index.html
|
||||
pub alter_options: Vec<AlterTableOperation>,
|
||||
}
|
||||
|
||||
impl fmt::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_comma_separated(&self.columns))?;
|
||||
if !self.include.is_empty() {
|
||||
write!(f, " INCLUDE ({})", display_comma_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}")?;
|
||||
}
|
||||
if !self.index_options.is_empty() {
|
||||
write!(f, " {}", display_separated(&self.index_options, " "))?;
|
||||
}
|
||||
if !self.alter_options.is_empty() {
|
||||
write!(f, " {}", display_separated(&self.alter_options, " "))?;
|
||||
}
|
||||
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 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>,
|
||||
// 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>,
|
||||
}
|
||||
|
||||
impl fmt::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}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 { "" },
|
||||
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 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(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
|
|
470
src/ast/dml.rs
470
src/ast/dml.rs
|
@ -29,476 +29,14 @@ use serde::{Deserialize, Serialize};
|
|||
#[cfg(feature = "visitor")]
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use crate::display_utils::{indented_list, DisplayCommaSeparated, Indent, NewLine, SpaceOrNewline};
|
||||
|
||||
pub use super::ddl::{ColumnDef, TableConstraint};
|
||||
use crate::display_utils::{indented_list, Indent, SpaceOrNewline};
|
||||
|
||||
use super::{
|
||||
display_comma_separated, display_separated, query::InputFormatClause, Assignment, ClusteredBy,
|
||||
CommentDef, CreateTableOptions, Expr, FileFormat, FromTable, HiveDistributionStyle, HiveFormat,
|
||||
HiveIOFormat, HiveRowFormat, Ident, IndexType, InsertAliases, MysqlInsertPriority, ObjectName,
|
||||
OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SelectItem,
|
||||
Setting, SqliteOnConflict, StorageSerializationPolicy, TableObject, TableWithJoins, Tag,
|
||||
WrappedCollection,
|
||||
display_comma_separated, query::InputFormatClause, Assignment, Expr, FromTable, Ident,
|
||||
InsertAliases, MysqlInsertPriority, ObjectName, OnInsert, OrderByExpr, Query, SelectItem,
|
||||
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 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>,
|
||||
// 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>,
|
||||
}
|
||||
|
||||
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}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 { "" },
|
||||
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 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.
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
|
|
|
@ -24,9 +24,8 @@ use serde::{Deserialize, Serialize};
|
|||
#[cfg(feature = "visitor")]
|
||||
use sqlparser_derive::{Visit, VisitMut};
|
||||
|
||||
use super::super::dml::CreateTable;
|
||||
use crate::ast::{
|
||||
ClusteredBy, ColumnDef, CommentDef, CreateTableOptions, Expr, FileFormat,
|
||||
ClusteredBy, ColumnDef, CommentDef, CreateTable, CreateTableOptions, Expr, FileFormat,
|
||||
HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit, OneOrManyWithParens, Query,
|
||||
RowAccessPolicy, Statement, StorageSerializationPolicy, TableConstraint, Tag,
|
||||
WrappedCollection,
|
||||
|
|
|
@ -63,14 +63,14 @@ pub use self::ddl::{
|
|||
AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
|
||||
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy,
|
||||
ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction,
|
||||
Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode,
|
||||
IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind,
|
||||
IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner,
|
||||
Partition, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity,
|
||||
TableConstraint, TagsColumnOption, UserDefinedTypeCompositeAttributeDef,
|
||||
UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
CreateIndex, CreateTable, Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs,
|
||||
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
|
||||
IdentityPropertyKind, IdentityPropertyOrder, IndexColumn, IndexOption, IndexType,
|
||||
KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction,
|
||||
RenameTableNameKind, ReplicaIdentity, TableConstraint, TagsColumnOption,
|
||||
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
|
||||
};
|
||||
pub use self::dml::{CreateIndex, CreateTable, Delete, IndexColumn, Insert};
|
||||
pub use self::dml::{Delete, Insert};
|
||||
pub use self::operator::{BinaryOperator, UnaryOperator};
|
||||
pub use self::query::{
|
||||
AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
|
||||
|
|
|
@ -713,6 +713,7 @@ impl Spanned for TableConstraint {
|
|||
name,
|
||||
index_type: _,
|
||||
columns,
|
||||
index_options: _,
|
||||
} => union_spans(
|
||||
name.iter()
|
||||
.map(|i| i.span)
|
||||
|
@ -747,6 +748,8 @@ impl Spanned for CreateIndex {
|
|||
nulls_distinct: _, // bool
|
||||
with,
|
||||
predicate,
|
||||
index_options: _,
|
||||
alter_options,
|
||||
} = self;
|
||||
|
||||
union_spans(
|
||||
|
@ -756,7 +759,8 @@ impl Spanned for CreateIndex {
|
|||
.chain(columns.iter().map(|i| i.column.span()))
|
||||
.chain(include.iter().map(|i| i.span))
|
||||
.chain(with.iter().map(|i| i.span()))
|
||||
.chain(predicate.iter().map(|i| i.span())),
|
||||
.chain(predicate.iter().map(|i| i.span()))
|
||||
.chain(alter_options.iter().map(|i| i.span())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7083,6 +7083,22 @@ impl<'a> Parser<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
// MySQL options (including the modern style of `USING` after the column list instead of
|
||||
// before, which is deprecated) shouldn't conflict with other preceding options (e.g. `WITH
|
||||
// PARSER` won't be caught by the above `WITH` clause parsing because MySQL doesn't set that
|
||||
// support flag). This is probably invalid syntax for other dialects, but it is simpler to
|
||||
// parse it anyway (as we do inside `ALTER TABLE` and `CREATE TABLE` parsing).
|
||||
let index_options = self.parse_index_options()?;
|
||||
|
||||
// MySQL allows `ALGORITHM` and `LOCK` options. Unlike in `ALTER TABLE`, they need not be comma separated.
|
||||
let mut alter_options = Vec::new();
|
||||
while self
|
||||
.peek_one_of_keywords(&[Keyword::ALGORITHM, Keyword::LOCK])
|
||||
.is_some()
|
||||
{
|
||||
alter_options.push(self.parse_alter_table_operation()?)
|
||||
}
|
||||
|
||||
Ok(Statement::CreateIndex(CreateIndex {
|
||||
name: index_name,
|
||||
table_name,
|
||||
|
@ -7095,6 +7111,8 @@ impl<'a> Parser<'a> {
|
|||
nulls_distinct,
|
||||
with,
|
||||
predicate,
|
||||
index_options,
|
||||
alter_options,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -8407,12 +8425,14 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let index_type = self.parse_optional_using_then_index_type()?;
|
||||
let columns = self.parse_parenthesized_index_column_list()?;
|
||||
let index_options = self.parse_index_options()?;
|
||||
|
||||
Ok(Some(TableConstraint::Index {
|
||||
display_as_key,
|
||||
name,
|
||||
index_type,
|
||||
columns,
|
||||
index_options,
|
||||
}))
|
||||
}
|
||||
Token::Word(w)
|
||||
|
@ -17475,6 +17495,7 @@ mod tests {
|
|||
name: None,
|
||||
index_type: None,
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -17486,6 +17507,7 @@ mod tests {
|
|||
name: None,
|
||||
index_type: None,
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -17497,6 +17519,7 @@ mod tests {
|
|||
name: Some(Ident::with_quote('\'', "index")),
|
||||
index_type: None,
|
||||
columns: vec![mk_expected_col("c1"), mk_expected_col("c2")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -17508,6 +17531,7 @@ mod tests {
|
|||
name: None,
|
||||
index_type: Some(IndexType::BTree),
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -17519,6 +17543,7 @@ mod tests {
|
|||
name: None,
|
||||
index_type: Some(IndexType::Hash),
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -17530,6 +17555,7 @@ mod tests {
|
|||
name: Some(Ident::new("idx_name")),
|
||||
index_type: Some(IndexType::BTree),
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -17541,6 +17567,7 @@ mod tests {
|
|||
name: Some(Ident::new("idx_name")),
|
||||
index_type: Some(IndexType::Hash),
|
||||
columns: vec![mk_expected_col("c1")],
|
||||
index_options: vec![],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9189,7 +9189,7 @@ fn ensure_multiple_dialects_are_tested() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_index() {
|
||||
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test(name,age DESC)";
|
||||
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test(name, age DESC)";
|
||||
let indexed_columns: Vec<IndexColumn> = vec![
|
||||
IndexColumn {
|
||||
operator_class: None,
|
||||
|
@ -9235,7 +9235,7 @@ fn parse_create_index() {
|
|||
|
||||
#[test]
|
||||
fn test_create_index_with_using_function() {
|
||||
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test USING BTREE (name,age DESC)";
|
||||
let sql = "CREATE UNIQUE INDEX IF NOT EXISTS idx_name ON test USING BTREE (name, age DESC)";
|
||||
let indexed_columns: Vec<IndexColumn> = vec![
|
||||
IndexColumn {
|
||||
operator_class: None,
|
||||
|
@ -9273,6 +9273,8 @@ fn test_create_index_with_using_function() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq!("idx_name", name.to_string());
|
||||
assert_eq!("test", table_name.to_string());
|
||||
|
@ -9283,6 +9285,8 @@ fn test_create_index_with_using_function() {
|
|||
assert!(if_not_exists);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -9324,6 +9328,8 @@ fn test_create_index_with_with_clause() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
pretty_assertions::assert_eq!("title_idx", name.to_string());
|
||||
pretty_assertions::assert_eq!("films", table_name.to_string());
|
||||
|
@ -9333,6 +9339,8 @@ fn test_create_index_with_with_clause() {
|
|||
assert!(!if_not_exists);
|
||||
assert!(include.is_empty());
|
||||
pretty_assertions::assert_eq!(with_parameters, with);
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
|
|
@ -4215,3 +4215,31 @@ fn parse_show_charset() {
|
|||
mysql().verified_stmt("SHOW CHARSET WHERE charset = 'utf8mb4%'");
|
||||
mysql().verified_stmt("SHOW CHARSET LIKE 'utf8mb4%'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ddl_with_index_using() {
|
||||
let columns = "(name, age DESC)";
|
||||
let using = "USING BTREE";
|
||||
|
||||
for sql in [
|
||||
format!("CREATE INDEX idx_name ON test {using} {columns}"),
|
||||
format!("CREATE TABLE foo (name VARCHAR(255), age INT, KEY idx_name {using} {columns})"),
|
||||
format!("ALTER TABLE foo ADD KEY idx_name {using} {columns}"),
|
||||
format!("CREATE INDEX idx_name ON test{columns} {using}"),
|
||||
format!("CREATE TABLE foo (name VARCHAR(255), age INT, KEY idx_name {columns} {using})"),
|
||||
format!("ALTER TABLE foo ADD KEY idx_name {columns} {using}"),
|
||||
] {
|
||||
mysql_and_generic().verified_stmt(&sql);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_index_options() {
|
||||
mysql_and_generic()
|
||||
.verified_stmt("CREATE INDEX idx_name ON t(c1, c2) USING HASH LOCK = SHARED");
|
||||
mysql_and_generic()
|
||||
.verified_stmt("CREATE INDEX idx_name ON t(c1, c2) USING BTREE ALGORITHM = INPLACE");
|
||||
mysql_and_generic().verified_stmt(
|
||||
"CREATE INDEX idx_name ON t(c1, c2) USING BTREE LOCK = EXCLUSIVE ALGORITHM = DEFAULT",
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2486,7 +2486,7 @@ fn parse_array_multi_subscript() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_index() {
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1,col2)";
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1, col2)";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2500,6 +2500,8 @@ fn parse_create_index() {
|
|||
include,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["my_index"], &name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2510,6 +2512,8 @@ fn parse_create_index() {
|
|||
assert_eq_vec(&["col1", "col2"], &columns);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2517,7 +2521,7 @@ fn parse_create_index() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_anonymous_index() {
|
||||
let sql = "CREATE INDEX ON my_table(col1,col2)";
|
||||
let sql = "CREATE INDEX ON my_table(col1, col2)";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name,
|
||||
|
@ -2531,6 +2535,8 @@ fn parse_create_anonymous_index() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq!(None, name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2541,6 +2547,8 @@ fn parse_create_anonymous_index() {
|
|||
assert_eq_vec(&["col1", "col2"], &columns);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2577,7 +2585,7 @@ fn parse_create_indices_with_operator_classes() {
|
|||
.unwrap_or_default()
|
||||
);
|
||||
let multi_column_sql_statement = format!(
|
||||
"CREATE INDEX the_index_name ON users USING {expected_index_type} (column_name,concat_users_name(first_name, last_name){})",
|
||||
"CREATE INDEX the_index_name ON users USING {expected_index_type} (column_name, concat_users_name(first_name, last_name){})",
|
||||
expected_operator_class.as_ref().map(|oc| format!(" {oc}"))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
|
@ -2639,6 +2647,8 @@ fn parse_create_indices_with_operator_classes() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["the_index_name"], &name);
|
||||
assert_eq_vec(&["users"], &table_name);
|
||||
|
@ -2646,6 +2656,8 @@ fn parse_create_indices_with_operator_classes() {
|
|||
assert_eq!(expected_function_column, columns[0],);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2663,6 +2675,8 @@ fn parse_create_indices_with_operator_classes() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["the_index_name"], &name);
|
||||
assert_eq_vec(&["users"], &table_name);
|
||||
|
@ -2688,6 +2702,8 @@ fn parse_create_indices_with_operator_classes() {
|
|||
assert_eq!(expected_function_column, columns[1],);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2698,7 +2714,7 @@ fn parse_create_indices_with_operator_classes() {
|
|||
#[test]
|
||||
fn parse_create_bloom() {
|
||||
let sql =
|
||||
"CREATE INDEX bloomidx ON tbloom USING BLOOM (i1,i2,i3) WITH (length = 80, col1 = 2, col2 = 2, col3 = 4)";
|
||||
"CREATE INDEX bloomidx ON tbloom USING BLOOM (i1, i2, i3) WITH (length = 80, col1 = 2, col2 = 2, col3 = 4)";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2712,6 +2728,8 @@ fn parse_create_bloom() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["bloomidx"], &name);
|
||||
assert_eq_vec(&["tbloom"], &table_name);
|
||||
|
@ -2743,6 +2761,8 @@ fn parse_create_bloom() {
|
|||
],
|
||||
with
|
||||
);
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2764,6 +2784,8 @@ fn parse_create_brin() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["brin_sensor_data_recorded_at"], &name);
|
||||
assert_eq_vec(&["sensor_data"], &table_name);
|
||||
|
@ -2771,6 +2793,8 @@ fn parse_create_brin() {
|
|||
assert_eq_vec(&["recorded_at"], &columns);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2813,7 +2837,7 @@ fn parse_create_table_with_empty_inherits_fails() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_index_concurrently() {
|
||||
let sql = "CREATE INDEX CONCURRENTLY IF NOT EXISTS my_index ON my_table(col1,col2)";
|
||||
let sql = "CREATE INDEX CONCURRENTLY IF NOT EXISTS my_index ON my_table(col1, col2)";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2827,6 +2851,8 @@ fn parse_create_index_concurrently() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["my_index"], &name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2837,6 +2863,8 @@ fn parse_create_index_concurrently() {
|
|||
assert_eq_vec(&["col1", "col2"], &columns);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2844,7 +2872,7 @@ fn parse_create_index_concurrently() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_index_with_predicate() {
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1,col2) WHERE col3 IS NULL";
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1, col2) WHERE col3 IS NULL";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2858,6 +2886,8 @@ fn parse_create_index_with_predicate() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: Some(_),
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["my_index"], &name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2868,6 +2898,8 @@ fn parse_create_index_with_predicate() {
|
|||
assert_eq_vec(&["col1", "col2"], &columns);
|
||||
assert!(include.is_empty());
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2875,7 +2907,7 @@ fn parse_create_index_with_predicate() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_index_with_include() {
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1,col2) INCLUDE (col3)";
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1, col2) INCLUDE (col3, col4)";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2889,6 +2921,8 @@ fn parse_create_index_with_include() {
|
|||
nulls_distinct: None,
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["my_index"], &name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2897,8 +2931,10 @@ fn parse_create_index_with_include() {
|
|||
assert!(!concurrently);
|
||||
assert!(if_not_exists);
|
||||
assert_eq_vec(&["col1", "col2"], &columns);
|
||||
assert_eq_vec(&["col3"], &include);
|
||||
assert_eq_vec(&["col3", "col4"], &include);
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -2906,7 +2942,7 @@ fn parse_create_index_with_include() {
|
|||
|
||||
#[test]
|
||||
fn parse_create_index_with_nulls_distinct() {
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1,col2) NULLS NOT DISTINCT";
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1, col2) NULLS NOT DISTINCT";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2920,6 +2956,8 @@ fn parse_create_index_with_nulls_distinct() {
|
|||
nulls_distinct: Some(nulls_distinct),
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["my_index"], &name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2931,11 +2969,13 @@ fn parse_create_index_with_nulls_distinct() {
|
|||
assert!(include.is_empty());
|
||||
assert!(!nulls_distinct);
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1,col2) NULLS DISTINCT";
|
||||
let sql = "CREATE INDEX IF NOT EXISTS my_index ON my_table(col1, col2) NULLS DISTINCT";
|
||||
match pg().verified_stmt(sql) {
|
||||
Statement::CreateIndex(CreateIndex {
|
||||
name: Some(ObjectName(name)),
|
||||
|
@ -2949,6 +2989,8 @@ fn parse_create_index_with_nulls_distinct() {
|
|||
nulls_distinct: Some(nulls_distinct),
|
||||
with,
|
||||
predicate: None,
|
||||
index_options,
|
||||
alter_options,
|
||||
}) => {
|
||||
assert_eq_vec(&["my_index"], &name);
|
||||
assert_eq_vec(&["my_table"], &table_name);
|
||||
|
@ -2960,6 +3002,8 @@ fn parse_create_index_with_nulls_distinct() {
|
|||
assert!(include.is_empty());
|
||||
assert!(nulls_distinct);
|
||||
assert!(with.is_empty());
|
||||
assert!(index_options.is_empty());
|
||||
assert!(alter_options.is_empty());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue