You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by if...@apache.org on 2026/04/29 09:50:21 UTC

(datafusion-sqlparser-rs) 01/01: [Teradata] Add `CREATE TABLE` options

This is an automated email from the ASF dual-hosted git repository.

iffyio pushed a commit to branch teradata-create-table-statement
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 352ca4c6e55795ed429b9d297e248f3f702c8dbe
Author: ifeanyi <if...@validio.io>
AuthorDate: Fri Apr 17 15:41:46 2026 +0200

    [Teradata] Add `CREATE TABLE` options
    
    Adds support for some options in a Teradata `CREATE TABLE`
    statement.
---
 src/ast/ddl.rs                       | 59 ++++++++++++++++++++++++++++--
 src/ast/helpers/stmt_create_table.rs | 33 ++++++++++++++++-
 src/ast/mod.rs                       |  2 +-
 src/ast/spans.rs                     |  3 ++
 src/dialect/mod.rs                   | 10 ++++++
 src/dialect/teradata.rs              |  5 +++
 src/keywords.rs                      |  1 +
 src/parser/mod.rs                    | 70 +++++++++++++++++++++++++++++++++++-
 tests/sqlparser_duckdb.rs            |  3 ++
 tests/sqlparser_mssql.rs             |  6 ++++
 tests/sqlparser_postgres.rs          |  3 ++
 tests/sqlparser_snowflake.rs         |  7 ----
 tests/sqlparser_teradata.rs          | 62 +++++++++++++++++++++++++++++++-
 13 files changed, 251 insertions(+), 13 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 67aefb39..ad22d5c2 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -3060,6 +3060,20 @@ pub struct CreateTable {
     /// Redshift `BACKUP` option: `BACKUP { YES | NO }`
     /// <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_TABLE_NEW.html>
     pub backup: Option<bool>,
+    /// `MULTISET | SET` table-kind prefix.
+    /// `Some(true)` => `MULTISET`, `Some(false)` => `SET`.
+    ///
+    /// [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)
+    pub multiset: Option<bool>,
+    /// `FALLBACK` clause.
+    /// `Some(true)` => `FALLBACK`, `Some(false)` => `NO FALLBACK`
+    ///
+    /// [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)
+    pub fallback: Option<bool>,
+    /// `WITH DATA` clause on a `CREATE TABLE ... AS` statement.
+    ///
+    /// [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)
+    pub with_data: Option<WithData>,
 }
 
 impl fmt::Display for CreateTable {
@@ -3073,7 +3087,7 @@ impl fmt::Display for CreateTable {
         //   `CREATE TABLE t (a INT) AS SELECT a from t2`
         write!(
             f,
-            "CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
+            "CREATE {or_replace}{external}{global}{multiset}{temporary}{transient}{volatile}{dynamic}{iceberg}{snapshot}TABLE {if_not_exists}{name}",
             or_replace = if self.or_replace { "OR REPLACE " } else { "" },
             external = if self.external { "EXTERNAL " } else { "" },
             snapshot = if self.snapshot { "SNAPSHOT " } else { "" },
@@ -3087,14 +3101,20 @@ impl fmt::Display for CreateTable {
                 })
                 .unwrap_or(""),
             if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
+            multiset = self
+                .multiset
+                .map(|m| if m { "MULTISET " } else { "SET " })
+                .unwrap_or(""),
             temporary = if self.temporary { "TEMPORARY " } else { "" },
             transient = if self.transient { "TRANSIENT " } else { "" },
             volatile = if self.volatile { "VOLATILE " } else { "" },
-            // Only for Snowflake
             iceberg = if self.iceberg { "ICEBERG " } else { "" },
             dynamic = if self.dynamic { "DYNAMIC " } else { "" },
             name = self.name,
         )?;
+        if let Some(fallback) = self.fallback {
+            write!(f, ", {}", if fallback { "FALLBACK" } else { "NO FALLBACK" })?;
+        }
         if let Some(partition_of) = &self.partition_of {
             write!(f, " PARTITION OF {partition_of}")?;
         }
@@ -3379,6 +3399,41 @@ impl fmt::Display for CreateTable {
         if let Some(query) = &self.query {
             write!(f, " AS {query}")?;
         }
+        if let Some(with_data) = &self.with_data {
+            write!(f, " {with_data}")?;
+        }
+        Ok(())
+    }
+}
+
+/// `WITH DATA` clause on `CREATE TABLE ... AS` statement.
+///
+/// [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)
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct WithData {
+    /// `true` for `WITH DATA`, `false` for `WITH NO DATA`.
+    pub data: bool,
+    /// `Some(true)` for `AND STATISTICS`, `Some(false)` for `AND NO STATISTICS`,
+    /// `None` if the `AND [NO] STATISTICS` sub-clause is omitted.
+    pub statistics: Option<bool>,
+}
+
+impl fmt::Display for WithData {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str("WITH ")?;
+        if !self.data {
+            f.write_str("NO ")?;
+        }
+        f.write_str("DATA")?;
+        if let Some(stats) = self.statistics {
+            f.write_str(" AND ")?;
+            if !stats {
+                f.write_str("NO ")?;
+            }
+            f.write_str("STATISTICS")?;
+        }
         Ok(())
     }
 }
diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs
index ab2feb69..fc81d3b8 100644
--- a/src/ast/helpers/stmt_create_table.rs
+++ b/src/ast/helpers/stmt_create_table.rs
@@ -29,7 +29,7 @@ use crate::ast::{
     DistStyle, Expr, FileFormat, ForValues, HiveDistributionStyle, HiveFormat, Ident,
     InitializeKind, ObjectName, OnCommit, OneOrManyWithParens, Query, RefreshModeKind,
     RowAccessPolicy, Statement, StorageLifecyclePolicy, StorageSerializationPolicy,
-    TableConstraint, TableVersion, Tag, WrappedCollection,
+    TableConstraint, TableVersion, Tag, WithData, WrappedCollection,
 };
 
 use crate::parser::ParserError;
@@ -183,6 +183,12 @@ pub struct CreateTableBuilder {
     pub sortkey: Option<Vec<Expr>>,
     /// Redshift `BACKUP` option.
     pub backup: Option<bool>,
+    /// `MULTISET | SET` table-kind prefix.
+    pub multiset: Option<bool>,
+    /// `FALLBACK` clause.
+    pub fallback: Option<bool>,
+    /// `WITH DATA` clause.
+    pub with_data: Option<WithData>,
 }
 
 impl CreateTableBuilder {
@@ -248,6 +254,9 @@ impl CreateTableBuilder {
             distkey: None,
             sortkey: None,
             backup: None,
+            multiset: None,
+            fallback: None,
+            with_data: None,
         }
     }
     /// Set `OR REPLACE` for the CREATE TABLE statement.
@@ -556,6 +565,22 @@ impl CreateTableBuilder {
         self.backup = backup;
         self
     }
+    /// Set `MULTISET | SET` table-kind prefix.
+    /// Some(true) => `MULTISET`, Some(false) => `SET`.
+    pub fn multiset(mut self, multiset: Option<bool>) -> Self {
+        self.multiset = multiset;
+        self
+    }
+    /// Set `FALLBACK` / `NO FALLBACK` flag.
+    pub fn fallback(mut self, fallback: Option<bool>) -> Self {
+        self.fallback = fallback;
+        self
+    }
+    /// Set `WITH DATA` clause.
+    pub fn with_data(mut self, with_data: Option<WithData>) -> Self {
+        self.with_data = with_data;
+        self
+    }
     /// Consume the builder and produce a `CreateTable`.
     pub fn build(self) -> CreateTable {
         CreateTable {
@@ -618,6 +643,9 @@ impl CreateTableBuilder {
             distkey: self.distkey,
             sortkey: self.sortkey,
             backup: self.backup,
+            multiset: self.multiset,
+            fallback: self.fallback,
+            with_data: self.with_data,
         }
     }
 }
@@ -699,6 +727,9 @@ impl From<CreateTable> for CreateTableBuilder {
             distkey: table.distkey,
             sortkey: table.sortkey,
             backup: table.backup,
+            multiset: table.multiset,
+            fallback: table.fallback,
+            with_data: table.with_data,
         }
     }
 }
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 886bea26..26de859e 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -81,7 +81,7 @@ pub use self::ddl::{
     PartitionBoundValue, ProcedureParam, ReferentialAction, RenameTableNameKind, ReplicaIdentity,
     TagsColumnOption, TriggerObjectKind, Truncate, UserDefinedTypeCompositeAttributeDef,
     UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
-    UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
+    UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef, WithData,
 };
 pub use self::dml::{
     Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 0dc834ba..18691c03 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -604,6 +604,9 @@ impl Spanned for CreateTable {
             distkey: _,
             sortkey: _,
             backup: _,
+            multiset: _,
+            fallback: _,
+            with_data: _,
         } = self;
 
         union_spans(
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 6ab6cb15..84032847 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1224,6 +1224,16 @@ pub trait Dialect: Debug + Any {
         false
     }
 
+    /// Returns true if the dialect accepts a comma-separated list of table-level
+    /// options placed between the table name and the column-list parenthesis, e.g.
+    ///
+    /// ```sql
+    /// CREATE TABLE foo, NO FALLBACK, NO BEFORE JOURNAL (col INTEGER)
+    /// ```
+    fn supports_leading_comma_before_table_options(&self) -> bool {
+        false
+    }
+
     /// Returns true if the dialect supports PartiQL for querying semi-structured data
     /// <https://partiql.org/index.html>
     fn supports_partiql(&self) -> bool {
diff --git a/src/dialect/teradata.rs b/src/dialect/teradata.rs
index e88a4007..c8470cb6 100644
--- a/src/dialect/teradata.rs
+++ b/src/dialect/teradata.rs
@@ -89,4 +89,9 @@ impl Dialect for TeradataDialect {
     fn supports_string_literal_concatenation(&self) -> bool {
         true
     }
+
+    /// See <https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Definition-Language-Syntax-and-Examples/Table-Statements/CREATE-TABLE-and-CREATE-TABLE-AS>
+    fn supports_leading_comma_before_table_options(&self) -> bool {
+        true
+    }
 }
diff --git a/src/keywords.rs b/src/keywords.rs
index 4fc8f72d..a0a65be6 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -414,6 +414,7 @@ define_keywords!(
     FACTS,
     FAIL,
     FAILOVER,
+    FALLBACK,
     FALSE,
     FAMILY,
     FETCH,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 668c520e..e7667743 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -5124,6 +5124,7 @@ impl<'a> Parser<'a> {
     pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
         let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
         let or_alter = self.parse_keywords(&[Keyword::OR, Keyword::ALTER]);
+        let multiset = self.maybe_parse_multiset();
         let local = self.parse_one_of_keywords(&[Keyword::LOCAL]).is_some();
         let global = self.parse_one_of_keywords(&[Keyword::GLOBAL]).is_some();
         let transient = self.parse_one_of_keywords(&[Keyword::TRANSIENT]).is_some();
@@ -5137,13 +5138,14 @@ impl<'a> Parser<'a> {
         let temporary = self
             .parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
             .is_some();
+        let volatile = self.parse_keyword(Keyword::VOLATILE);
         let persistent = dialect_of!(self is DuckDbDialect)
             && self.parse_one_of_keywords(&[Keyword::PERSISTENT]).is_some();
         let create_view_params = self.parse_create_view_params()?;
         if self.peek_keywords(&[Keyword::SNAPSHOT, Keyword::TABLE]) {
             self.parse_create_snapshot_table().map(Into::into)
         } else if self.parse_keyword(Keyword::TABLE) {
-            self.parse_create_table(or_replace, temporary, global, transient)
+            self.parse_create_table(or_replace, temporary, global, transient, volatile, multiset)
                 .map(Into::into)
         } else if self.peek_keyword(Keyword::MATERIALIZED)
             || self.peek_keyword(Keyword::VIEW)
@@ -8470,11 +8472,25 @@ impl<'a> Parser<'a> {
         temporary: bool,
         global: Option<bool>,
         transient: bool,
+        volatile: bool,
+        multiset: Option<bool>,
     ) -> Result<CreateTable, ParserError> {
         let allow_unquoted_hyphen = dialect_of!(self is BigQueryDialect);
         let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
         let table_name = self.parse_object_name(allow_unquoted_hyphen)?;
 
+        let fallback = if self.dialect.supports_leading_comma_before_table_options()
+            && self.consume_token(&Token::Comma)
+        {
+            let fallback = self.maybe_parse_fallback()?;
+            if fallback.is_none() {
+                self.prev_token(); // Put back comma.
+            }
+            fallback
+        } else {
+            None
+        };
+
         // PostgreSQL PARTITION OF for child partition tables
         // Note: This is a PostgreSQL-specific feature, but the dialect check was intentionally
         // removed to allow GenericDialect and other dialects to parse this syntax. This enables
@@ -8626,6 +8642,13 @@ impl<'a> Parser<'a> {
             None
         };
 
+        // `WITH DATA` clause only applies if there is a query body.
+        let with_data = if query.is_some() {
+            self.maybe_parse_with_data()?
+        } else {
+            None
+        };
+
         Ok(CreateTableBuilder::new(table_name)
             .temporary(temporary)
             .columns(columns)
@@ -8633,6 +8656,9 @@ impl<'a> Parser<'a> {
             .or_replace(or_replace)
             .if_not_exists(if_not_exists)
             .transient(transient)
+            .volatile(volatile)
+            .multiset(multiset)
+            .fallback(fallback)
             .hive_distribution(hive_distribution)
             .hive_formats(hive_formats)
             .global(global)
@@ -8652,6 +8678,7 @@ impl<'a> Parser<'a> {
             .for_values(for_values)
             .table_options(create_table_config.table_options)
             .primary_key(primary_key)
+            .with_data(with_data)
             .strict(strict)
             .backup(backup)
             .diststyle(diststyle)
@@ -8660,6 +8687,47 @@ impl<'a> Parser<'a> {
             .build())
     }
 
+    /// Parse `MULTISET` table-kind prefix on `CREATE TABLE`.
+    fn maybe_parse_multiset(&mut self) -> Option<bool> {
+        match self.parse_one_of_keywords(&[Keyword::SET, Keyword::MULTISET]) {
+            Some(Keyword::MULTISET) => Some(true),
+            Some(Keyword::SET) => Some(false),
+            _ => None,
+        }
+    }
+
+    /// Parse `FALLBACK` option on a `CREATE TABLE` statement,
+    fn maybe_parse_fallback(&mut self) -> Result<Option<bool>, ParserError> {
+        if self.parse_keywords(&[Keyword::NO, Keyword::FALLBACK]) {
+            Ok(Some(false))
+        } else if self.parse_keyword(Keyword::FALLBACK) {
+            Ok(Some(true))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Parse [`WithData`] clause on `CREATE TABLE ... AS` statement.
+    fn maybe_parse_with_data(&mut self) -> Result<Option<WithData>, ParserError> {
+        let data = if self.parse_keywords(&[Keyword::WITH, Keyword::DATA]) {
+            true
+        } else if self.parse_keywords(&[Keyword::WITH, Keyword::NO, Keyword::DATA]) {
+            false
+        } else {
+            return Ok(None);
+        };
+
+        let statistics = if self.parse_keywords(&[Keyword::AND, Keyword::STATISTICS]) {
+            Some(true)
+        } else if self.parse_keywords(&[Keyword::AND, Keyword::NO, Keyword::STATISTICS]) {
+            Some(false)
+        } else {
+            None
+        };
+
+        Ok(Some(WithData { data, statistics }))
+    }
+
     fn maybe_parse_create_table_like(
         &mut self,
         allow_unquoted_hyphen: bool,
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs
index df626858..548ad27c 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -794,6 +794,9 @@ fn test_duckdb_union_datatype() {
             distkey: Default::default(),
             sortkey: Default::default(),
             backup: Default::default(),
+            multiset: Default::default(),
+            fallback: Default::default(),
+            with_data: Default::default(),
         }),
         stmt
     );
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 1e053da7..d784c74a 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -2013,6 +2013,9 @@ fn parse_create_table_with_valid_options() {
                 distkey: None,
                 sortkey: None,
                 backup: None,
+                multiset: None,
+                fallback: None,
+                with_data: None,
             })
         );
     }
@@ -2187,6 +2190,9 @@ fn parse_create_table_with_identity_column() {
                 distkey: None,
                 sortkey: None,
                 backup: None,
+                multiset: None,
+                fallback: None,
+                with_data: None,
             }),
         );
     }
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 86315b1e..87d17d1b 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -6716,6 +6716,9 @@ fn parse_trigger_related_functions() {
             distkey: None,
             sortkey: None,
             backup: None,
+            multiset: None,
+            fallback: None,
+            with_data: None,
         }
     );
 
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index 790bf151..52b4a841 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -471,13 +471,6 @@ fn test_snowflake_create_invalid_temporal_table() {
         ))
     );
 
-    assert_eq!(
-        snowflake().parse_sql_statements("CREATE TEMP VOLATILE TABLE my_table (a INT)"),
-        Err(ParserError::ParserError(
-            "Expected: an object type after CREATE, found: VOLATILE".to_string()
-        ))
-    );
-
     assert_eq!(
         snowflake().parse_sql_statements("CREATE TEMP TRANSIENT TABLE my_table (a INT)"),
         Err(ParserError::ParserError(
diff --git a/tests/sqlparser_teradata.rs b/tests/sqlparser_teradata.rs
index b8e6a53c..a4e66af0 100644
--- a/tests/sqlparser_teradata.rs
+++ b/tests/sqlparser_teradata.rs
@@ -17,7 +17,8 @@
 
 //! Test SQL syntax, specific to [sqlparser::dialect::TeradataDialect].
 
-use sqlparser::dialect::{Dialect, TeradataDialect};
+use sqlparser::dialect::{Dialect, GenericDialect, TeradataDialect};
+use sqlparser::test_utils::all_dialects_where;
 use test_utils::TestedDialects;
 
 mod test_utils;
@@ -26,6 +27,10 @@ fn teradata() -> TestedDialects {
     TestedDialects::new(vec![Box::new(TeradataDialect)])
 }
 
+fn teradata_and_generic() -> TestedDialects {
+    TestedDialects::new(vec![Box::new(TeradataDialect), Box::new(GenericDialect {})])
+}
+
 #[test]
 fn dialect_methods() {
     let d: &dyn Dialect = &TeradataDialect;
@@ -45,6 +50,7 @@ fn dialect_methods() {
     assert!(d.supports_top_before_distinct());
     assert!(d.supports_window_function_null_treatment_arg());
     assert!(d.supports_string_literal_concatenation());
+    assert!(d.supports_leading_comma_before_table_options());
 }
 
 #[test]
@@ -61,3 +67,57 @@ fn parse_identifier() {
         r#"NULL AS "quoted id""#
     ));
 }
+
+#[test]
+fn parse_create_table_multiset() {
+    teradata_and_generic().verified_stmt("CREATE MULTISET TABLE foo (id INT)");
+    teradata_and_generic().verified_stmt("CREATE SET TABLE foo (id INT)");
+}
+
+#[test]
+fn parse_create_table_volatile() {
+    teradata_and_generic().verified_stmt("CREATE VOLATILE TABLE foo (id INT)");
+    teradata_and_generic().verified_stmt("CREATE MULTISET VOLATILE TABLE foo (id INT)");
+}
+
+#[test]
+fn parse_create_table_fallback() {
+    teradata().verified_stmt("CREATE TABLE foo, FALLBACK (id INT)");
+    teradata().verified_stmt("CREATE TABLE foo, NO FALLBACK (id INT)");
+    teradata().verified_stmt("CREATE MULTISET TABLE foo, NO FALLBACK (id INT)");
+}
+
+#[test]
+fn parse_create_table_as_with_data() {
+    teradata_and_generic().verified_stmt("CREATE TABLE foo AS (SELECT 1 AS a) WITH DATA");
+    teradata_and_generic().verified_stmt("CREATE TABLE foo AS (SELECT 1 AS a) WITH NO DATA");
+    teradata_and_generic()
+        .verified_stmt("CREATE TABLE foo AS (SELECT 1 AS a) WITH DATA AND STATISTICS");
+    teradata_and_generic()
+        .verified_stmt("CREATE TABLE foo AS (SELECT 1 AS a) WITH DATA AND NO STATISTICS");
+    teradata_and_generic()
+        .verified_stmt("CREATE TABLE foo AS (SELECT 1 AS a) WITH NO DATA AND STATISTICS");
+    teradata_and_generic()
+        .verified_stmt("CREATE TABLE foo AS (SELECT 1 AS a) WITH NO DATA AND NO STATISTICS");
+}
+
+#[test]
+fn parse_create_table_options() {
+    teradata().verified_stmt(concat!(
+        "CREATE MULTISET VOLATILE TABLE foo, NO FALLBACK ",
+        "(id INT, name VARCHAR(100)) ",
+        "ON COMMIT PRESERVE ROWS"
+    ));
+}
+
+#[test]
+fn parse_leading_comma_before_table_options() {
+    let dialect = all_dialects_where(|d| d.supports_leading_comma_before_table_options());
+    dialect.verified_stmt("CREATE TABLE foo, FALLBACK (id INT)");
+
+    let unsupported_dialects =
+        all_dialects_where(|d| !d.supports_leading_comma_before_table_options());
+    assert!(unsupported_dialects
+        .parse_sql_statements("CREATE TABLE foo, FALLBACK (id INT)")
+        .is_err());
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org