Skip to content

Commit 45134e7

Browse files
committed
support constraint characteristics clause
1 parent 3ec337e commit 45134e7

File tree

7 files changed

+347
-30
lines changed

7 files changed

+347
-30
lines changed

src/ast/ddl.rs

+119-11
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ pub enum TableConstraint {
384384
columns: Vec<Ident>,
385385
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
386386
is_primary: bool,
387+
characteristics: Option<ConstraintCharacteristics>,
387388
},
388389
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
389390
/// REFERENCES <foreign_table> (<referred_columns>)
@@ -397,6 +398,7 @@ pub enum TableConstraint {
397398
referred_columns: Vec<Ident>,
398399
on_delete: Option<ReferentialAction>,
399400
on_update: Option<ReferentialAction>,
401+
characteristics: Option<ConstraintCharacteristics>,
400402
},
401403
/// `[ CONSTRAINT <name> ] CHECK (<expr>)`
402404
Check {
@@ -453,20 +455,30 @@ impl fmt::Display for TableConstraint {
453455
name,
454456
columns,
455457
is_primary,
456-
} => write!(
457-
f,
458-
"{}{} ({})",
459-
display_constraint_name(name),
460-
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
461-
display_comma_separated(columns)
462-
),
458+
characteristics,
459+
} => {
460+
write!(
461+
f,
462+
"{}{} ({})",
463+
display_constraint_name(name),
464+
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
465+
display_comma_separated(columns)
466+
)?;
467+
468+
if let Some(characteristics) = characteristics {
469+
write!(f, " {}", characteristics)?;
470+
}
471+
472+
Ok(())
473+
}
463474
TableConstraint::ForeignKey {
464475
name,
465476
columns,
466477
foreign_table,
467478
referred_columns,
468479
on_delete,
469480
on_update,
481+
characteristics,
470482
} => {
471483
write!(
472484
f,
@@ -482,6 +494,9 @@ impl fmt::Display for TableConstraint {
482494
if let Some(action) = on_update {
483495
write!(f, " ON UPDATE {action}")?;
484496
}
497+
if let Some(characteristics) = characteristics {
498+
write!(f, " {}", characteristics)?;
499+
}
485500
Ok(())
486501
}
487502
TableConstraint::Check { name, expr } => {
@@ -676,20 +691,24 @@ pub enum ColumnOption {
676691
NotNull,
677692
/// `DEFAULT <restricted-expr>`
678693
Default(Expr),
679-
/// `{ PRIMARY KEY | UNIQUE }`
694+
/// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
680695
Unique {
681696
is_primary: bool,
697+
characteristics: Option<ConstraintCharacteristics>,
682698
},
683699
/// A referential integrity constraint (`[FOREIGN KEY REFERENCES
684700
/// <foreign_table> (<referred_columns>)
685701
/// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
686702
/// [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
687-
/// }`).
703+
/// }
704+
/// [<constraint_characteristics>]
705+
/// `).
688706
ForeignKey {
689707
foreign_table: ObjectName,
690708
referred_columns: Vec<Ident>,
691709
on_delete: Option<ReferentialAction>,
692710
on_update: Option<ReferentialAction>,
711+
characteristics: Option<ConstraintCharacteristics>,
693712
},
694713
/// `CHECK (<expr>)`
695714
Check(Expr),
@@ -719,14 +738,22 @@ impl fmt::Display for ColumnOption {
719738
Null => write!(f, "NULL"),
720739
NotNull => write!(f, "NOT NULL"),
721740
Default(expr) => write!(f, "DEFAULT {expr}"),
722-
Unique { is_primary } => {
723-
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })
741+
Unique {
742+
is_primary,
743+
characteristics,
744+
} => {
745+
write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
746+
if let Some(characteristics) = characteristics {
747+
write!(f, " {}", characteristics)?;
748+
}
749+
Ok(())
724750
}
725751
ForeignKey {
726752
foreign_table,
727753
referred_columns,
728754
on_delete,
729755
on_update,
756+
characteristics,
730757
} => {
731758
write!(f, "REFERENCES {foreign_table}")?;
732759
if !referred_columns.is_empty() {
@@ -738,6 +765,9 @@ impl fmt::Display for ColumnOption {
738765
if let Some(action) = on_update {
739766
write!(f, " ON UPDATE {action}")?;
740767
}
768+
if let Some(characteristics) = characteristics {
769+
write!(f, " {}", characteristics)?;
770+
}
741771
Ok(())
742772
}
743773
Check(expr) => write!(f, "CHECK ({expr})"),
@@ -826,6 +856,84 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
826856
ConstraintName(name)
827857
}
828858

859+
/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
860+
///
861+
/// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order.
862+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
863+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
864+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
865+
pub struct ConstraintCharacteristics {
866+
/// `[ DEFERRABLE | NOT DEFERRABLE ]`
867+
pub deferrable: Option<bool>,
868+
/// `[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
869+
pub initially: Option<DeferrableInitial>,
870+
/// `[ ENFORCED | NOT ENFORCED ]`
871+
pub enforced: Option<bool>,
872+
}
873+
874+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
875+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
876+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
877+
pub enum DeferrableInitial {
878+
/// `INITIALLY IMMEDIATE`
879+
Immediate,
880+
/// `INITIALLY DEFERRED`
881+
Deferred,
882+
}
883+
884+
impl ConstraintCharacteristics {
885+
fn deferrable_text(&self) -> Option<&'static str> {
886+
self.deferrable.map(|deferrable| {
887+
if deferrable {
888+
"DEFERRABLE"
889+
} else {
890+
"NOT DEFERRABLE"
891+
}
892+
})
893+
}
894+
895+
fn initially_immediate_text(&self) -> Option<&'static str> {
896+
self.initially
897+
.map(|initially_immediate| match initially_immediate {
898+
DeferrableInitial::Immediate => "INITIALLY IMMEDIATE",
899+
DeferrableInitial::Deferred => "INITIALLY DEFERRED",
900+
})
901+
}
902+
903+
fn enforced_text(&self) -> Option<&'static str> {
904+
self.enforced.map(
905+
|enforced| {
906+
if enforced {
907+
"ENFORCED"
908+
} else {
909+
"NOT ENFORCED"
910+
}
911+
},
912+
)
913+
}
914+
}
915+
916+
impl fmt::Display for ConstraintCharacteristics {
917+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
918+
let deferrable = self.deferrable_text();
919+
let initially_immediate = self.initially_immediate_text();
920+
let enforced = self.enforced_text();
921+
922+
match (deferrable, initially_immediate, enforced) {
923+
(None, None, None) => Ok(()),
924+
(None, None, Some(enforced)) => write!(f, "{enforced}"),
925+
(None, Some(initial), None) => write!(f, "{initial}"),
926+
(None, Some(initial), Some(enforced)) => write!(f, "{initial} {enforced}"),
927+
(Some(deferrable), None, None) => write!(f, "{deferrable}"),
928+
(Some(deferrable), None, Some(enforced)) => write!(f, "{deferrable} {enforced}"),
929+
(Some(deferrable), Some(initial), None) => write!(f, "{deferrable} {initial}"),
930+
(Some(deferrable), Some(initial), Some(enforced)) => {
931+
write!(f, "{deferrable} {initial} {enforced}")
932+
}
933+
}
934+
}
935+
}
936+
829937
/// `<referential_action> =
830938
/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
831939
///

src/ast/mod.rs

+3-2
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,
3839
};
3940
pub use self::operator::{BinaryOperator, UnaryOperator};

src/keywords.rs

+3
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

+60-2
Original file line numberDiff line numberDiff line change
@@ -4392,9 +4392,17 @@ impl<'a> Parser<'a> {
43924392
} else if self.parse_keyword(Keyword::DEFAULT) {
43934393
Ok(Some(ColumnOption::Default(self.parse_expr()?)))
43944394
} else if self.parse_keywords(&[Keyword::PRIMARY, Keyword::KEY]) {
4395-
Ok(Some(ColumnOption::Unique { is_primary: true }))
4395+
let characteristics = self.parse_constraint_characteristics()?;
4396+
Ok(Some(ColumnOption::Unique {
4397+
is_primary: true,
4398+
characteristics,
4399+
}))
43964400
} else if self.parse_keyword(Keyword::UNIQUE) {
4397-
Ok(Some(ColumnOption::Unique { is_primary: false }))
4401+
let characteristics = self.parse_constraint_characteristics()?;
4402+
Ok(Some(ColumnOption::Unique {
4403+
is_primary: false,
4404+
characteristics,
4405+
}))
43984406
} else if self.parse_keyword(Keyword::REFERENCES) {
43994407
let foreign_table = self.parse_object_name()?;
44004408
// PostgreSQL allows omitting the column list and
@@ -4413,11 +4421,14 @@ impl<'a> Parser<'a> {
44134421
break;
44144422
}
44154423
}
4424+
let characteristics = self.parse_constraint_characteristics()?;
4425+
44164426
Ok(Some(ColumnOption::ForeignKey {
44174427
foreign_table,
44184428
referred_columns,
44194429
on_delete,
44204430
on_update,
4431+
characteristics,
44214432
}))
44224433
} else if self.parse_keyword(Keyword::CHECK) {
44234434
self.expect_token(&Token::LParen)?;
@@ -4565,6 +4576,47 @@ impl<'a> Parser<'a> {
45654576
}
45664577
}
45674578

4579+
pub fn parse_constraint_characteristics(
4580+
&mut self,
4581+
) -> Result<Option<ConstraintCharacteristics>, ParserError> {
4582+
let mut cc = ConstraintCharacteristics {
4583+
deferrable: None,
4584+
initially: None,
4585+
enforced: None,
4586+
};
4587+
4588+
loop {
4589+
if cc.deferrable.is_none() && self.parse_keywords(&[Keyword::NOT, Keyword::DEFERRABLE])
4590+
{
4591+
cc.deferrable = Some(false);
4592+
} else if cc.deferrable.is_none() && self.parse_keyword(Keyword::DEFERRABLE) {
4593+
cc.deferrable = Some(true);
4594+
} else if cc.initially.is_none() && self.parse_keyword(Keyword::INITIALLY) {
4595+
if self.parse_keyword(Keyword::DEFERRED) {
4596+
cc.initially = Some(DeferrableInitial::Deferred);
4597+
} else if self.parse_keyword(Keyword::IMMEDIATE) {
4598+
cc.initially = Some(DeferrableInitial::Immediate);
4599+
} else {
4600+
self.expected("one of DEFERRED or IMMEDIATE", self.peek_token())?;
4601+
}
4602+
} else if cc.enforced.is_none() && self.parse_keyword(Keyword::ENFORCED) {
4603+
cc.enforced = Some(true);
4604+
} else if cc.enforced.is_none()
4605+
&& self.parse_keywords(&[Keyword::NOT, Keyword::ENFORCED])
4606+
{
4607+
cc.enforced = Some(false);
4608+
} else {
4609+
break;
4610+
}
4611+
}
4612+
4613+
if cc.deferrable.is_some() || cc.initially.is_some() || cc.enforced.is_some() {
4614+
Ok(Some(cc))
4615+
} else {
4616+
Ok(None)
4617+
}
4618+
}
4619+
45684620
pub fn parse_optional_table_constraint(
45694621
&mut self,
45704622
) -> Result<Option<TableConstraint>, ParserError> {
@@ -4588,10 +4640,12 @@ impl<'a> Parser<'a> {
45884640
.or(name);
45894641

45904642
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
4643+
let characteristics = self.parse_constraint_characteristics()?;
45914644
Ok(Some(TableConstraint::Unique {
45924645
name,
45934646
columns,
45944647
is_primary,
4648+
characteristics,
45954649
}))
45964650
}
45974651
Token::Word(w) if w.keyword == Keyword::FOREIGN => {
@@ -4613,13 +4667,17 @@ impl<'a> Parser<'a> {
46134667
break;
46144668
}
46154669
}
4670+
4671+
let characteristics = self.parse_constraint_characteristics()?;
4672+
46164673
Ok(Some(TableConstraint::ForeignKey {
46174674
name,
46184675
columns,
46194676
foreign_table,
46204677
referred_columns,
46214678
on_delete,
46224679
on_update,
4680+
characteristics,
46234681
}))
46244682
}
46254683
Token::Word(w) if w.keyword == Keyword::CHECK => {

0 commit comments

Comments
 (0)