This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch 
gh-readonly-queue/main/pr-2213-fb10a76f1772fc7f5805931dc308792f3c3ee716
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 15dc6a22a99131072f05b94f59f1532852209cc6
Author: Guan-Ming (Wesley) Chiu <[email protected]>
AuthorDate: Thu Feb 19 19:29:28 2026 +0800

    PostgreSQL: Support PRIMARY KEY/UNIQUE USING INDEX (#2213)
    
    Signed-off-by: Guan-Ming Chiu <[email protected]>
---
 src/ast/mod.rs               |  4 +--
 src/ast/spans.rs             |  2 ++
 src/ast/table_constraints.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++
 src/parser/mod.rs            | 31 ++++++++++++++++++++
 tests/sqlparser_postgres.rs  | 39 +++++++++++++++++++++++++
 5 files changed, 142 insertions(+), 2 deletions(-)

diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index dbf5003c..d534b300 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -136,8 +136,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;
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index d792c13c..74f19e83 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -626,6 +626,8 @@ impl Spanned for TableConstraint {
             TableConstraint::Check(constraint) => constraint.span(),
             TableConstraint::Index(constraint) => constraint.span(),
             TableConstraint::FulltextOrSpatial(constraint) => 
constraint.span(),
+            TableConstraint::PrimaryKeyUsingIndex(constraint)
+            | TableConstraint::UniqueUsingIndex(constraint) => 
constraint.span(),
         }
     }
 }
diff --git a/src/ast/table_constraints.rs b/src/ast/table_constraints.rs
index cb3c2376..9ba196a8 100644
--- a/src/ast/table_constraints.rs
+++ b/src/ast/table_constraints.rs
@@ -101,6 +101,22 @@ 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` constraint:
+    ///
+    /// `[ CONSTRAINT constraint_name ] PRIMARY KEY USING INDEX index_name
+    ///   [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY 
IMMEDIATE ]`
+    ///
+    /// [1]: https://www.postgresql.org/docs/current/sql-altertable.html
+    PrimaryKeyUsingIndex(ConstraintUsingIndex),
+    /// PostgreSQL [definition][1] for promoting an existing unique index to a
+    /// `UNIQUE` constraint:
+    ///
+    /// `[ CONSTRAINT constraint_name ] UNIQUE USING INDEX index_name
+    ///   [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY 
IMMEDIATE ]`
+    ///
+    /// [1]: https://www.postgresql.org/docs/current/sql-altertable.html
+    UniqueUsingIndex(ConstraintUsingIndex),
 }
 
 impl From<UniqueConstraint> for TableConstraint {
@@ -148,6 +164,8 @@ impl fmt::Display for TableConstraint {
             TableConstraint::Check(constraint) => constraint.fmt(f),
             TableConstraint::Index(constraint) => constraint.fmt(f),
             TableConstraint::FulltextOrSpatial(constraint) => 
constraint.fmt(f),
+            TableConstraint::PrimaryKeyUsingIndex(c) => c.fmt_with_keyword(f, 
"PRIMARY KEY"),
+            TableConstraint::UniqueUsingIndex(c) => c.fmt_with_keyword(f, 
"UNIQUE"),
         }
     }
 }
@@ -535,3 +553,53 @@ 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>,
+    /// The name of the existing unique index to promote.
+    pub index_name: Ident,
+    /// Optional characteristics like `DEFERRABLE`.
+    pub characteristics: Option<ConstraintCharacteristics>,
+}
+
+impl ConstraintUsingIndex {
+    /// Format as `[CONSTRAINT name] <keyword> USING INDEX index_name 
[characteristics]`.
+    pub fn fmt_with_keyword(&self, f: &mut fmt::Formatter, keyword: &str) -> 
fmt::Result {
+        use crate::ast::ddl::{display_constraint_name, display_option_spaced};
+        write!(
+            f,
+            "{}{} USING INDEX {}",
+            display_constraint_name(&self.name),
+            keyword,
+            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)
+    }
+}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 4f32422f..6c9314d9 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9333,6 +9333,21 @@ impl<'a> Parser<'a> {
         }
     }
 
+    /// Parse `index_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED 
| INITIALLY IMMEDIATE ]`
+    /// after `{ PRIMARY KEY | UNIQUE } USING INDEX`.
+    fn parse_constraint_using_index(
+        &mut self,
+        name: Option<Ident>,
+    ) -> Result<ConstraintUsingIndex, ParserError> {
+        let index_name = self.parse_identifier()?;
+        let characteristics = self.parse_constraint_characteristics()?;
+        Ok(ConstraintUsingIndex {
+            name,
+            index_name,
+            characteristics,
+        })
+    }
+
     /// Parse optional constraint characteristics such as `DEFERRABLE`, 
`INITIALLY` and `ENFORCED`.
     pub fn parse_constraint_characteristics(
         &mut self,
@@ -9397,6 +9412,14 @@ 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]) {
+                    return Ok(Some(TableConstraint::UniqueUsingIndex(
+                        self.parse_constraint_using_index(name)?,
+                    )));
+                }
+
                 let index_type_display = self.parse_index_type_display();
                 if !dialect_of!(self is GenericDialect | MySqlDialect)
                     && !index_type_display.is_none()
@@ -9432,6 +9455,14 @@ 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]) {
+                    return Ok(Some(TableConstraint::PrimaryKeyUsingIndex(
+                        self.parse_constraint_using_index(name)?,
+                    )));
+                }
+
                 // optional index name
                 let index_name = self.parse_optional_ident()?;
                 let index_type = self.parse_optional_using_then_index_type()?;
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index f8c73813..d79e2b83 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -627,6 +627,45 @@ 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::PrimaryKeyUsingIndex(c),
+                ..
+            } => {
+                assert_eq!(c.name.as_ref().unwrap().to_string(), "c");
+                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");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to