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

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

commit 83baf5e89179dd00321330312fe460100d755c25
Author: Michael Victor Zink <[email protected]>
AuthorDate: Fri Feb 27 02:59:05 2026 -0800

    Support MySQL KEY keyword in column definitions (#2243)
    
    Co-authored-by: Yoav Cohen <[email protected]>
---
 src/ast/ddl.rs           |  2 +-
 src/dialect/generic.rs   |  4 ++++
 src/dialect/mod.rs       | 12 ++++++++++++
 src/dialect/mysql.rs     |  5 +++++
 src/parser/mod.rs        | 23 ++++++++++++++++++++++-
 tests/sqlparser_mysql.rs |  9 +++++++++
 6 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 0c4f93e6..3a951f66 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -2040,7 +2040,7 @@ impl fmt::Display for ColumnOption {
                 Ok(())
             }
             Unique(constraint) => {
-                write!(f, "UNIQUE")?;
+                write!(f, "UNIQUE{:>}", constraint.index_type_display)?;
                 if let Some(characteristics) = &constraint.characteristics {
                     write!(f, " {characteristics}")?;
                 }
diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs
index 1cf195e6..a7a3c271 100644
--- a/src/dialect/generic.rs
+++ b/src/dialect/generic.rs
@@ -280,4 +280,8 @@ impl Dialect for GenericDialect {
     fn supports_constraint_keyword_without_name(&self) -> bool {
         true
     }
+
+    fn supports_key_column_option(&self) -> bool {
+        true
+    }
 }
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 698c12ec..796b25f0 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1195,6 +1195,18 @@ pub trait Dialect: Debug + Any {
         false
     }
 
+    /// Returns true if the dialect supports the `KEY` keyword as part of
+    /// column-level constraints in a `CREATE TABLE` statement.
+    ///
+    /// When enabled, the parser accepts these MySQL-specific column options:
+    /// - `UNIQUE [KEY]` — optional `KEY` after `UNIQUE`
+    /// - `[PRIMARY] KEY` — standalone `KEY` as shorthand for `PRIMARY KEY`
+    ///
+    /// <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
+    fn supports_key_column_option(&self) -> bool {
+        false
+    }
+
     /// Returns true if the specified keyword is reserved and cannot be
     /// used as an identifier without special handling like quoting.
     fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs
index bdced482..6b057539 100644
--- a/src/dialect/mysql.rs
+++ b/src/dialect/mysql.rs
@@ -206,6 +206,11 @@ impl Dialect for MySqlDialect {
     fn supports_constraint_keyword_without_name(&self) -> bool {
         true
     }
+
+    /// See: <https://dev.mysql.com/doc/refman/8.4/en/create-table.html>
+    fn supports_key_column_option(&self) -> bool {
+        true
+    }
 }
 
 /// `LOCK TABLES`
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 75db4d24..a00eab34 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -9048,12 +9048,18 @@ impl<'a> Parser<'a> {
                 .into(),
             ))
         } else if self.parse_keyword(Keyword::UNIQUE) {
+            let index_type_display =
+                if self.dialect.supports_key_column_option() && 
self.parse_keyword(Keyword::KEY) {
+                    KeyOrIndexDisplay::Key
+                } else {
+                    KeyOrIndexDisplay::None
+                };
             let characteristics = self.parse_constraint_characteristics()?;
             Ok(Some(
                 UniqueConstraint {
                     name: None,
                     index_name: None,
-                    index_type_display: KeyOrIndexDisplay::None,
+                    index_type_display,
                     index_type: None,
                     columns: vec![],
                     index_options: vec![],
@@ -9062,6 +9068,21 @@ impl<'a> Parser<'a> {
                 }
                 .into(),
             ))
+        } else if self.dialect.supports_key_column_option() && 
self.parse_keyword(Keyword::KEY) {
+            // In MySQL, `KEY` in a column definition is shorthand for 
`PRIMARY KEY`.
+            // See: https://dev.mysql.com/doc/refman/8.4/en/create-table.html
+            let characteristics = self.parse_constraint_characteristics()?;
+            Ok(Some(
+                PrimaryKeyConstraint {
+                    name: None,
+                    index_name: None,
+                    index_type: None,
+                    columns: vec![],
+                    index_options: vec![],
+                    characteristics,
+                }
+                .into(),
+            ))
         } else if self.parse_keyword(Keyword::REFERENCES) {
             let foreign_table = self.parse_object_name(false)?;
             // PostgreSQL allows omitting the column list and
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 30405623..b4ae764c 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -944,6 +944,15 @@ fn 
parse_create_table_primary_and_unique_key_characteristic_test() {
     }
 }
 
+#[test]
+fn parse_create_table_column_key_options() {
+    mysql_and_generic().verified_stmt("CREATE TABLE foo (x INT UNIQUE KEY)");
+    mysql_and_generic().one_statement_parses_to(
+        "CREATE TABLE foo (x INT KEY)",
+        "CREATE TABLE foo (x INT PRIMARY KEY)",
+    );
+}
+
 #[test]
 fn parse_create_table_comment() {
     let without_equal = "CREATE TABLE foo (bar INT) COMMENT 'baz'";


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

Reply via email to