Skip to content

Commit d870cb8

Browse files
Nikita-stralamb
authored andcommitted
Support MySQL UNIQUE table constraint (apache#1164)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
1 parent 3156448 commit d870cb8

File tree

4 files changed

+441
-94
lines changed

4 files changed

+441
-94
lines changed

src/ast/ddl.rs

+163-13
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
1616
#[cfg(not(feature = "std"))]
1717
use alloc::{boxed::Box, string::String, vec::Vec};
18-
use core::fmt;
18+
use core::fmt::{self, Write};
1919

2020
#[cfg(feature = "serde")]
2121
use serde::{Deserialize, Serialize};
@@ -397,12 +397,68 @@ impl fmt::Display for AlterColumnOperation {
397397
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
398398
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
399399
pub enum TableConstraint {
400-
/// `[ CONSTRAINT <name> ] { PRIMARY KEY | UNIQUE } (<columns>)`
400+
/// MySQL [definition][1] for `UNIQUE` constraints statements:\
401+
/// * `[CONSTRAINT [<name>]] UNIQUE <index_type_display> [<index_name>] [index_type] (<columns>) <index_options>`
402+
///
403+
/// where:
404+
/// * [index_type][2] is `USING {BTREE | HASH}`
405+
/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
406+
/// * [index_type_display][4] is `[INDEX | KEY]`
407+
///
408+
/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
409+
/// [2]: IndexType
410+
/// [3]: IndexOption
411+
/// [4]: KeyOrIndexDisplay
401412
Unique {
413+
/// Constraint name.
414+
///
415+
/// Can be not the same as `index_name`
402416
name: Option<Ident>,
417+
/// Index name
418+
index_name: Option<Ident>,
419+
/// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
420+
index_type_display: KeyOrIndexDisplay,
421+
/// Optional `USING` of [index type][1] statement before columns.
422+
///
423+
/// [1]: IndexType
424+
index_type: Option<IndexType>,
425+
/// Identifiers of the columns that are unique.
403426
columns: Vec<Ident>,
404-
/// Whether this is a `PRIMARY KEY` or just a `UNIQUE` constraint
405-
is_primary: bool,
427+
index_options: Vec<IndexOption>,
428+
characteristics: Option<ConstraintCharacteristics>,
429+
},
430+
/// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\
431+
/// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
432+
///
433+
/// Actually the specification have no `[index_name]` but the next query will complete successfully:
434+
/// ```sql
435+
/// CREATE TABLE unspec_table (
436+
/// xid INT NOT NULL,
437+
/// CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid)
438+
/// );
439+
/// ```
440+
///
441+
/// where:
442+
/// * [index_type][2] is `USING {BTREE | HASH}`
443+
/// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
444+
///
445+
/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
446+
/// [2]: IndexType
447+
/// [3]: IndexOption
448+
PrimaryKey {
449+
/// Constraint name.
450+
///
451+
/// Can be not the same as `index_name`
452+
name: Option<Ident>,
453+
/// Index name
454+
index_name: Option<Ident>,
455+
/// Optional `USING` of [index type][1] statement before columns.
456+
///
457+
/// [1]: IndexType
458+
index_type: Option<IndexType>,
459+
/// Identifiers of the columns that form the primary key.
460+
columns: Vec<Ident>,
461+
index_options: Vec<IndexOption>,
406462
characteristics: Option<ConstraintCharacteristics>,
407463
},
408464
/// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
@@ -472,22 +528,51 @@ impl fmt::Display for TableConstraint {
472528
match self {
473529
TableConstraint::Unique {
474530
name,
531+
index_name,
532+
index_type_display,
533+
index_type,
475534
columns,
476-
is_primary,
535+
index_options,
477536
characteristics,
478537
} => {
479538
write!(
480539
f,
481-
"{}{} ({})",
540+
"{}UNIQUE{index_type_display:>}{}{} ({})",
482541
display_constraint_name(name),
483-
if *is_primary { "PRIMARY KEY" } else { "UNIQUE" },
484-
display_comma_separated(columns)
542+
display_option_spaced(index_name),
543+
display_option(" USING ", "", index_type),
544+
display_comma_separated(columns),
485545
)?;
486546

487-
if let Some(characteristics) = characteristics {
488-
write!(f, " {}", characteristics)?;
547+
if !index_options.is_empty() {
548+
write!(f, " {}", display_separated(index_options, " "))?;
549+
}
550+
551+
write!(f, "{}", display_option_spaced(characteristics))?;
552+
Ok(())
553+
}
554+
TableConstraint::PrimaryKey {
555+
name,
556+
index_name,
557+
index_type,
558+
columns,
559+
index_options,
560+
characteristics,
561+
} => {
562+
write!(
563+
f,
564+
"{}PRIMARY KEY{}{} ({})",
565+
display_constraint_name(name),
566+
display_option_spaced(index_name),
567+
display_option(" USING ", "", index_type),
568+
display_comma_separated(columns),
569+
)?;
570+
571+
if !index_options.is_empty() {
572+
write!(f, " {}", display_separated(index_options, " "))?;
489573
}
490574

575+
write!(f, "{}", display_option_spaced(characteristics))?;
491576
Ok(())
492577
}
493578
TableConstraint::ForeignKey {
@@ -550,9 +635,7 @@ impl fmt::Display for TableConstraint {
550635
write!(f, "SPATIAL")?;
551636
}
552637

553-
if !matches!(index_type_display, KeyOrIndexDisplay::None) {
554-
write!(f, " {index_type_display}")?;
555-
}
638+
write!(f, "{index_type_display:>}")?;
556639

557640
if let Some(name) = opt_index_name {
558641
write!(f, " {name}")?;
@@ -585,8 +668,20 @@ pub enum KeyOrIndexDisplay {
585668
Index,
586669
}
587670

671+
impl KeyOrIndexDisplay {
672+
pub fn is_none(self) -> bool {
673+
matches!(self, Self::None)
674+
}
675+
}
676+
588677
impl fmt::Display for KeyOrIndexDisplay {
589678
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
679+
let left_space = matches!(f.align(), Some(fmt::Alignment::Right));
680+
681+
if left_space && !self.is_none() {
682+
f.write_char(' ')?
683+
}
684+
590685
match self {
591686
KeyOrIndexDisplay::None => {
592687
write!(f, "")
@@ -626,6 +721,30 @@ impl fmt::Display for IndexType {
626721
}
627722
}
628723
}
724+
725+
/// MySQLs index option.
726+
///
727+
/// This structure used here [`MySQL` CREATE TABLE][1], [`MySQL` CREATE INDEX][2].
728+
///
729+
/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
730+
/// [2]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html
731+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
732+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
733+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
734+
pub enum IndexOption {
735+
Using(IndexType),
736+
Comment(String),
737+
}
738+
739+
impl fmt::Display for IndexOption {
740+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
741+
match self {
742+
Self::Using(index_type) => write!(f, "USING {index_type}"),
743+
Self::Comment(s) => write!(f, "COMMENT '{s}'"),
744+
}
745+
}
746+
}
747+
629748
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
630749
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
631750
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -909,6 +1028,7 @@ pub enum GeneratedExpressionMode {
9091028
Stored,
9101029
}
9111030

1031+
#[must_use]
9121032
fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
9131033
struct ConstraintName<'a>(&'a Option<Ident>);
9141034
impl<'a> fmt::Display for ConstraintName<'a> {
@@ -922,6 +1042,36 @@ fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
9221042
ConstraintName(name)
9231043
}
9241044

1045+
/// If `option` is
1046+
/// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"`
1047+
/// * `_` => do nothing
1048+
#[must_use]
1049+
fn display_option<'a, T: fmt::Display>(
1050+
prefix: &'a str,
1051+
postfix: &'a str,
1052+
option: &'a Option<T>,
1053+
) -> impl fmt::Display + 'a {
1054+
struct OptionDisplay<'a, T>(&'a str, &'a str, &'a Option<T>);
1055+
impl<'a, T: fmt::Display> fmt::Display for OptionDisplay<'a, T> {
1056+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1057+
if let Some(inner) = self.2 {
1058+
let (prefix, postfix) = (self.0, self.1);
1059+
write!(f, "{prefix}{inner}{postfix}")?;
1060+
}
1061+
Ok(())
1062+
}
1063+
}
1064+
OptionDisplay(prefix, postfix, option)
1065+
}
1066+
1067+
/// If `option` is
1068+
/// * `Some(inner)` => create display struct for `" {inner}"`
1069+
/// * `_` => do nothing
1070+
#[must_use]
1071+
fn display_option_spaced<T: fmt::Display>(option: &Option<T>) -> impl fmt::Display + '_ {
1072+
display_option(" ", "", option)
1073+
}
1074+
9251075
/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
9261076
///
9271077
/// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order.

src/ast/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}
3333
pub use self::ddl::{
3434
AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption,
3535
ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs,
36-
GeneratedExpressionMode, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
36+
GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Partition, ProcedureParam,
3737
ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef,
3838
UserDefinedTypeRepresentation, ViewColumnDef,
3939
};

0 commit comments

Comments
 (0)