This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 15dc6a22 PostgreSQL: Support PRIMARY KEY/UNIQUE USING INDEX (#2213)
15dc6a22 is described below
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]