Skip to content

Commit c86508b

Browse files
authored
Add support for constraint characteristics clause (apache#1099)
1 parent 1fb9f3e commit c86508b

File tree

7 files changed

+480
-22
lines changed

7 files changed

+480
-22
lines changed

src/ast/ddl.rs

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ pub enum TableConstraint {
385385
columns: Vec<Ident>,
386386
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
387387
is_primary: bool,
388+
characteristics: Option<ConstraintCharacteristics>,
388389
},
389390
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
390391
/// REFERENCES <foreign_table> (<referred_columns>)
@@ -398,6 +399,7 @@ pub enum TableConstraint {
398399
referred_columns: Vec<Ident>,
399400
on_delete: Option<ReferentialAction>,
400401
on_update: Option<ReferentialAction>,
402+
characteristics: Option<ConstraintCharacteristics>,
401403
},
402404
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
403405
Check {
@@ -454,20 +456,30 @@ impl fmt::Display for TableConstraint {
454456
name,
455457
columns,
456458
is_primary,
457-
} => write!(
458-
f,
459-
"{}{} ({})",
460-
display_constraint_name(name),
461-
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
462-
display_comma_separated(columns)
463-
),
459+
characteristics,
460+
} => {
461+
write!(
462+
f,
463+
"{}{} ({})",
464+
display_constraint_name(name),
465+
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
466+
display_comma_separated(columns)
467+
)?;
468+
469+
if let Some(characteristics) = characteristics {
470+
write!(f, " {}", characteristics)?;
471+
}
472+
473+
Ok(())
474+
}
464475
TableConstraint::ForeignKey {
465476
name,
466477
columns,
467478
foreign_table,
468479
referred_columns,
469480
on_delete,
470481
on_update,
482+
characteristics,
471483
} => {
472484
write!(
473485
f,
@@ -483,6 +495,9 @@ impl fmt::Display for TableConstraint {
483495
if let Some(action) = on_update {
484496
write!(f, " ON UPDATE {action}")?;
485497
}
498+
if let Some(characteristics) = characteristics {
499+
write!(f, " {}", characteristics)?;
500+
}
486501
Ok(())
487502
}
488503
TableConstraint::Check { name, expr } => {
@@ -713,20 +728,24 @@ pub enum ColumnOption {
713728
NotNull,
714729
/// `DEFAULT <restricted-expr>`
715730
Default(Expr),
716-
/// `{ PRIMARY KEY | UNIQUE }`
731+
/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
717732
Unique {
718733
is_primary: bool,
734+
characteristics: Option<ConstraintCharacteristics>,
719735
},
720736
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
721737
/// <foreign_table> (<referred_columns>)
722738
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
723739
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
724-
/// }`).
740+
/// }
741+
/// [<constraint_characteristics>]
742+
/// `).
725743
ForeignKey {
726744
foreign_table: ObjectName,
727745
referred_columns: Vec<Ident>,
728746
on_delete: Option<ReferentialAction>,
729747
on_update: Option<ReferentialAction>,
748+
characteristics: Option<ConstraintCharacteristics>,
730749
},
731750
/// `CHECK (<expr>)`
732751
Check(Expr),
@@ -764,14 +783,22 @@ impl fmt::Display for ColumnOption {
764783
Null => write!(f, "NULL"),
765784
NotNull => write!(f, "NOT NULL"),
766785
Default(expr) => write!(f, "DEFAULT {expr}"),
767-
Unique { is_primary } => {
768-
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })
786+
Unique {
787+
is_primary,
788+
characteristics,
789+
} => {
790+
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
791+
if let Some(characteristics) = characteristics {
792+
write!(f, " {}", characteristics)?;
793+
}
794+
Ok(())
769795
}
770796
ForeignKey {
771797
foreign_table,
772798
referred_columns,
773799
on_delete,
774800
on_update,
801+
characteristics,
775802
} => {
776803
write!(f, "REFERENCES {foreign_table}")?;
777804
if !referred_columns.is_empty() {
@@ -783,6 +810,9 @@ impl fmt::Display for ColumnOption {
783810
if let Some(action) = on_update {
784811
write!(f, " ON UPDATE {action}")?;
785812
}
813+
if let Some(characteristics) = characteristics {
814+
write!(f, " {}", characteristics)?;
815+
}
786816
Ok(())
787817
}
788818
Check(expr) => write!(f, "CHECK ({expr})"),
@@ -874,6 +904,84 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
874904
ConstraintName(name)
875905
}
876906

907+
/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
908+
///
909+
/// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order.
910+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
911+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
912+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
913+
pub struct ConstraintCharacteristics {
914+
/// `[ DEFERRABLE | NOT DEFERRABLE ]`
915+
pub deferrable: Option<bool>,
916+
/// `[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
917+
pub initially: Option<DeferrableInitial>,
918+
/// `[ ENFORCED | NOT ENFORCED ]`
919+
pub enforced: Option<bool>,
920+
}
921+
922+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
923+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
924+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
925+
pub enum DeferrableInitial {
926+
/// `INITIALLY IMMEDIATE`
927+
Immediate,
928+
/// `INITIALLY DEFERRED`
929+
Deferred,
930+
}
931+
932+
impl ConstraintCharacteristics {
933+
fn deferrable_text(&self) -> Option<&'static str> {
934+
self.deferrable.map(|deferrable| {
935+
if deferrable {
936+
"DEFERRABLE"
937+
} else {
938+
"NOT DEFERRABLE"
939+
}
940+
})
941+
}
942+
943+
fn initially_immediate_text(&self) -> Option<&'static str> {
944+
self.initially
945+
.map(|initially_immediate| match initially_immediate {
946+
DeferrableInitial::Immediate => "INITIALLY IMMEDIATE",
947+
DeferrableInitial::Deferred => "INITIALLY DEFERRED",
948+
})
949+
}
950+
951+
fn enforced_text(&self) -> Option<&'static str> {
952+
self.enforced.map(
953+
|enforced| {
954+
if enforced {
955+
"ENFORCED"
956+
} else {
957+
"NOT ENFORCED"
958+
}
959+
},
960+
)
961+
}
962+
}
963+
964+
impl fmt::Display for ConstraintCharacteristics {
965+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
966+
let deferrable = self.deferrable_text();
967+
let initially_immediate = self.initially_immediate_text();
968+
let enforced = self.enforced_text();
969+
970+
match (deferrable, initially_immediate, enforced) {
971+
(None, None, None) => Ok(()),
972+
(None, None, Some(enforced)) => write!(f, "{enforced}"),
973+
(None, Some(initial), None) => write!(f, "{initial}"),
974+
(None, Some(initial), Some(enforced)) => write!(f, "{initial} {enforced}"),
975+
(Some(deferrable), None, None) => write!(f, "{deferrable}"),
976+
(Some(deferrable), None, Some(enforced)) => write!(f, "{deferrable} {enforced}"),
977+
(Some(deferrable), Some(initial), None) => write!(f, "{deferrable} {initial}"),
978+
(Some(deferrable), Some(initial), Some(enforced)) => {
979+
write!(f, "{deferrable} {initial} {enforced}")
980+
}
981+
}
982+
}
983+
}
984+
877985
/// `<referential_action> =
878986
/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
879987
///

src/ast/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ pub use self::data_type::{
3232
pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue};
3333
pub use self::ddl::{
3434
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
35-
ColumnOptionDef, GeneratedAs, GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition,
36-
ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
35+
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
36+
GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
37+
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
3738
UserDefinedTypeRepresentation, ViewColumnDef,
3839
};
3940
pub use self::operator::{BinaryOperator, UnaryOperator};

src/keywords.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ define_keywords!(
210210
DECIMAL,
211211
DECLARE,
212212
DEFAULT,
213+
DEFERRABLE,
213214
DEFERRED,
214215
DELAYED,
215216
DELETE,
@@ -250,6 +251,7 @@ define_keywords!(
250251
ENDPOINT,
251252
END_FRAME,
252253
END_PARTITION,
254+
ENFORCED,
253255
ENGINE,
254256
ENUM,
255257
EPOCH,
@@ -343,6 +345,7 @@ define_keywords!(
343345
INDEX,
344346
INDICATOR,
345347
INHERIT,
348+
INITIALLY,
346349
INNER,
347350
INOUT,
348351
INPUTFORMAT,

src/parser/mod.rs

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4478,9 +4478,17 @@ impl<'a> Parser<'a> {
44784478
} else if self.parse_keyword(Keyword::DEFAULT) {
44794479
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
44804480
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
4481-
Ok(Some(ColumnOption::Unique { is_primary: true }))
4481+
let characteristics = self.parse_constraint_characteristics()?;
4482+
Ok(Some(ColumnOption::Unique {
4483+
is_primary: true,
4484+
characteristics,
4485+
}))
44824486
} else if self.parse_keyword(Keyword::UNIQUE) {
4483-
Ok(Some(ColumnOption::Unique { is_primary: false }))
4487+
let characteristics = self.parse_constraint_characteristics()?;
4488+
Ok(Some(ColumnOption::Unique {
4489+
is_primary: false,
4490+
characteristics,
4491+
}))
44844492
} else if self.parse_keyword(Keyword::REFERENCES) {
44854493
let foreign_table = self.parse_object_name(false)?;
44864494
// PostgreSQL allows omitting the column list and
@@ -4499,11 +4507,14 @@ impl<'a> Parser<'a> {
44994507
break;
45004508
}
45014509
}
4510+
let characteristics = self.parse_constraint_characteristics()?;
4511+
45024512
Ok(Some(ColumnOption::ForeignKey {
45034513
foreign_table,
45044514
referred_columns,
45054515
on_delete,
45064516
on_update,
4517+
characteristics,
45074518
}))
45084519
} else if self.parse_keyword(Keyword::CHECK) {
45094520
self.expect_token(&Token::LParen)?;
@@ -4658,6 +4669,47 @@ impl<'a> Parser<'a> {
46584669
}
46594670
}
46604671

4672+
pub fn parse_constraint_characteristics(
4673+
&mut self,
4674+
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
4675+
let mut cc = ConstraintCharacteristics {
4676+
deferrable: None,
4677+
initially: None,
4678+
enforced: None,
4679+
};
4680+
4681+
loop {
4682+
if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE])
4683+
{
4684+
cc.deferrable = Some(false);
4685+
} else if cc.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) {
4686+
cc.deferrable = Some(true);
4687+
} else if cc.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) {
4688+
if self.parse_keyword(Keyword::DEFERRED) {
4689+
cc.initially = Some(DeferrableInitial::Deferred);
4690+
} else if self.parse_keyword(Keyword::IMMEDIATE) {
4691+
cc.initially = Some(DeferrableInitial::Immediate);
4692+
} else {
4693+
self.expected("one of DEFERRED or IMMEDIATE", self.peek_token())?;
4694+
}
4695+
} else if cc.enforced.is_none() && self.parse_keyword(Keyword::ENFORCED) {
4696+
cc.enforced = Some(true);
4697+
} else if cc.enforced.is_none()
4698+
&& self.parse_keywords(&[Keyword::NOT, Keyword::ENFORCED])
4699+
{
4700+
cc.enforced = Some(false);
4701+
} else {
4702+
break;
4703+
}
4704+
}
4705+
4706+
if cc.deferrable.is_some() || cc.initially.is_some() || cc.enforced.is_some() {
4707+
Ok(Some(cc))
4708+
} else {
4709+
Ok(None)
4710+
}
4711+
}
4712+
46614713
pub fn parse_optional_table_constraint(
46624714
&mut self,
46634715
) -> Result<Option<TableConstraint>, ParserError> {
@@ -4681,10 +4733,12 @@ impl<'a> Parser<'a> {
46814733
.or(name);
46824734

46834735
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
4736+
let characteristics = self.parse_constraint_characteristics()?;
46844737
Ok(Some(TableConstraint::Unique {
46854738
name,
46864739
columns,
46874740
is_primary,
4741+
characteristics,
46884742
}))
46894743
}
46904744
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
@@ -4706,13 +4760,17 @@ impl<'a> Parser<'a> {
47064760
break;
47074761
}
47084762
}
4763+
4764+
let characteristics = self.parse_constraint_characteristics()?;
4765+
47094766
Ok(Some(TableConstraint::ForeignKey {
47104767
name,
47114768
columns,
47124769
foreign_table,
47134770
referred_columns,
47144771
on_delete,
47154772
on_update,
4773+
characteristics,
47164774
}))
47174775
}
47184776
Token::Word(w) if w.keyword == Keyword::CHECK => {

0 commit comments

Comments
 (0)