Skip to content

Commit 91a677f

Browse files
committed
PostgreSQL: Support PRIMARY KEY/UNIQUE USING INDEX
Signed-off-by: Guan-Ming Chiu <guanmingchiu@gmail.com>
1 parent 8e36e8e commit 91a677f

5 files changed

Lines changed: 145 additions & 2 deletions

File tree

src/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ mod dml;
136136
pub mod helpers;
137137
pub mod table_constraints;
138138
pub use table_constraints::{
139-
CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint,
140-
PrimaryKeyConstraint, TableConstraint, UniqueConstraint,
139+
CheckConstraint, ConstraintUsingIndex, ForeignKeyConstraint, FullTextOrSpatialConstraint,
140+
IndexConstraint, PrimaryKeyConstraint, TableConstraint, UniqueConstraint,
141141
};
142142
mod operator;
143143
mod query;

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ impl Spanned for TableConstraint {
625625
TableConstraint::Check(constraint) => constraint.span(),
626626
TableConstraint::Index(constraint) => constraint.span(),
627627
TableConstraint::FulltextOrSpatial(constraint) => constraint.span(),
628+
TableConstraint::ConstraintUsingIndex(constraint) => constraint.span(),
628629
}
629630
}
630631
}

src/ast/table_constraints.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ pub enum TableConstraint {
101101
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
102102
/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html
103103
FulltextOrSpatial(FullTextOrSpatialConstraint),
104+
/// PostgreSQL [definition][1] for promoting an existing unique index to a
105+
/// `PRIMARY KEY` or `UNIQUE` constraint:
106+
///
107+
/// `[ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name
108+
/// [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
109+
///
110+
/// [1]: https://www.postgresql.org/docs/current/sql-altertable.html
111+
ConstraintUsingIndex(ConstraintUsingIndex),
104112
}
105113

106114
impl From<UniqueConstraint> for TableConstraint {
@@ -139,6 +147,12 @@ impl From<FullTextOrSpatialConstraint> for TableConstraint {
139147
}
140148
}
141149

150+
impl From<ConstraintUsingIndex> for TableConstraint {
151+
fn from(constraint: ConstraintUsingIndex) -> Self {
152+
TableConstraint::ConstraintUsingIndex(constraint)
153+
}
154+
}
155+
142156
impl fmt::Display for TableConstraint {
143157
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144158
match self {
@@ -148,6 +162,7 @@ impl fmt::Display for TableConstraint {
148162
TableConstraint::Check(constraint) => constraint.fmt(f),
149163
TableConstraint::Index(constraint) => constraint.fmt(f),
150164
TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f),
165+
TableConstraint::ConstraintUsingIndex(constraint) => constraint.fmt(f),
151166
}
152167
}
153168
}
@@ -535,3 +550,58 @@ impl crate::ast::Spanned for UniqueConstraint {
535550
)
536551
}
537552
}
553+
554+
/// PostgreSQL constraint that promotes an existing unique index to a table constraint.
555+
///
556+
/// `[ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name
557+
/// [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
558+
///
559+
/// See <https://www.postgresql.org/docs/current/sql-altertable.html>
560+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
561+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
562+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
563+
pub struct ConstraintUsingIndex {
564+
/// Optional constraint name.
565+
pub name: Option<Ident>,
566+
/// Whether this is a `PRIMARY KEY` (true) or `UNIQUE` (false) constraint.
567+
pub is_primary_key: bool,
568+
/// The name of the existing unique index to promote.
569+
pub index_name: Ident,
570+
/// Optional characteristics like `DEFERRABLE`.
571+
pub characteristics: Option<ConstraintCharacteristics>,
572+
}
573+
574+
impl fmt::Display for ConstraintUsingIndex {
575+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
576+
use crate::ast::ddl::{display_constraint_name, display_option_spaced};
577+
write!(
578+
f,
579+
"{}{} USING INDEX {}",
580+
display_constraint_name(&self.name),
581+
if self.is_primary_key {
582+
"PRIMARY KEY"
583+
} else {
584+
"UNIQUE"
585+
},
586+
self.index_name,
587+
)?;
588+
write!(f, "{}", display_option_spaced(&self.characteristics))?;
589+
Ok(())
590+
}
591+
}
592+
593+
impl crate::ast::Spanned for ConstraintUsingIndex {
594+
fn span(&self) -> Span {
595+
let start = self
596+
.name
597+
.as_ref()
598+
.map(|i| i.span)
599+
.unwrap_or(self.index_name.span);
600+
let end = self
601+
.characteristics
602+
.as_ref()
603+
.map(|c| c.span())
604+
.unwrap_or(self.index_name.span);
605+
start.union(&end)
606+
}
607+
}

src/parser/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9340,6 +9340,22 @@ impl<'a> Parser<'a> {
93409340
let next_token = self.next_token();
93419341
match next_token.token {
93429342
Token::Word(w) if w.keyword == Keyword::UNIQUE => {
9343+
// PostgreSQL: UNIQUE USING INDEX index_name
9344+
// https://www.postgresql.org/docs/current/sql-altertable.html
9345+
if self.parse_keywords(&[Keyword::USING, Keyword::INDEX]) {
9346+
let index_name = self.parse_identifier()?;
9347+
let characteristics = self.parse_constraint_characteristics()?;
9348+
return Ok(Some(
9349+
ConstraintUsingIndex {
9350+
name,
9351+
is_primary_key: false,
9352+
index_name,
9353+
characteristics,
9354+
}
9355+
.into(),
9356+
));
9357+
}
9358+
93439359
let index_type_display = self.parse_index_type_display();
93449360
if !dialect_of!(self is GenericDialect | MySqlDialect)
93459361
&& !index_type_display.is_none()
@@ -9375,6 +9391,22 @@ impl<'a> Parser<'a> {
93759391
// after `PRIMARY` always stay `KEY`
93769392
self.expect_keyword_is(Keyword::KEY)?;
93779393

9394+
// PostgreSQL: PRIMARY KEY USING INDEX index_name
9395+
// https://www.postgresql.org/docs/current/sql-altertable.html
9396+
if self.parse_keywords(&[Keyword::USING, Keyword::INDEX]) {
9397+
let index_name = self.parse_identifier()?;
9398+
let characteristics = self.parse_constraint_characteristics()?;
9399+
return Ok(Some(
9400+
ConstraintUsingIndex {
9401+
name,
9402+
is_primary_key: true,
9403+
index_name,
9404+
characteristics,
9405+
}
9406+
.into(),
9407+
));
9408+
}
9409+
93789410
// optional index name
93799411
let index_name = self.parse_optional_ident()?;
93809412
let index_type = self.parse_optional_using_then_index_type()?;

tests/sqlparser_postgres.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,46 @@ fn parse_alter_table_constraints_unique_nulls_distinct() {
627627
pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE (c)");
628628
}
629629

630+
#[test]
631+
fn parse_alter_table_constraint_using_index() {
632+
// PRIMARY KEY USING INDEX
633+
// https://www.postgresql.org/docs/current/sql-altertable.html
634+
let sql = "ALTER TABLE tab ADD CONSTRAINT c PRIMARY KEY USING INDEX my_index";
635+
match pg_and_generic().verified_stmt(sql) {
636+
Statement::AlterTable(alter_table) => match &alter_table.operations[0] {
637+
AlterTableOperation::AddConstraint {
638+
constraint: TableConstraint::ConstraintUsingIndex(c),
639+
..
640+
} => {
641+
assert_eq!(c.name.as_ref().unwrap().to_string(), "c");
642+
assert!(c.is_primary_key);
643+
assert_eq!(c.index_name.to_string(), "my_index");
644+
assert!(c.characteristics.is_none());
645+
}
646+
_ => unreachable!(),
647+
},
648+
_ => unreachable!(),
649+
}
650+
651+
// UNIQUE USING INDEX
652+
pg_and_generic().verified_stmt("ALTER TABLE tab ADD CONSTRAINT c UNIQUE USING INDEX my_index");
653+
654+
// Without constraint name
655+
pg_and_generic().verified_stmt("ALTER TABLE tab ADD PRIMARY KEY USING INDEX my_index");
656+
pg_and_generic().verified_stmt("ALTER TABLE tab ADD UNIQUE USING INDEX my_index");
657+
658+
// With DEFERRABLE
659+
pg_and_generic().verified_stmt(
660+
"ALTER TABLE tab ADD CONSTRAINT c PRIMARY KEY USING INDEX my_index DEFERRABLE",
661+
);
662+
pg_and_generic().verified_stmt(
663+
"ALTER TABLE tab ADD CONSTRAINT c UNIQUE USING INDEX my_index NOT DEFERRABLE INITIALLY IMMEDIATE",
664+
);
665+
pg_and_generic().verified_stmt(
666+
"ALTER TABLE tab ADD CONSTRAINT c PRIMARY KEY USING INDEX my_index DEFERRABLE INITIALLY DEFERRED",
667+
);
668+
}
669+
630670
#[test]
631671
fn parse_alter_table_disable() {
632672
pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL SECURITY");

0 commit comments

Comments
 (0)