Skip to content

Commit f98c9d1

Browse files
authored
[Teradata] Add CREATE TABLE options (apache#2329)
1 parent 3f347e3 commit f98c9d1

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
@@ -3059,6 +3059,20 @@ pub struct CreateTable {
30593059
/// Redshift `BACKUP` option: `BACKUP { YES | NO }`
30603060
/// <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
30613061
pub backup: Option<bool>,
3062+
/// `MULTISET | SET` table-kind prefix.
3063+
/// `Some(true)` => `MULTISET`, `Some(false)` => `SET`.
3064+
///
3065+
/// [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)
3066+
pub multiset: Option<bool>,
3067+
/// `FALLBACK` clause.
3068+
/// `Some(true)` => `FALLBACK`, `Some(false)` => `NO FALLBACK`
3069+
///
3070+
/// [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)
3071+
pub fallback: Option<bool>,
3072+
/// `WITH DATA` clause on a `CREATE TABLE ... AS` statement.
3073+
///
3074+
/// [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)
3075+
pub with_data: Option<WithData>,
30623076
}
30633077

30643078
impl fmt::Display for CreateTable {
@@ -3072,7 +3086,7 @@ impl fmt::Display for CreateTable {
30723086
// `CREATE TABLE t (a INT) AS SELECT a from t2`
30733087
write!(
30743088
f,
3075-
"CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
3089+
"CREATE {or_replace}{external}{global}{multiset}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
30763090
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
30773091
external = if self.external { "EXTERNAL " } else { "" },
30783092
snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
@@ -3086,14 +3100,20 @@ impl fmt::Display for CreateTable {
30863100
})
30873101
.unwrap_or(""),
30883102
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
3103+
multiset = self
3104+
.multiset
3105+
.map(|m| if m { "MULTISET " } else { "SET " })
3106+
.unwrap_or(""),
30893107
temporary = if self.temporary { "TEMPORARY " } else { "" },
30903108
transient = if self.transient { "TRANSIENT " } else { "" },
30913109
volatile = if self.volatile { "VOLATILE " } else { "" },
3092-
// Only for Snowflake
30933110
iceberg = if self.iceberg { "ICEBERG " } else { "" },
30943111
dynamic = if self.dynamic { "DYNAMIC " } else { "" },
30953112
name = self.name,
30963113
)?;
3114+
if let Some(fallback) = self.fallback {
3115+
write!(f, ", {}", if fallback { "FALLBACK" } else { "NO FALLBACK" })?;
3116+
}
30973117
if let Some(partition_of) = &self.partition_of {
30983118
write!(f, " PARTITION OF {partition_of}")?;
30993119
}
@@ -3378,6 +3398,41 @@ impl fmt::Display for CreateTable {
33783398
if let Some(query) = &self.query {
33793399
write!(f, " AS {query}")?;
33803400
}
3401+
if let Some(with_data) = &self.with_data {
3402+
write!(f, " {with_data}")?;
3403+
}
3404+
Ok(())
3405+
}
3406+
}
3407+
3408+
/// `WITH DATA` clause on `CREATE TABLE ... AS` statement.
3409+
///
3410+
/// [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)
3411+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
3412+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3413+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
3414+
pub struct WithData {
3415+
/// `true` for `WITH DATA`, `false` for `WITH NO DATA`.
3416+
pub data: bool,
3417+
/// `Some(true)` for `AND STATISTICS`, `Some(false)` for `AND NO STATISTICS`,
3418+
/// `None` if the `AND [NO] STATISTICS` sub-clause is omitted.
3419+
pub statistics: Option<bool>,
3420+
}
3421+
3422+
impl fmt::Display for WithData {
3423+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3424+
f.write_str("WITH ")?;
3425+
if !self.data {
3426+
f.write_str("NO ")?;
3427+
}
3428+
f.write_str("DATA")?;
3429+
if let Some(stats) = self.statistics {
3430+
f.write_str(" AND ")?;
3431+
if !stats {
3432+
f.write_str("NO ")?;
3433+
}
3434+
f.write_str("STATISTICS")?;
3435+
}
33813436
Ok(())
33823437
}
33833438
}

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)
@@ -8476,11 +8478,25 @@ impl<'a> Parser<'a> {
84768478
temporary: bool,
84778479
global: Option<bool>,
84788480
transient: bool,
8481+
volatile: bool,
8482+
multiset: Option<bool>,
84798483
) -> Result<CreateTable, ParserError> {
84808484
let allow_unquoted_hyphen = dialect_of!(self is BigQueryDialect);
84818485
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
84828486
let table_name = self.parse_object_name(allow_unquoted_hyphen)?;
84838487

8488+
let fallback = if self.dialect.supports_leading_comma_before_table_options()
8489+
&& self.consume_token(&Token::Comma)
8490+
{
8491+
let fallback = self.maybe_parse_fallback()?;
8492+
if fallback.is_none() {
8493+
self.prev_token(); // Put back comma.
8494+
}
8495+
fallback
8496+
} else {
8497+
None
8498+
};
8499+
84848500
// PostgreSQL PARTITION OF for child partition tables
84858501
// Note: This is a PostgreSQL-specific feature, but the dialect check was intentionally
84868502
// removed to allow GenericDialect and other dialects to parse this syntax. This enables
@@ -8632,13 +8648,23 @@ impl<'a> Parser<'a> {
86328648
None
86338649
};
86348650

8651+
// `WITH DATA` clause only applies if there is a query body.
8652+
let with_data = if query.is_some() {
8653+
self.maybe_parse_with_data()?
8654+
} else {
8655+
None
8656+
};
8657+
86358658
Ok(CreateTableBuilder::new(table_name)
86368659
.temporary(temporary)
86378660
.columns(columns)
86388661
.constraints(constraints)
86398662
.or_replace(or_replace)
86408663
.if_not_exists(if_not_exists)
86418664
.transient(transient)
8665+
.volatile(volatile)
8666+
.multiset(multiset)
8667+
.fallback(fallback)
86428668
.hive_distribution(hive_distribution)
86438669
.hive_formats(hive_formats)
86448670
.global(global)
@@ -8658,6 +8684,7 @@ impl<'a> Parser<'a> {
86588684
.for_values(for_values)
86598685
.table_options(create_table_config.table_options)
86608686
.primary_key(primary_key)
8687+
.with_data(with_data)
86618688
.strict(strict)
86628689
.backup(backup)
86638690
.diststyle(diststyle)
@@ -8666,6 +8693,47 @@ impl<'a> Parser<'a> {
86668693
.build())
86678694
}
86688695

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