Skip to content

Commit 6517da6

Browse files
authored
Support parsing optional nulls handling for unique constraint (#1567)
1 parent 6d4188d commit 6517da6

File tree

6 files changed

+71
-4
lines changed

6 files changed

+71
-4
lines changed

src/ast/ddl.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,8 @@ pub enum TableConstraint {
669669
columns: Vec<Ident>,
670670
index_options: Vec<IndexOption>,
671671
characteristics: Option<ConstraintCharacteristics>,
672+
/// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]`
673+
nulls_distinct: NullsDistinctOption,
672674
},
673675
/// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\
674676
/// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
@@ -777,10 +779,11 @@ impl fmt::Display for TableConstraint {
777779
columns,
778780
index_options,
779781
characteristics,
782+
nulls_distinct,
780783
} => {
781784
write!(
782785
f,
783-
"{}UNIQUE{index_type_display:>}{}{} ({})",
786+
"{}UNIQUE{nulls_distinct}{index_type_display:>}{}{} ({})",
784787
display_constraint_name(name),
785788
display_option_spaced(index_name),
786789
display_option(" USING ", "", index_type),
@@ -988,6 +991,31 @@ impl fmt::Display for IndexOption {
988991
}
989992
}
990993

994+
/// [Postgres] unique index nulls handling option: `[ NULLS [ NOT ] DISTINCT ]`
995+
///
996+
/// [Postgres]: https://www.postgresql.org/docs/17/sql-altertable.html
997+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
998+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
999+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1000+
pub enum NullsDistinctOption {
1001+
/// Not specified
1002+
None,
1003+
/// NULLS DISTINCT
1004+
Distinct,
1005+
/// NULLS NOT DISTINCT
1006+
NotDistinct,
1007+
}
1008+
1009+
impl fmt::Display for NullsDistinctOption {
1010+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1011+
match self {
1012+
Self::None => Ok(()),
1013+
Self::Distinct => write!(f, " NULLS DISTINCT"),
1014+
Self::NotDistinct => write!(f, " NULLS NOT DISTINCT"),
1015+
}
1016+
}
1017+
}
1018+
9911019
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
9921020
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9931021
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/ast/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ pub use self::ddl::{
4949
ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty,
5050
ConstraintCharacteristics, CreateFunction, Deduplicate, DeferrableInitial, GeneratedAs,
5151
GeneratedExpressionMode, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind,
52-
IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, Owner,
53-
Partition, ProcedureParam, ReferentialAction, TableConstraint, TagsColumnOption,
54-
UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef,
52+
IdentityPropertyKind, IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay,
53+
NullsDistinctOption, Owner, Partition, ProcedureParam, ReferentialAction, TableConstraint,
54+
TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation,
55+
ViewColumnDef,
5556
};
5657
pub use self::dml::{CreateIndex, CreateTable, Delete, Insert};
5758
pub use self::operator::{BinaryOperator, UnaryOperator};

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ impl Spanned for TableConstraint {
587587
columns,
588588
index_options: _,
589589
characteristics,
590+
nulls_distinct: _,
590591
} => union_spans(
591592
name.iter()
592593
.map(|i| i.span)

src/parser/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6729,6 +6729,8 @@ impl<'a> Parser<'a> {
67296729
.expected("`index_name` or `(column_name [, ...])`", self.peek_token());
67306730
}
67316731

6732+
let nulls_distinct = self.parse_optional_nulls_distinct()?;
6733+
67326734
// optional index name
67336735
let index_name = self.parse_optional_indent()?;
67346736
let index_type = self.parse_optional_using_then_index_type()?;
@@ -6744,6 +6746,7 @@ impl<'a> Parser<'a> {
67446746
columns,
67456747
index_options,
67466748
characteristics,
6749+
nulls_distinct,
67476750
}))
67486751
}
67496752
Token::Word(w) if w.keyword == Keyword::PRIMARY => {
@@ -6866,6 +6869,20 @@ impl<'a> Parser<'a> {
68666869
}
68676870
}
68686871

6872+
fn parse_optional_nulls_distinct(&mut self) -> Result<NullsDistinctOption, ParserError> {
6873+
Ok(if self.parse_keyword(Keyword::NULLS) {
6874+
let not = self.parse_keyword(Keyword::NOT);
6875+
self.expect_keyword(Keyword::DISTINCT)?;
6876+
if not {
6877+
NullsDistinctOption::NotDistinct
6878+
} else {
6879+
NullsDistinctOption::Distinct
6880+
}
6881+
} else {
6882+
NullsDistinctOption::None
6883+
})
6884+
}
6885+
68696886
pub fn maybe_parse_options(
68706887
&mut self,
68716888
keyword: Keyword,

tests/sqlparser_mysql.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ fn table_constraint_unique_primary_ctor(
669669
columns,
670670
index_options,
671671
characteristics,
672+
nulls_distinct: NullsDistinctOption::None,
672673
},
673674
None => TableConstraint::PrimaryKey {
674675
name,

tests/sqlparser_postgres.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,25 @@ fn parse_alter_table_constraints_rename() {
594594
}
595595
}
596596

597+
#[test]
598+
fn parse_alter_table_constraints_unique_nulls_distinct() {
599+
match pg_and_generic()
600+
.verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE NULLS NOT DISTINCT (c)")
601+
{
602+
Statement::AlterTable { operations, .. } => match &operations[0] {
603+
AlterTableOperation::AddConstraint(TableConstraint::Unique {
604+
nulls_distinct, ..
605+
}) => {
606+
assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct)
607+
}
608+
_ => unreachable!(),
609+
},
610+
_ => unreachable!(),
611+
}
612+
pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE NULLS DISTINCT (c)");
613+
pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE (c)");
614+
}
615+
597616
#[test]
598617
fn parse_alter_table_disable() {
599618
pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL SECURITY");

0 commit comments

Comments
 (0)