diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java index a6e1c693e..94bc8393f 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpressionPartition.java @@ -9,13 +9,26 @@ */ package net.sf.jsqlparser.statement.alter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.create.table.PartitionBound; +import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; +import net.sf.jsqlparser.statement.select.PlainSelect; /** - * Internal subclass for partition maintenance operations within ALTER TABLE. Handles TRUNCATE, - * COALESCE, REORGANIZE, EXCHANGE, ANALYZE, CHECK, OPTIMIZE, REBUILD, REPAIR PARTITION, PARTITION - * BY, and REMOVE PARTITIONING. + * Structured model for partition operations within {@code ALTER TABLE}. + * + *

+ * MySQL partition names and definitions remain available through the inherited + * {@link #getPartitions()} and {@link #getPartitionDefinitions()} methods. This subtype adds + * structured access to the complete {@code PARTITION BY} clause, the table used by + * {@code EXCHANGE PARTITION}, and its validation mode. It also models PostgreSQL + * {@code ATTACH/DETACH PARTITION} operations. */ public class AlterExpressionPartition extends AlterExpression { @@ -23,9 +36,17 @@ public enum DetachMode { CONCURRENTLY, FINALIZE } + public enum ExchangeValidationMode { + WITH, WITHOUT + } + private Table partitionTable; private PartitionBound partitionBound; private DetachMode detachMode; + private TablePartitioning partitioning; + private Integer coalescePartitionCount; + private Table exchangeTable; + private ExchangeValidationMode exchangeValidationMode; public Table getPartitionTable() { return partitionTable; @@ -51,6 +72,136 @@ public void setDetachMode(DetachMode detachMode) { this.detachMode = detachMode; } + /** + * Returns the full MySQL partitioning clause for a {@link AlterOperation#PARTITION_BY} + * operation. + */ + public TablePartitioning getPartitioning() { + return partitioning; + } + + public void setPartitioning(TablePartitioning partitioning) { + this.partitioning = partitioning; + if (partitioning != null) { + setPartitionType(partitioning.getType() != null + ? partitioning.getType().toString() + : null); + setPartitionExpression(partitioning.getExpression() != null + ? partitioning.getExpression() + : partitioning.getExpressionList()); + if (partitioning.getColumns() != null) { + List partitionColumns = new ArrayList<>(); + partitioning.getColumns().forEach( + column -> partitionColumns.add(column.getFullyQualifiedName())); + setPartitionColumns(partitionColumns); + } else { + setPartitionColumns(null); + } + setPartitionDefinitions(partitioning.getPartitionDefinitions()); + } + } + + /** + * Convenience alias that names the inherited MySQL partition-name list explicitly. + */ + public List getPartitionNames() { + return getPartitions(); + } + + public void setPartitionNames(List partitionNames) { + setPartitions(partitionNames); + } + + /** + * Returns whether the operation targets every partition rather than named partitions. + */ + public boolean isAllPartitions() { + List partitionNames = getPartitionNames(); + return partitionNames != null && partitionNames.size() == 1 + && "ALL".equalsIgnoreCase(partitionNames.get(0)); + } + + public void setAllPartitions(boolean allPartitions) { + if (allPartitions) { + setPartitionNames(Collections.singletonList("ALL")); + } else if (isAllPartitions()) { + setPartitionNames(null); + } + } + + /** + * Returns the number of partitions added by a MySQL {@code COALESCE PARTITION} operation. + * Unlike the legacy primitive accessor, this is {@code null} when no count was supplied. + */ + public Integer getCoalescePartitionCount() { + return coalescePartitionCount; + } + + public void setCoalescePartitionCount(Integer coalescePartitionCount) { + this.coalescePartitionCount = coalescePartitionCount; + if (coalescePartitionCount != null) { + super.setCoalescePartitionNumber(coalescePartitionCount); + } + } + + @Override + public void setCoalescePartitionNumber(int coalescePartitionNumber) { + super.setCoalescePartitionNumber(coalescePartitionNumber); + this.coalescePartitionCount = coalescePartitionNumber; + } + + public Table getExchangeTable() { + return exchangeTable; + } + + public void setExchangeTable(Table exchangeTable) { + this.exchangeTable = exchangeTable; + super.setExchangePartitionTableName( + exchangeTable != null ? exchangeTable.getFullyQualifiedName() : null); + } + + @Override + public void setExchangePartitionTableName(String exchangePartitionTableName) { + super.setExchangePartitionTableName(exchangePartitionTableName); + this.exchangeTable = exchangePartitionTableName != null + ? new Table(exchangePartitionTableName) + : null; + } + + public ExchangeValidationMode getExchangeValidationMode() { + return exchangeValidationMode; + } + + public void setExchangeValidationMode(ExchangeValidationMode exchangeValidationMode) { + this.exchangeValidationMode = exchangeValidationMode; + super.setExchangePartitionWithValidation( + exchangeValidationMode == ExchangeValidationMode.WITH); + super.setExchangePartitionWithoutValidation( + exchangeValidationMode == ExchangeValidationMode.WITHOUT); + } + + @Override + public void setExchangePartitionWithValidation(boolean exchangePartitionWithValidation) { + super.setExchangePartitionWithValidation(exchangePartitionWithValidation); + if (exchangePartitionWithValidation) { + exchangeValidationMode = ExchangeValidationMode.WITH; + super.setExchangePartitionWithoutValidation(false); + } else if (exchangeValidationMode == ExchangeValidationMode.WITH) { + exchangeValidationMode = null; + } + } + + @Override + public void setExchangePartitionWithoutValidation(boolean exchangePartitionWithoutValidation) { + super.setExchangePartitionWithoutValidation(exchangePartitionWithoutValidation); + if (exchangePartitionWithoutValidation) { + exchangeValidationMode = ExchangeValidationMode.WITHOUT; + super.setExchangePartitionWithValidation(false); + } else if (exchangeValidationMode == ExchangeValidationMode.WITHOUT) { + exchangeValidationMode = null; + } + } + public AlterExpressionPartition withPartitionTable(Table partitionTable) { setPartitionTable(partitionTable); return this; @@ -66,18 +217,121 @@ public AlterExpressionPartition withDetachMode(DetachMode detachMode) { return this; } + @Override + public AlterExpressionPartition withOperation(AlterOperation operation) { + setOperation(operation); + return this; + } + + public AlterExpressionPartition withPartitioning(TablePartitioning partitioning) { + setPartitioning(partitioning); + return this; + } + + public AlterExpressionPartition withPartitionNames(List partitionNames) { + setPartitionNames(partitionNames); + return this; + } + + public AlterExpressionPartition withAllPartitions(boolean allPartitions) { + setAllPartitions(allPartitions); + return this; + } + + public AlterExpressionPartition withCoalescePartitionCount(Integer coalescePartitionCount) { + setCoalescePartitionCount(coalescePartitionCount); + return this; + } + + public AlterExpressionPartition withExchangeTable(Table exchangeTable) { + setExchangeTable(exchangeTable); + return this; + } + + public AlterExpressionPartition withExchangeValidationMode( + ExchangeValidationMode exchangeValidationMode) { + setExchangeValidationMode(exchangeValidationMode); + return this; + } + + public AlterExpressionPartition withPartitionDefinitions( + List partitionDefinitions) { + setPartitionDefinitions(partitionDefinitions); + return this; + } + + public AlterExpressionPartition addPartitionNames(String... partitionNames) { + List collection = + Optional.ofNullable(getPartitionNames()).orElseGet(ArrayList::new); + Collections.addAll(collection, partitionNames); + return withPartitionNames(collection); + } + + public AlterExpressionPartition addPartitionNames(Collection partitionNames) { + List collection = + Optional.ofNullable(getPartitionNames()).orElseGet(ArrayList::new); + collection.addAll(partitionNames); + return withPartitionNames(collection); + } + + public AlterExpressionPartition addPartitionDefinitions( + PartitionDefinition... partitionDefinitions) { + List collection = Optional.ofNullable(getPartitionDefinitions()) + .orElseGet(ArrayList::new); + Collections.addAll(collection, partitionDefinitions); + return withPartitionDefinitions(collection); + } + + public AlterExpressionPartition addPartitionDefinitions( + Collection partitionDefinitions) { + List collection = Optional.ofNullable(getPartitionDefinitions()) + .orElseGet(ArrayList::new); + collection.addAll(partitionDefinitions); + return withPartitionDefinitions(collection); + } + @Override protected void appendBody(StringBuilder b) { - if (getOperation() == AlterOperation.ATTACH_PARTITION) { - b.append("ATTACH PARTITION ").append(partitionTable).append(" ") - .append(partitionBound); - } else if (getOperation() == AlterOperation.DETACH_PARTITION) { - b.append("DETACH PARTITION ").append(partitionTable); - if (detachMode != null) { - b.append(" ").append(detachMode); - } - } else { - toStringPartition(b); + switch (getOperation()) { + case ADD_PARTITION: + b.append("ADD PARTITION ") + .append(PlainSelect.getStringList(getPartitionDefinitions(), true, true)); + break; + case DROP_PARTITION: + b.append("DROP PARTITION ") + .append(PlainSelect.getStringList(getPartitionNames())); + break; + case ATTACH_PARTITION: + b.append("ATTACH PARTITION ").append(partitionTable).append(" ") + .append(partitionBound); + break; + case DETACH_PARTITION: + b.append("DETACH PARTITION ").append(partitionTable); + if (detachMode != null) { + b.append(" ").append(detachMode); + } + break; + case PARTITION_BY: + if (partitioning != null) { + b.append(partitioning); + } else { + toStringPartition(b); + } + break; + case EXCHANGE_PARTITION: + if (exchangeTable != null) { + b.append("EXCHANGE PARTITION ").append(getPartitionNames().get(0)) + .append(" WITH TABLE ").append(exchangeTable); + if (exchangeValidationMode != null) { + b.append(" ").append(exchangeValidationMode).append(" VALIDATION"); + } + } else { + toStringPartition(b); + } + break; + default: + toStringPartition(b); + break; } } } diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java b/src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java index 2c8a9a77b..febc2c127 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java @@ -21,7 +21,7 @@ import net.sf.jsqlparser.statement.select.PlainSelect; /** - * MySQL table partitioning options used by {@code CREATE TABLE}. + * MySQL table partitioning options used by {@code CREATE TABLE} and {@code ALTER TABLE}. */ public class TablePartitioning implements Serializable { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 52994191b..c2816f034 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -13046,24 +13046,18 @@ void PartitionDefinitionOption(PartitionDefinition partitionDefinition): List PartitionNamesList() : { - Token tk; + String partitionName = null; List partitionNames = new ArrayList(); } { + partitionName = RelObjectName() { + partitionNames.add(partitionName); + } ( - tk = { - partitionNames.add(tk.image); - } - | - tk = { - partitionNames.add(tk.image); + LOOKAHEAD(2) "," partitionName = RelObjectName() { + partitionNames.add(partitionName); } - ( - LOOKAHEAD(2) "," tk = { - partitionNames.add(tk.image); - } - )* - ) + )* { return partitionNames; } @@ -13230,6 +13224,7 @@ AlterExpression AlterExpressionDrop(): ( ( { + alterExp = new AlterExpressionPartition(); alterExp.setOperation(AlterOperation.DROP_PARTITION); } partitions=PartitionNamesList() { @@ -13299,12 +13294,12 @@ AlterExpression AlterExpressionDrop(): */ AlterExpression AlterExpressionPartitionOp(): { - AlterExpression alterExp = new AlterExpressionPartition(); + AlterExpressionPartition alterExp = new AlterExpressionPartition(); Token tk; List partitions = null; List partitionDefinition = null; - List columnNames = null; - Expression exp = null; + Table exchangeTable = null; + TablePartitioning partitioning = null; } { ( @@ -13313,7 +13308,7 @@ AlterExpression AlterExpressionPartitionOp(): | LOOKAHEAD(2) tk= { alterExp.setOperation(AlterOperation.COALESCE_PARTITION); - alterExp.setCoalescePartitionNumber(Integer.valueOf(tk.image)); + alterExp.setCoalescePartitionCount(Integer.valueOf(tk.image)); } | LOOKAHEAD(2) @@ -13324,18 +13319,22 @@ AlterExpression AlterExpressionPartitionOp(): } | LOOKAHEAD(2) partitions=PartitionNamesList() - tk= + exchangeTable=Table() [ LOOKAHEAD(2) ( - { alterExp.setExchangePartitionWithValidation(true); } + { + alterExp.setExchangeValidationMode(AlterExpressionPartition.ExchangeValidationMode.WITH); + } | - { alterExp.setExchangePartitionWithoutValidation(false); } + { + alterExp.setExchangeValidationMode(AlterExpressionPartition.ExchangeValidationMode.WITHOUT); + } ) ] { alterExp.setOperation(AlterOperation.EXCHANGE_PARTITION); alterExp.setPartitions(partitions); - alterExp.setExchangePartitionTableName(tk.image); + alterExp.setExchangeTable(exchangeTable); } | LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.ANALYZE_PARTITION); } @@ -13355,14 +13354,10 @@ AlterExpression AlterExpressionPartitionOp(): | LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.REMOVE_PARTITIONING); } | - LOOKAHEAD(2) { alterExp.setOperation(AlterOperation.PARTITION_BY); } - { alterExp.setPartitionType("RANGE"); } - ( - "(" exp=Expression() ")" { alterExp.setPartitionExpression(exp); } - | - columnNames=ColumnsNamesList() { alterExp.setPartitionColumns(columnNames); } - ) - partitionDefinition=PartitionDefinitions() { alterExp.setPartitionDefinitions(partitionDefinition); } + LOOKAHEAD(2) partitioning=CreateTablePartitioning() { + alterExp.setOperation(AlterOperation.PARTITION_BY); + alterExp.setPartitioning(partitioning); + } ) { return alterExp; } } @@ -13464,6 +13459,7 @@ AlterExpression AlterExpressionAddAlterModify(): | LOOKAHEAD(3) ( { + alterExp = new AlterExpressionPartition(); alterExp.setOperation(AlterOperation.ADD_PARTITION); } partitionDefinition=PartitionDefinitions() { diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/MySQLAlterTablePartitionTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/MySQLAlterTablePartitionTest.java new file mode 100644 index 000000000..75dbb7aed --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/alter/MySQLAlterTablePartitionTest.java @@ -0,0 +1,183 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.alter; + +import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.stream.Stream; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.create.table.PartitionDefinition; +import net.sf.jsqlparser.statement.create.table.TablePartitioning; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +/** MySQL 8.4 {@code ALTER TABLE} partition syntax verified against the MySQL manual. */ +public class MySQLAlterTablePartitionTest { + + private static final List MYSQL84_PARTITION_OPERATIONS = List.of( + "ALTER TABLE sales ADD PARTITION " + + "(PARTITION p2027 VALUES LESS THAN (2028) ENGINE = InnoDB)", + "ALTER TABLE sales DROP PARTITION p2023, `p2024`", + "ALTER TABLE sales DISCARD PARTITION p2024 TABLESPACE", + "ALTER TABLE sales IMPORT PARTITION ALL TABLESPACE", + "ALTER TABLE sales TRUNCATE PARTITION ALL", + "ALTER TABLE sales COALESCE PARTITION 2", + "ALTER TABLE sales REORGANIZE PARTITION p0, p1 INTO " + + "(PARTITION p_low VALUES LESS THAN (100), " + + "PARTITION p_high VALUES LESS THAN MAXVALUE)", + "ALTER TABLE sales EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024", + "ALTER TABLE sales EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024 " + + "WITH VALIDATION", + "ALTER TABLE sales EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024 " + + "WITHOUT VALIDATION", + "ALTER TABLE sales ANALYZE PARTITION p0, p1", + "ALTER TABLE sales CHECK PARTITION ALL", + "ALTER TABLE sales OPTIMIZE PARTITION p0", + "ALTER TABLE sales REBUILD PARTITION p0", + "ALTER TABLE sales REPAIR PARTITION ALL", + "ALTER TABLE sales REMOVE PARTITIONING"); + + private static final List MYSQL84_PARTITIONING_CLAUSES = List.of( + "ALTER TABLE sales PARTITION BY HASH (id) PARTITIONS 8", + "ALTER TABLE sales PARTITION BY LINEAR KEY ALGORITHM = 1 (id) PARTITIONS 4", + "ALTER TABLE sales PARTITION BY RANGE (YEAR(created_at)) " + + "SUBPARTITION BY HASH (id) SUBPARTITIONS 2 " + + "(PARTITION p2026 VALUES LESS THAN (2027), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE)", + "ALTER TABLE sales PARTITION BY LIST COLUMNS (region, tier) " + + "(PARTITION p_eu VALUES IN (('eu', 1), ('eu', 2)), " + + "PARTITION p_us VALUES IN (('us', 1)))"); + + private static Stream mysql84PartitionOperations() { + return MYSQL84_PARTITION_OPERATIONS.stream(); + } + + private static Stream mysql84PartitioningClauses() { + return MYSQL84_PARTITIONING_CLAUSES.stream(); + } + + @ParameterizedTest + @MethodSource("mysql84PartitionOperations") + public void testPartitionOperationsUseStructuredSubtype(String sql) + throws JSQLParserException { + assertInstanceOf(AlterExpressionPartition.class, parseExpression(sql)); + assertSqlCanBeParsedAndDeparsed(sql); + } + + @ParameterizedTest + @MethodSource("mysql84PartitioningClauses") + public void testPartitionByUsesCreateTablePartitioningModel(String sql) + throws JSQLParserException { + AlterExpressionPartition expression = parseExpression(sql); + assertEquals(AlterOperation.PARTITION_BY, expression.getOperation()); + assertNotNull(expression.getPartitioning()); + assertSqlCanBeParsedAndDeparsed(sql); + } + + @Test + public void testAddPartitionDefinitionAst() throws JSQLParserException { + AlterExpressionPartition expression = parseExpression( + "ALTER TABLE sales ADD PARTITION " + + "(PARTITION p2027 VALUES LESS THAN (2028) ENGINE = InnoDB " + + "COMMENT = 'archive')"); + + assertEquals(AlterOperation.ADD_PARTITION, expression.getOperation()); + PartitionDefinition definition = expression.getPartitionDefinitions().get(0); + assertEquals("p2027", definition.getPartitionName()); + assertEquals(PartitionDefinition.ValueOperator.LESS_THAN, + definition.getValueOperator()); + assertEquals(List.of("2028"), definition.getValues()); + assertEquals("InnoDB", definition.getStorageEngine()); + assertEquals("'archive'", definition.getComment()); + } + + @Test + public void testAllPartitionsAndCoalesceCountAst() throws JSQLParserException { + AlterExpressionPartition all = + parseExpression("ALTER TABLE sales TRUNCATE PARTITION ALL"); + assertTrue(all.isAllPartitions()); + assertEquals(List.of("ALL"), all.getPartitionNames()); + assertNull(all.getCoalescePartitionCount()); + + AlterExpressionPartition coalesce = + parseExpression("ALTER TABLE sales COALESCE PARTITION 2"); + assertFalse(coalesce.isAllPartitions()); + assertEquals(2, coalesce.getCoalescePartitionCount()); + assertEquals(2, coalesce.getCoalescePartitionNumber()); + } + + @Test + public void testExchangePartitionAst() throws JSQLParserException { + AlterExpressionPartition expression = parseExpression( + "ALTER TABLE sales EXCHANGE PARTITION `p0` WITH TABLE " + + "`archive`.`sales_2024` WITHOUT VALIDATION"); + + assertEquals(List.of("`p0`"), expression.getPartitionNames()); + assertEquals("`archive`.`sales_2024`", + expression.getExchangeTable().getFullyQualifiedName()); + assertEquals(AlterExpressionPartition.ExchangeValidationMode.WITHOUT, + expression.getExchangeValidationMode()); + assertTrue(expression.isExchangePartitionWithoutValidation()); + assertFalse(expression.isExchangePartitionWithValidation()); + assertEquals("`archive`.`sales_2024`", expression.getExchangePartitionTableName()); + assertSqlCanBeParsedAndDeparsed("ALTER TABLE sales " + expression); + } + + @Test + public void testPartitionByAst() throws JSQLParserException { + AlterExpressionPartition expression = parseExpression( + "ALTER TABLE sales PARTITION BY LIST COLUMNS (region, tier) " + + "(PARTITION p_eu VALUES IN (('eu', 1), ('eu', 2)), " + + "PARTITION p_us VALUES IN (('us', 1)))"); + + TablePartitioning partitioning = expression.getPartitioning(); + assertEquals(TablePartitioning.Type.LIST, partitioning.getType()); + assertTrue(partitioning.isColumnsSyntax()); + assertEquals("region", partitioning.getColumns().get(0).getColumnName()); + assertEquals("tier", partitioning.getColumns().get(1).getColumnName()); + assertEquals(PartitionDefinition.ValueOperator.IN, + partitioning.getPartitionDefinitions().get(0).getValueOperator()); + + // Legacy accessors remain populated for source compatibility. + assertEquals("LIST", expression.getPartitionType()); + assertEquals(List.of("region", "tier"), expression.getPartitionColumns()); + assertEquals(partitioning.getPartitionDefinitions(), expression.getPartitionDefinitions()); + } + + @Test + public void testProgrammaticConstruction() { + AlterExpressionPartition expression = new AlterExpressionPartition() + .withOperation(AlterOperation.EXCHANGE_PARTITION) + .addPartitionNames("p0") + .withExchangeTable(new Table("archive", "sales_2024")) + .withExchangeValidationMode( + AlterExpressionPartition.ExchangeValidationMode.WITHOUT); + + assertEquals("EXCHANGE PARTITION p0 WITH TABLE archive.sales_2024 WITHOUT VALIDATION", + expression.toString()); + } + + private static AlterExpressionPartition parseExpression(String sql) + throws JSQLParserException { + Alter alter = (Alter) CCJSqlParserUtil.parse(sql); + return assertInstanceOf(AlterExpressionPartition.class, + alter.getAlterExpressions().get(0)); + } +}