Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ mod dml;
pub mod helpers;
pub mod table_constraints;
pub use table_constraints::{
CheckConstraint, ForeignKeyConstraint, FullTextOrSpatialConstraint, IndexConstraint,
PrimaryKeyConstraint, TableConstraint, UniqueConstraint,
CheckConstraint, ConstraintUsingIndex, ForeignKeyConstraint, FullTextOrSpatialConstraint,
IndexConstraint, PrimaryKeyConstraint, TableConstraint, UniqueConstraint,
};
mod operator;
mod query;
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ impl Spanned for TableConstraint {
TableConstraint::Check(constraint) => constraint.span(),
TableConstraint::Index(constraint) => constraint.span(),
TableConstraint::FulltextOrSpatial(constraint) => constraint.span(),
TableConstraint::ConstraintUsingIndex(constraint) => constraint.span(),
}
}
}
Expand Down
70 changes: 70 additions & 0 deletions src/ast/table_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ pub enum TableConstraint {
/// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
/// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html
FulltextOrSpatial(FullTextOrSpatialConstraint),
/// PostgreSQL [definition][1] for promoting an existing unique index to a
/// `PRIMARY KEY` or `UNIQUE` constraint:
///
/// `[ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name
/// [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
///
/// [1]: https://www.postgresql.org/docs/current/sql-altertable.html
ConstraintUsingIndex(ConstraintUsingIndex),
}

impl From<UniqueConstraint> for TableConstraint {
Expand Down Expand Up @@ -139,6 +147,12 @@ impl From<FullTextOrSpatialConstraint> for TableConstraint {
}
}

impl From<ConstraintUsingIndex> for TableConstraint {
fn from(constraint: ConstraintUsingIndex) -> Self {
TableConstraint::ConstraintUsingIndex(constraint)
}
}

impl fmt::Display for TableConstraint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Expand All @@ -148,6 +162,7 @@ impl fmt::Display for TableConstraint {
TableConstraint::Check(constraint) => constraint.fmt(f),
TableConstraint::Index(constraint) => constraint.fmt(f),
TableConstraint::FulltextOrSpatial(constraint) => constraint.fmt(f),
TableConstraint::ConstraintUsingIndex(constraint) => constraint.fmt(f),
}
}
}
Expand Down Expand Up @@ -535,3 +550,58 @@ impl crate::ast::Spanned for UniqueConstraint {
)
}
}

/// PostgreSQL constraint that promotes an existing unique index to a table constraint.
///
/// `[ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name
/// [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
///
/// See <https://www.postgresql.org/docs/current/sql-altertable.html>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct ConstraintUsingIndex {
/// Optional constraint name.
pub name: Option<Ident>,
/// Whether this is a `PRIMARY KEY` (true) or `UNIQUE` (false) constraint.
pub is_primary_key: bool,
/// The name of the existing unique index to promote.
pub index_name: Ident,
/// Optional characteristics like `DEFERRABLE`.
pub characteristics: Option<ConstraintCharacteristics>,
}

impl fmt::Display for ConstraintUsingIndex {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::ast::ddl::{display_constraint_name, display_option_spaced};
write!(
f,
"{}{} USING INDEX {}",
display_constraint_name(&self.name),
if self.is_primary_key {
"PRIMARY KEY"
} else {
"UNIQUE"
},
self.index_name,
)?;
write!(f, "{}", display_option_spaced(&self.characteristics))?;
Ok(())
}
}

impl crate::ast::Spanned for ConstraintUsingIndex {
fn span(&self) -> Span {
let start = self
.name
.as_ref()
.map(|i| i.span)
.unwrap_or(self.index_name.span);
let end = self
.characteristics
.as_ref()
.map(|c| c.span())
.unwrap_or(self.index_name.span);
start.union(&end)
}
}
32 changes: 32 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9314,6 +9314,22 @@ impl<'a> Parser<'a> {
let next_token = self.next_token();
match next_token.token {
Token::Word(w) if w.keyword == Keyword::UNIQUE => {
// PostgreSQL: UNIQUE USING INDEX index_name
// https://www.postgresql.org/docs/current/sql-altertable.html
if self.parse_keywords(&[Keyword::USING, Keyword::INDEX]) {
let index_name = self.parse_identifier()?;
let characteristics = self.parse_constraint_characteristics()?;
return Ok(Some(
ConstraintUsingIndex {
name,
is_primary_key: false,
index_name,
characteristics,
}
.into(),
));
}

let index_type_display = self.parse_index_type_display();
if !dialect_of!(self is GenericDialect | MySqlDialect)
&& !index_type_display.is_none()
Expand Down Expand Up @@ -9349,6 +9365,22 @@ impl<'a> Parser<'a> {
// after `PRIMARY` always stay `KEY`
self.expect_keyword_is(Keyword::KEY)?;

// PostgreSQL: PRIMARY KEY USING INDEX index_name
// https://www.postgresql.org/docs/current/sql-altertable.html
if self.parse_keywords(&[Keyword::USING, Keyword::INDEX]) {
let index_name = self.parse_identifier()?;
let characteristics = self.parse_constraint_characteristics()?;
return Ok(Some(
ConstraintUsingIndex {
name,
is_primary_key: true,
index_name,
characteristics,
}
.into(),
));
}

// optional index name
let index_name = self.parse_optional_ident()?;
let index_type = self.parse_optional_using_then_index_type()?;
Expand Down
40 changes: 40 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,46 @@ fn parse_alter_table_constraints_unique_nulls_distinct() {
pg_and_generic().verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE (c)");
}

#[test]
fn parse_alter_table_constraint_using_index() {
// PRIMARY KEY USING INDEX
// https://www.postgresql.org/docs/current/sql-altertable.html
let sql = "ALTER TABLE tab ADD CONSTRAINT c PRIMARY KEY USING INDEX my_index";
match pg_and_generic().verified_stmt(sql) {
Statement::AlterTable(alter_table) => match &alter_table.operations[0] {
AlterTableOperation::AddConstraint {
constraint: TableConstraint::ConstraintUsingIndex(c),
..
} => {
assert_eq!(c.name.as_ref().unwrap().to_string(), "c");
assert!(c.is_primary_key);
assert_eq!(c.index_name.to_string(), "my_index");
assert!(c.characteristics.is_none());
}
_ => unreachable!(),
},
_ => unreachable!(),
}

// UNIQUE USING INDEX
pg_and_generic().verified_stmt("ALTER TABLE tab ADD CONSTRAINT c UNIQUE USING INDEX my_index");

// Without constraint name
pg_and_generic().verified_stmt("ALTER TABLE tab ADD PRIMARY KEY USING INDEX my_index");
pg_and_generic().verified_stmt("ALTER TABLE tab ADD UNIQUE USING INDEX my_index");

// With DEFERRABLE
pg_and_generic().verified_stmt(
"ALTER TABLE tab ADD CONSTRAINT c PRIMARY KEY USING INDEX my_index DEFERRABLE",
);
pg_and_generic().verified_stmt(
"ALTER TABLE tab ADD CONSTRAINT c UNIQUE USING INDEX my_index NOT DEFERRABLE INITIALLY IMMEDIATE",
);
pg_and_generic().verified_stmt(
"ALTER TABLE tab ADD CONSTRAINT c PRIMARY KEY USING INDEX my_index DEFERRABLE INITIALLY DEFERRED",
);
}

#[test]
fn parse_alter_table_disable() {
pg_and_generic().verified_stmt("ALTER TABLE tab DISABLE ROW LEVEL SECURITY");
Expand Down
Loading