Repository: apache/datafusion-sqlparser-rs (rust)

Issue:
Parse MySQL `ALGORITHM` and `LOCK` options to `ALTER TABLE`
Example:

```sql
ALTER TABLE tbl_name MODIFY COLUMN col_name column_definition FIRST, ALGORITHM=INPLACE, LOCK=NONE;
```

See [`ALTER TABLE` docs](https://dev.mysql.com/doc/refman/8.4/en/alter-table.html) for syntax and [Online DDL Operations docs](https://dev.mysql.com/doc/refman/8.4/en/innodb-online-ddl-operations.html) for semantics.

```
  | ALGORITHM [=] {DEFAULT | INSTANT | INPLACE | COPY}
  | LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}
```

Pull request description:
Add LOCK operation for ALTER TABLE
Closes #1665


Patch:
diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs
index 99d8521..39e43ef 100644
--- a/src/ast/ddl.rs
+++ b/src/ast/ddl.rs
@@ -288,6 +288,16 @@ pub enum AlterTableOperation {
         equals: bool,
         algorithm: AlterTableAlgorithm,
     },
+
+    /// `LOCK [=] { DEFAULT | NONE | SHARED | EXCLUSIVE }`
+    ///
+    /// [MySQL]-specific table alter lock.
+    ///
+    /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/alter-table.html
+    Lock {
+        equals: bool,
+        lock: AlterTableLock,
+    },
     /// `AUTO_INCREMENT [=] <value>`
     ///
     /// [MySQL]-specific table option for raising current auto increment value.
@@ -366,6 +376,30 @@ impl fmt::Display for AlterTableAlgorithm {
     }
 }
 
+/// [MySQL] `ALTER TABLE` lock.
+///
+/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/alter-table.html
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum AlterTableLock {
+    Default,
+    None,
+    Shared,
+    Exclusive,
+}
+
+impl fmt::Display for AlterTableLock {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        f.write_str(match self {
+            Self::Default => "DEFAULT",
+            Self::None => "NONE",
+            Self::Shared => "SHARED",
+            Self::Exclusive => "EXCLUSIVE",
+        })
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -692,6 +726,9 @@ impl fmt::Display for AlterTableOperation {
                     value
                 )
             }
+            AlterTableOperation::Lock { equals, lock } => {
+                write!(f, "LOCK {}{}", if *equals { "= " } else { "" }, lock)
+            }
         }
     }
 }
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 8c40792..69f2e57 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -48,7 +48,7 @@ pub use self::dcl::{
 };
 pub use self::ddl::{
     AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation,
-    AlterTableAlgorithm, AlterTableOperation, AlterType, AlterTypeAddValue,
+    AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue,
     AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue,
     ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty,
     ConstraintCharacteristics, CreateConnector, CreateFunction, Deduplicate, DeferrableInitial,
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 0ee11f2..783248a 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1119,6 +1119,7 @@ impl Spanned for AlterTableOperation {
             AlterTableOperation::ResumeRecluster => Span::empty(),
             AlterTableOperation::Algorithm { .. } => Span::empty(),
             AlterTableOperation::AutoIncrement { value, .. } => value.span(),
+            AlterTableOperation::Lock { .. } => Span::empty(),
         }
     }
 }
diff --git a/src/keywords.rs b/src/keywords.rs
index 47da100..974230f 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -797,6 +797,7 @@ define_keywords!(
     SETS,
     SETTINGS,
     SHARE,
+    SHARED,
     SHARING,
     SHOW,
     SIGNED,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index e4c170e..0d8edc0 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8311,6 +8311,24 @@ impl<'a> Parser<'a> {
             AlterTableOperation::SuspendRecluster
         } else if self.parse_keywords(&[Keyword::RESUME, Keyword::RECLUSTER]) {
             AlterTableOperation::ResumeRecluster
+        } else if self.parse_keyword(Keyword::LOCK) {
+            let equals = self.consume_token(&Token::Eq);
+            let lock = match self.parse_one_of_keywords(&[
+                Keyword::DEFAULT,
+                Keyword::EXCLUSIVE,
+                Keyword::NONE,
+                Keyword::SHARED,
+            ]) {
+                Some(Keyword::DEFAULT) => AlterTableLock::Default,
+                Some(Keyword::EXCLUSIVE) => AlterTableLock::Exclusive,
+                Some(Keyword::NONE) => AlterTableLock::None,
+                Some(Keyword::SHARED) => AlterTableLock::Shared,
+                _ => self.expected(
+                    "DEFAULT, EXCLUSIVE, NONE or SHARED after LOCK [=]",
+                    self.peek_token(),
+                )?,
+            };
+            AlterTableOperation::Lock { equals, lock }
         } else if self.parse_keyword(Keyword::ALGORITHM) {
             let equals = self.consume_token(&Token::Eq);
             let algorithm = match self.parse_one_of_keywords(&[


Test patch:
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index a563359..b6287d9 100644
--- a/tests/sqlparser_mysql.rs
+++ b/tests/sqlparser_mysql.rs
@@ -2454,6 +2454,52 @@ fn parse_alter_table_with_algorithm() {
     mysql_and_generic().verified_stmt("ALTER TABLE `users` ALGORITHM = COPY");
 }
 
+#[test]
+fn parse_alter_table_with_lock() {
+    let sql = "ALTER TABLE tab LOCK = SHARED";
+    let expected_operation = AlterTableOperation::Lock {
+        equals: true,
+        lock: AlterTableLock::Shared,
+    };
+    let operation = alter_table_op(mysql_and_generic().verified_stmt(sql));
+    assert_eq!(expected_operation, operation);
+
+    let sql =
+        "ALTER TABLE users DROP COLUMN password_digest, LOCK = EXCLUSIVE, RENAME COLUMN name TO username";
+    let stmt = mysql_and_generic().verified_stmt(sql);
+    match stmt {
+        Statement::AlterTable { operations, .. } => {
+            assert_eq!(
+                operations,
+                vec![
+                    AlterTableOperation::DropColumn {
+                        column_name: Ident::new("password_digest"),
+                        if_exists: false,
+                        drop_behavior: None,
+                    },
+                    AlterTableOperation::Lock {
+                        equals: true,
+                        lock: AlterTableLock::Exclusive,
+                    },
+                    AlterTableOperation::RenameColumn {
+                        old_column_name: Ident::new("name"),
+                        new_column_name: Ident::new("username")
+                    },
+                ]
+            )
+        }
+        _ => panic!("Unexpected statement {stmt}"),
+    }
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK DEFAULT");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK SHARED");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK NONE");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK EXCLUSIVE");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK = DEFAULT");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK = SHARED");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK = NONE");
+    mysql_and_generic().verified_stmt("ALTER TABLE `users` LOCK = EXCLUSIVE");
+}
+
 #[test]
 fn parse_alter_table_auto_increment() {
     let sql = "ALTER TABLE tab AUTO_INCREMENT = 42";