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

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new 6517da6b Support parsing optional nulls handling for unique constraint 
(#1567)
6517da6b is described below

commit 6517da6b7db4176fa340add848ba518392f1f934
Author: Michael Victor Zink <[email protected]>
AuthorDate: Tue Dec 3 17:09:00 2024 -0800

    Support parsing optional nulls handling for unique constraint (#1567)
---
 src/ast/ddl.rs              | 30 +++++++++++++++++++++++++++++-
 src/ast/mod.rs              |  7 ++++---
 src/ast/spans.rs            |  1 +
 src/parser/mod.rs           | 17 +++++++++++++++++
 tests/sqlparser_mysql.rs    |  1 +
 tests/sqlparser_postgres.rs | 19 +++++++++++++++++++
 6 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 9a7d297b..6c930a42 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -669,6 +669,8 @@ pub enum TableConstraint {
         columns: Vec<Ident>,
         index_options: Vec<IndexOption>,
         characteristics: Option<ConstraintCharacteristics>,
+        /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
+        nulls_distinct: NullsDistinctOption,
     },
     /// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\
     /// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] 
(<columns>) <index_options>`
@@ -777,10 +779,11 @@ impl fmt::Display for TableConstraint {
                 columns,
                 index_options,
                 characteristics,
+                nulls_distinct,
             } => {
                 write!(
                     f,
-                    "{}UNIQUE{index_type_display:>}{}{} ({})",
+                    "{}UNIQUE{nulls_distinct}{index_type_display:>}{}{} ({})",
                     display_constraint_name(name),
                     display_option_spaced(index_name),
                     display_option(" USING ", "", index_type),
@@ -988,6 +991,31 @@ impl fmt::Display for IndexOption {
     }
 }
 
+/// [Postgres] unique index nulls handling option: `[ NULLS [ NOT ] DISTINCT ]`
+///
+/// [Postgres]: https://www.postgresql.org/docs/17/sql-altertable.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 NullsDistinctOption {
+    /// Not specified
+    None,
+    /// NULLS DISTINCT
+    Distinct,
+    /// NULLS NOT DISTINCT
+    NotDistinct,
+}
+
+impl fmt::Display for NullsDistinctOption {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::None => Ok(()),
+            Self::Distinct => write!(f, " NULLS DISTINCT"),
+            Self::NotDistinct => write!(f, " NULLS NOT DISTINCT"),
+        }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index ef4ccff4..d4278e4f 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -49,9 +49,10 @@ pub use self::ddl::{
     ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, 
ColumnPolicyProperty,
     ConstraintCharacteristics, CreateFunction, Deduplicate, DeferrableInitial, 
GeneratedAs,
     GeneratedExpressionMode, IdentityParameters, IdentityProperty, 
IdentityPropertyFormatKind,
-    IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, 
KeyOrIndexDisplay, Owner,
-    Partition, ProcedureParam, ReferentialAction, TableConstraint, 
TagsColumnOption,
-    UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, 
ViewColumnDef,
+    IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, 
KeyOrIndexDisplay,
+    NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, 
TableConstraint,
+    TagsColumnOption, UserDefinedTypeCompositeAttributeDef, 
UserDefinedTypeRepresentation,
+    ViewColumnDef,
 };
 pub use self::dml::{CreateIndex, CreateTable, Delete, Insert};
 pub use self::operator::{BinaryOperator, UnaryOperator};
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 1e0f1bf0..a5439417 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -587,6 +587,7 @@ impl Spanned for TableConstraint {
                 columns,
                 index_options: _,
                 characteristics,
+                nulls_distinct: _,
             } => union_spans(
                 name.iter()
                     .map(|i| i.span)
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index efdf0d6d..32e7e374 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -6729,6 +6729,8 @@ impl<'a> Parser<'a> {
                         .expected("`index_name` or `(column_name [, ...])`", 
self.peek_token());
                 }
 
+                let nulls_distinct = self.parse_optional_nulls_distinct()?;
+
                 // optional index name
                 let index_name = self.parse_optional_indent()?;
                 let index_type = self.parse_optional_using_then_index_type()?;
@@ -6744,6 +6746,7 @@ impl<'a> Parser<'a> {
                     columns,
                     index_options,
                     characteristics,
+                    nulls_distinct,
                 }))
             }
             Token::Word(w) if w.keyword == Keyword::PRIMARY => {
@@ -6866,6 +6869,20 @@ impl<'a> Parser<'a> {
         }
     }
 
+    fn parse_optional_nulls_distinct(&mut self) -> Result<NullsDistinctOption, 
ParserError> {
+        Ok(if self.parse_keyword(Keyword::NULLS) {
+            let not = self.parse_keyword(Keyword::NOT);
+            self.expect_keyword(Keyword::DISTINCT)?;
+            if not {
+                NullsDistinctOption::NotDistinct
+            } else {
+                NullsDistinctOption::Distinct
+            }
+        } else {
+            NullsDistinctOption::None
+        })
+    }
+
     pub fn maybe_parse_options(
         &mut self,
         keyword: Keyword,
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 2b132331..f20a759a 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -669,6 +669,7 @@ fn table_constraint_unique_primary_ctor(
             columns,
             index_options,
             characteristics,
+            nulls_distinct: NullsDistinctOption::None,
         },
         None => TableConstraint::PrimaryKey {
             name,
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 52fe6c40..92368e9e 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -594,6 +594,25 @@ fn parse_alter_table_constraints_rename() {
     }
 }
 
+#[test]
+fn parse_alter_table_constraints_unique_nulls_distinct() {
+    match pg_and_generic()
+        .verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE NULLS NOT 
DISTINCT (c)")
+    {
+        Statement::AlterTable { operations, .. } => match &operations[0] {
+            AlterTableOperation::AddConstraint(TableConstraint::Unique {
+                nulls_distinct, ..
+            }) => {
+                assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct)
+            }
+            _ => unreachable!(),
+        },
+        _ => unreachable!(),
+    }
+    pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE 
NULLS DISTINCT (c)");
+    pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE 
(c)");
+}
+
 #[test]
 fn parse_alter_table_disable() {
     pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL 
SECURITY");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to