Skip to content

Commit 352ca4c

Browse files
committed
[Teradata] Add CREATE TABLE options
Adds support for some options in a Teradata `CREATE TABLE` statement.
1 parent 9833c03 commit 352ca4c

13 files changed

Lines changed: 251 additions & 13 deletions

src/ast/ddl.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3060,6 +3060,20 @@ pub struct CreateTable {
30603060
/// Redshift `BACKUP` option: `BACKUP { YES | NO }`
30613061
/// <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
30623062
pub backup: Option<bool>,
3063+
/// `MULTISET | SET` table-kind prefix.
3064+
/// `Some(true)` => `MULTISET`, `Some(false)` => `SET`.
3065+
///
3066+
/// [Teradata](https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS/Syntax-Elements/MULTISET-or-SET)
3067+
pub multiset: Option<bool>,
3068+
/// `FALLBACK` clause.
3069+
/// `Some(true)` => `FALLBACK`, `Some(false)` => `NO FALLBACK`
3070+
///
3071+
/// [Teradata](https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS/Syntax-Elements/FALLBACK-or-NO-FALLBACK)
3072+
pub fallback: Option<bool>,
3073+
/// `WITH DATA` clause on a `CREATE TABLE ... AS` statement.
3074+
///
3075+
/// [Teradata](https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS/Syntax-Elements/AS_clause/WITH-Clause-Phrase)
3076+
pub with_data: Option<WithData>,
30633077
}
30643078

30653079
impl fmt::Display for CreateTable {
@@ -3073,7 +3087,7 @@ impl fmt::Display for CreateTable {
30733087
// `CREATE TABLE t (a INT) AS SELECT a from t2`
30743088
write!(
30753089
f,
3076-
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
3090+
"CREATE {or_replace}{external}{global}{multiset}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
30773091
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
30783092
external = if self.external { "EXTERNAL " } else { "" },
30793093
snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
@@ -3087,14 +3101,20 @@ impl fmt::Display for CreateTable {
30873101
})
30883102
.unwrap_or(""),
30893103
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
3104+
multiset = self
3105+
.multiset
3106+
.map(|m| if m { "MULTISET " } else { "SET " })
3107+
.unwrap_or(""),
30903108
temporary = if self.temporary { "TEMPORARY " } else { "" },
30913109
transient = if self.transient { "TRANSIENT " } else { "" },
30923110
volatile = if self.volatile { "VOLATILE " } else { "" },
3093-
// Only for Snowflake
30943111
iceberg = if self.iceberg { "ICEBERG " } else { "" },
30953112
dynamic = if self.dynamic { "DYNAMIC " } else { "" },
30963113
name = self.name,
30973114
)?;
3115+
if let Some(fallback) = self.fallback {
3116+
write!(f, ", {}", if fallback { "FALLBACK" } else { "NO FALLBACK" })?;
3117+
}
30983118
if let Some(partition_of) = &self.partition_of {
30993119
write!(f, " PARTITION OF {partition_of}")?;
31003120
}
@@ -3379,6 +3399,41 @@ impl fmt::Display for CreateTable {
33793399
if let Some(query) = &self.query {
33803400
write!(f, " AS {query}")?;
33813401
}
3402+
if let Some(with_data) = &self.with_data {
3403+
write!(f, " {with_data}")?;
3404+
}
3405+
Ok(())
3406+
}
3407+
}
3408+
3409+
/// `WITH DATA` clause on `CREATE TABLE ... AS` statement.
3410+
///
3411+
/// [Teradata](https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS/Syntax-Elements/AS_clause/WITH-Clause-Phrase)
3412+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
3413+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3414+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3415+
pub struct WithData {
3416+
/// `true` for `WITH DATA`, `false` for `WITH NO DATA`.
3417+
pub data: bool,
3418+
/// `Some(true)` for `AND STATISTICS`, `Some(false)` for `AND NO STATISTICS`,
3419+
/// `None` if the `AND [NO] STATISTICS` sub-clause is omitted.
3420+
pub statistics: Option<bool>,
3421+
}
3422+
3423+
impl fmt::Display for WithData {
3424+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3425+
f.write_str("WITH ")?;
3426+
if !self.data {
3427+
f.write_str("NO ")?;
3428+
}
3429+
f.write_str("DATA")?;
3430+
if let Some(stats) = self.statistics {
3431+
f.write_str(" AND ")?;
3432+
if !stats {
3433+
f.write_str("NO ")?;
3434+
}
3435+
f.write_str("STATISTICS")?;
3436+
}
33823437
Ok(())
33833438
}
33843439
}

src/ast/helpers/stmt_create_table.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::ast::{
2929
DistStyle, Expr, FileFormat, ForValues, HiveDistributionStyle, HiveFormat, Ident,
3030
InitializeKind, ObjectName, OnCommit, OneOrManyWithParens, Query, RefreshModeKind,
3131
RowAccessPolicy, Statement, StorageLifecyclePolicy, StorageSerializationPolicy,
32-
TableConstraint, TableVersion, Tag, WrappedCollection,
32+
TableConstraint, TableVersion, Tag, WithData, WrappedCollection,
3333
};
3434

3535
use crate::parser::ParserError;
@@ -183,6 +183,12 @@ pub struct CreateTableBuilder {
183183
pub sortkey: Option<Vec<Expr>>,
184184
/// Redshift `BACKUP` option.
185185
pub backup: Option<bool>,
186+
/// `MULTISET | SET` table-kind prefix.
187+
pub multiset: Option<bool>,
188+
/// `FALLBACK` clause.
189+
pub fallback: Option<bool>,
190+
/// `WITH DATA` clause.
191+
pub with_data: Option<WithData>,
186192
}
187193

188194
impl CreateTableBuilder {
@@ -248,6 +254,9 @@ impl CreateTableBuilder {
248254
distkey: None,
249255
sortkey: None,
250256
backup: None,
257+
multiset: None,
258+
fallback: None,
259+
with_data: None,
251260
}
252261
}
253262
/// Set `OR REPLACE` for the CREATE TABLE statement.
@@ -556,6 +565,22 @@ impl CreateTableBuilder {
556565
self.backup = backup;
557566
self
558567
}
568+
/// Set `MULTISET | SET` table-kind prefix.
569+
/// Some(true) => `MULTISET`, Some(false) => `SET`.
570+
pub fn multiset(mut self, multiset: Option<bool>) -> Self {
571+
self.multiset = multiset;
572+
self
573+
}
574+
/// Set `FALLBACK` / `NO FALLBACK` flag.
575+
pub fn fallback(mut self, fallback: Option<bool>) -> Self {
576+
self.fallback = fallback;
577+
self
578+
}
579+
/// Set `WITH DATA` clause.
580+
pub fn with_data(mut self, with_data: Option<WithData>) -> Self {
581+
self.with_data = with_data;
582+
self
583+
}
559584
/// Consume the builder and produce a `CreateTable`.
560585
pub fn build(self) -> CreateTable {
561586
CreateTable {
@@ -618,6 +643,9 @@ impl CreateTableBuilder {
618643
distkey: self.distkey,
619644
sortkey: self.sortkey,
620645
backup: self.backup,
646+
multiset: self.multiset,
647+
fallback: self.fallback,
648+
with_data: self.with_data,
621649
}
622650
}
623651
}
@@ -699,6 +727,9 @@ impl From<CreateTable> for CreateTableBuilder {
699727
distkey: table.distkey,
700728
sortkey: table.sortkey,
701729
backup: table.backup,
730+
multiset: table.multiset,
731+
fallback: table.fallback,
732+
with_data: table.with_data,
702733
}
703734
}
704735
}

src/ast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pub use self::ddl::{
8181
PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity,
8282
TagsColumnOption, TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
8383
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
84-
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
84+
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, WithData,
8585
};
8686
pub use self::dml::{
8787
Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,

src/ast/spans.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,9 @@ impl Spanned for CreateTable {
604604
distkey: _,
605605
sortkey: _,
606606
backup: _,
607+
multiset: _,
608+
fallback: _,
609+
with_data: _,
607610
} = self;
608611

609612
union_spans(

src/dialect/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,16 @@ pub trait Dialect: Debug + Any {
12241224
false
12251225
}
12261226

1227+
/// Returns true if the dialect accepts a comma-separated list of table-level
1228+
/// options placed between the table name and the column-list parenthesis, e.g.
1229+
///
1230+
/// ```sql
1231+
/// CREATE TABLE foo, NO FALLBACK, NO BEFORE JOURNAL (col INTEGER)
1232+
/// ```
1233+
fn supports_leading_comma_before_table_options(&self) -> bool {
1234+
false
1235+
}
1236+
12271237
/// Returns true if the dialect supports PartiQL for querying semi-structured data
12281238
/// <https://partiql.org/index.html>
12291239
fn supports_partiql(&self) -> bool {

src/dialect/teradata.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,9 @@ impl Dialect for TeradataDialect {
8989
fn supports_string_literal_concatenation(&self) -> bool {
9090
true
9191
}
92+
93+
/// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS>
94+
fn supports_leading_comma_before_table_options(&self) -> bool {
95+
true
96+
}
9297
}

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ define_keywords!(
414414
FACTS,
415415
FAIL,
416416
FAILOVER,
417+
FALLBACK,
417418
FALSE,
418419
FAMILY,
419420
FETCH,

src/parser/mod.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5124,6 +5124,7 @@ impl<'a> Parser<'a> {
51245124
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
51255125
let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
51265126
let or_alter = self.parse_keywords(&[Keyword::OR, Keyword::ALTER]);
5127+
let multiset = self.maybe_parse_multiset();
51275128
let local = self.parse_one_of_keywords(&[Keyword::LOCAL]).is_some();
51285129
let global = self.parse_one_of_keywords(&[Keyword::GLOBAL]).is_some();
51295130
let transient = self.parse_one_of_keywords(&[Keyword::TRANSIENT]).is_some();
@@ -5137,13 +5138,14 @@ impl<'a> Parser<'a> {
51375138
let temporary = self
51385139
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
51395140
.is_some();
5141+
let volatile = self.parse_keyword(Keyword::VOLATILE);
51405142
let persistent = dialect_of!(self is DuckDbDialect)
51415143
&& self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
51425144
let create_view_params = self.parse_create_view_params()?;
51435145
if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
51445146
self.parse_create_snapshot_table().map(Into::into)
51455147
} else if self.parse_keyword(Keyword::TABLE) {
5146-
self.parse_create_table(or_replace, temporary, global, transient)
5148+
self.parse_create_table(or_replace, temporary, global, transient, volatile, multiset)
51475149
.map(Into::into)
51485150
} else if self.peek_keyword(Keyword::MATERIALIZED)
51495151
|| self.peek_keyword(Keyword::VIEW)
@@ -8470,11 +8472,25 @@ impl<'a> Parser<'a> {
84708472
temporary: bool,
84718473
global: Option<bool>,
84728474
transient: bool,
8475+
volatile: bool,
8476+
multiset: Option<bool>,
84738477
) -> Result<CreateTable, ParserError> {
84748478
let allow_unquoted_hyphen = dialect_of!(self is BigQueryDialect);
84758479
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
84768480
let table_name = self.parse_object_name(allow_unquoted_hyphen)?;
84778481

8482+
let fallback = if self.dialect.supports_leading_comma_before_table_options()
8483+
&& self.consume_token(&Token::Comma)
8484+
{
8485+
let fallback = self.maybe_parse_fallback()?;
8486+
if fallback.is_none() {
8487+
self.prev_token(); // Put back comma.
8488+
}
8489+
fallback
8490+
} else {
8491+
None
8492+
};
8493+
84788494
// PostgreSQL PARTITION OF for child partition tables
84798495
// Note: This is a PostgreSQL-specific feature, but the dialect check was intentionally
84808496
// removed to allow GenericDialect and other dialects to parse this syntax. This enables
@@ -8626,13 +8642,23 @@ impl<'a> Parser<'a> {
86268642
None
86278643
};
86288644

8645+
// `WITH DATA` clause only applies if there is a query body.
8646+
let with_data = if query.is_some() {
8647+
self.maybe_parse_with_data()?
8648+
} else {
8649+
None
8650+
};
8651+
86298652
Ok(CreateTableBuilder::new(table_name)
86308653
.temporary(temporary)
86318654
.columns(columns)
86328655
.constraints(constraints)
86338656
.or_replace(or_replace)
86348657
.if_not_exists(if_not_exists)
86358658
.transient(transient)
8659+
.volatile(volatile)
8660+
.multiset(multiset)
8661+
.fallback(fallback)
86368662
.hive_distribution(hive_distribution)
86378663
.hive_formats(hive_formats)
86388664
.global(global)
@@ -8652,6 +8678,7 @@ impl<'a> Parser<'a> {
86528678
.for_values(for_values)
86538679
.table_options(create_table_config.table_options)
86548680
.primary_key(primary_key)
8681+
.with_data(with_data)
86558682
.strict(strict)
86568683
.backup(backup)
86578684
.diststyle(diststyle)
@@ -8660,6 +8687,47 @@ impl<'a> Parser<'a> {
86608687
.build())
86618688
}
86628689

8690+
/// Parse `MULTISET` table-kind prefix on `CREATE TABLE`.
8691+
fn maybe_parse_multiset(&mut self) -> Option<bool> {
8692+
match self.parse_one_of_keywords(&[Keyword::SET, Keyword::MULTISET]) {
8693+
Some(Keyword::MULTISET) => Some(true),
8694+
Some(Keyword::SET) => Some(false),
8695+
_ => None,
8696+
}
8697+
}
8698+
8699+
/// Parse `FALLBACK` option on a `CREATE TABLE` statement,
8700+
fn maybe_parse_fallback(&mut self) -> Result<Option<bool>, ParserError> {
8701+
if self.parse_keywords(&[Keyword::NO, Keyword::FALLBACK]) {
8702+
Ok(Some(false))
8703+
} else if self.parse_keyword(Keyword::FALLBACK) {
8704+
Ok(Some(true))
8705+
} else {
8706+
Ok(None)
8707+
}
8708+
}
8709+
8710+
/// Parse [`WithData`] clause on `CREATE TABLE ... AS` statement.
8711+
fn maybe_parse_with_data(&mut self) -> Result<Option<WithData>, ParserError> {
8712+
let data = if self.parse_keywords(&[Keyword::WITH, Keyword::DATA]) {
8713+
true
8714+
} else if self.parse_keywords(&[Keyword::WITH, Keyword::NO, Keyword::DATA]) {
8715+
false
8716+
} else {
8717+
return Ok(None);
8718+
};
8719+
8720+
let statistics = if self.parse_keywords(&[Keyword::AND, Keyword::STATISTICS]) {
8721+
Some(true)
8722+
} else if self.parse_keywords(&[Keyword::AND, Keyword::NO, Keyword::STATISTICS]) {
8723+
Some(false)
8724+
} else {
8725+
None
8726+
};
8727+
8728+
Ok(Some(WithData { data, statistics }))
8729+
}
8730+
86638731
fn maybe_parse_create_table_like(
86648732
&mut self,
86658733
allow_unquoted_hyphen: bool,

tests/sqlparser_duckdb.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,9 @@ fn test_duckdb_union_datatype() {
794794
distkey: Default::default(),
795795
sortkey: Default::default(),
796796
backup: Default::default(),
797+
multiset: Default::default(),
798+
fallback: Default::default(),
799+
with_data: Default::default(),
797800
}),
798801
stmt
799802
);

tests/sqlparser_mssql.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,9 @@ fn parse_create_table_with_valid_options() {
20132013
distkey: None,
20142014
sortkey: None,
20152015
backup: None,
2016+
multiset: None,
2017+
fallback: None,
2018+
with_data: None,
20162019
})
20172020
);
20182021
}
@@ -2187,6 +2190,9 @@ fn parse_create_table_with_identity_column() {
21872190
distkey: None,
21882191
sortkey: None,
21892192
backup: None,
2193+
multiset: None,
2194+
fallback: None,
2195+
with_data: None,
21902196
}),
21912197
);
21922198
}

0 commit comments

Comments
 (0)