From de8bc2c89c441626a1caa89e2e5baa2878625384 Mon Sep 17 00:00:00 2001 From: minleejae Date: Wed, 2 Apr 2025 14:02:43 +0900 Subject: [PATCH] feat: support MySQL ALTER TABLE ... PARTITION BY syntax --- .../statement/alter/AlterExpression.java | 52 +++++++++ .../statement/alter/AlterOperation.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 31 +++++- .../jsqlparser/statement/alter/AlterTest.java | 105 ++++++++++++++++++ 4 files changed, 184 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java index 387790eb2..5f0c09d7d 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.statement.alter; +import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.ReferentialAction; import net.sf.jsqlparser.statement.ReferentialAction.Action; import net.sf.jsqlparser.statement.ReferentialAction.Type; @@ -75,6 +76,11 @@ public class AlterExpression implements Serializable { private boolean useIfNotExists = false; + private String partitionType; + private Expression partitionExpression; + private List partitionColumns; + + public Index getOldIndex() { return oldIndex; } @@ -494,6 +500,30 @@ public AlterExpression withUserIfNotExists(boolean userIfNotExists) { return this; } + public void setPartitionType(String partitionType) { + this.partitionType = partitionType; + } + + public String getPartitionType() { + return partitionType; + } + + public void setPartitionExpression(Expression partitionExpression) { + this.partitionExpression = partitionExpression; + } + + public Expression getPartitionExpression() { + return partitionExpression; + } + + public void setPartitionColumns(List partitionColumns) { + this.partitionColumns = partitionColumns; + } + + public List getPartitionColumns() { + return partitionColumns; + } + @Override @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength", "PMD.SwitchStmtsShouldHaveDefault"}) @@ -601,6 +631,28 @@ public String toString() { } else if (operation == AlterOperation.TRUNCATE_PARTITION && partitions != null) { b.append("TRUNCATE PARTITION ").append(PlainSelect.getStringList(partitions)); + } else if (operation == AlterOperation.PARTITION_BY) { + b.append("PARTITION BY ").append(partitionType).append(" "); + if (partitionExpression != null) { + b.append("(").append(partitionExpression).append(") "); + } else if (partitionColumns != null && !partitionColumns.isEmpty()) { + b.append("COLUMNS(").append(String.join(", ", partitionColumns)).append(") "); + } + b.append("("); + for (int i = 0; i < partitionDefinitions.size(); i++) { + PartitionDefinition partition = partitionDefinitions.get(i); + b.append("PARTITION ").append(partition.getPartitionName()) + .append(" ").append(partition.getPartitionOperation()) + .append(" (").append(PlainSelect.getStringList(partition.getValues())) + .append(")"); + if (partition.getStorageEngine() != null) { + b.append(" ENGINE = ").append(partition.getStorageEngine()); + } + if (i < partitionDefinitions.size() - 1) { + b.append(", "); + } + } + b.append(")"); } else { if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) { b.append("COMMENT =").append(" "); diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java index 523b62793..a9d955c36 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterOperation.java @@ -10,7 +10,7 @@ package net.sf.jsqlparser.statement.alter; public enum AlterOperation { - ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, TRUNCATE_PARTITION, SET_TABLE_OPTION, ENGINE, FORCE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS; + ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, PARTITION_BY, TRUNCATE_PARTITION, SET_TABLE_OPTION, ENGINE, FORCE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS; public static AlterOperation from(String operation) { return Enum.valueOf(AlterOperation.class, operation.toUpperCase()); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 9c774070f..88c369bba 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -7638,6 +7638,7 @@ List PartitionDefinitions(): String partitionName = null; String partitionOperation = null; String storageEngine = null; + Expression exp = null; } { "(" @@ -7651,10 +7652,9 @@ List PartitionDefinitions(): ( ( - "(" - ( tk= { values.add(tk.image); } - | tk= { values.add(tk.image); } - [ "," ] )* ")" + "(" exp = Expression() ")"{ + values.add(exp.toString()); + } | { values.add("MAXVALUE"); } ) { partitionOperation = "VALUES LESS THAN"; @@ -7673,7 +7673,6 @@ List PartitionDefinitions(): } } - List PartitionNamesList() : { Token tk; @@ -8134,6 +8133,28 @@ AlterExpression AlterExpression(): } } ) + | + LOOKAHEAD(2) ( + { + alterExp.setOperation(AlterOperation.PARTITION_BY); + } + { + alterExp.setPartitionType("RANGE"); + Expression exp = null; + } + ( + "(" exp=Expression() ")" { + alterExp.setPartitionExpression(exp); + } + | + columnNames=ColumnsNamesList() { + alterExp.setPartitionColumns(columnNames); + } + ) + partitionDefinition=PartitionDefinitions() { + alterExp.setPartitionDefinitions(partitionDefinition); + } + ) | LOOKAHEAD(2) ( (( {alterExp.setOperation(AlterOperation.RENAME_INDEX);} diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index b532f46bd..7ebcd7e1b 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -1500,4 +1500,109 @@ public void testAlterTableKeys() throws JSQLParserException { AlterExpression alterExpEnable = alterEnable.getAlterExpressions().get(0); assertEquals(AlterOperation.ENABLE_KEYS, alterExpEnable.getOperation()); } + + @Test + public void testAlterTablePartitionByRangeColumns() throws JSQLParserException { + String sql = "ALTER TABLE `payment_lock` " + + "PARTITION BY RANGE COLUMNS(`created_at`) (" + + "PARTITION p20210217 VALUES LESS THAN ('20210218') ENGINE = InnoDB, " + + "PARTITION p20210218 VALUES LESS THAN ('20210219') ENGINE = InnoDB);"; + Statement stmt = CCJSqlParserUtil.parse(sql); + assertInstanceOf(Alter.class, stmt); + Alter alter = (Alter) stmt; + assertEquals("`payment_lock`", alter.getTable().getFullyQualifiedName()); + + List alterExpressions = alter.getAlterExpressions(); + assertNotNull(alterExpressions); + assertEquals(1, alterExpressions.size()); + + AlterExpression partitionExp = alterExpressions.get(0); + assertEquals(AlterOperation.PARTITION_BY, partitionExp.getOperation()); + List partitions = partitionExp.getPartitionDefinitions(); + assertNotNull(partitions); + assertEquals(2, partitions.size()); + + assertEquals("p20210217", partitions.get(0).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(0).getPartitionOperation()); + assertEquals(Collections.singletonList("'20210218'"), partitions.get(0).getValues()); + + assertEquals("p20210218", partitions.get(1).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(1).getPartitionOperation()); + assertEquals(Collections.singletonList("'20210219'"), partitions.get(1).getValues()); + + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + public void testAlterTablePartitionByRangeUnixTimestamp() throws JSQLParserException { + String sql = "ALTER TABLE `test`.`pipeline_service_metadata_history` " + + "PARTITION BY RANGE (FLOOR(UNIX_TIMESTAMP(requested_at))) (" + + "PARTITION p202104 VALUES LESS THAN (UNIX_TIMESTAMP('2021-05-01 00:00:00')) ENGINE = InnoDB, " + + + "PARTITION p202105 VALUES LESS THAN (UNIX_TIMESTAMP('2021-06-01 00:00:00')) ENGINE = InnoDB);"; + Statement stmt = CCJSqlParserUtil.parse(sql); + assertInstanceOf(Alter.class, stmt); + Alter alter = (Alter) stmt; + assertEquals("`test`.`pipeline_service_metadata_history`", + alter.getTable().getFullyQualifiedName()); + + List alterExpressions = alter.getAlterExpressions(); + assertNotNull(alterExpressions); + assertEquals(1, alterExpressions.size()); + + AlterExpression partitionExp = alterExpressions.get(0); + assertEquals(AlterOperation.PARTITION_BY, partitionExp.getOperation()); + List partitions = partitionExp.getPartitionDefinitions(); + assertNotNull(partitions); + assertEquals(2, partitions.size()); + + assertEquals("p202104", partitions.get(0).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(0).getPartitionOperation()); + assertEquals(Collections.singletonList("UNIX_TIMESTAMP('2021-05-01 00:00:00')"), + partitions.get(0).getValues()); + + assertEquals("p202105", partitions.get(1).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(1).getPartitionOperation()); + assertEquals(Collections.singletonList("UNIX_TIMESTAMP('2021-06-01 00:00:00')"), + partitions.get(1).getValues()); + + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + public void testAlterTablePartitionByRangeUnixTimestamp2() throws JSQLParserException { + String sql = "ALTER TABLE MP_MNEWS.PUR_MNEWS_CONTS " + + "PARTITION BY RANGE (UNIX_TIMESTAMP(REG_DATE_TS)) (" + + "PARTITION p202007 VALUES LESS THAN (1596207600) ENGINE = InnoDB, " + + "PARTITION p202008 VALUES LESS THAN (1598886000) ENGINE = InnoDB, " + + "PARTITION p202009 VALUES LESS THAN (1601478000) ENGINE = InnoDB);"; + Statement stmt = CCJSqlParserUtil.parse(sql); + assertInstanceOf(Alter.class, stmt); + Alter alter = (Alter) stmt; + assertEquals("MP_MNEWS.PUR_MNEWS_CONTS", alter.getTable().getFullyQualifiedName()); + + List alterExpressions = alter.getAlterExpressions(); + assertNotNull(alterExpressions); + assertEquals(1, alterExpressions.size()); + + AlterExpression partitionExp = alterExpressions.get(0); + assertEquals(AlterOperation.PARTITION_BY, partitionExp.getOperation()); + List partitions = partitionExp.getPartitionDefinitions(); + assertNotNull(partitions); + assertEquals(3, partitions.size()); + + assertEquals("p202007", partitions.get(0).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(0).getPartitionOperation()); + assertEquals(Collections.singletonList("1596207600"), partitions.get(0).getValues()); + + assertEquals("p202008", partitions.get(1).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(1).getPartitionOperation()); + assertEquals(Collections.singletonList("1598886000"), partitions.get(1).getValues()); + + assertEquals("p202009", partitions.get(2).getPartitionName()); + assertEquals("VALUES LESS THAN", partitions.get(2).getPartitionOperation()); + assertEquals(Collections.singletonList("1601478000"), partitions.get(2).getValues()); + + assertSqlCanBeParsedAndDeparsed(sql); + } }