diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java index d2be0e026..acbe0b718 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java @@ -36,6 +36,7 @@ public class CreateTable implements Statement { private boolean selectParenthesis; private boolean ifNotExists = false; private boolean orReplace = false; + private TablePartitioning partitioning; private RowMovement rowMovement; @@ -148,6 +149,14 @@ public void setOrReplace(boolean orReplace) { this.orReplace = orReplace; } + public TablePartitioning getPartitioning() { + return partitioning; + } + + public void setPartitioning(TablePartitioning partitioning) { + this.partitioning = partitioning; + } + public boolean isSelectParenthesis() { return selectParenthesis; } @@ -216,6 +225,9 @@ private void appendTableOptions(StringBuilder b) { if (options != null && options.length() > 0) { b.append(" ").append(options); } + if (partitioning != null) { + b.append(" ").append(partitioning); + } } private void appendTableProperties(StringBuilder b) { @@ -272,6 +284,11 @@ public CreateTable withIfNotExists(boolean ifNotExists) { return this; } + public CreateTable withPartitioning(TablePartitioning partitioning) { + this.setPartitioning(partitioning); + return this; + } + public CreateTable withRowMovement(RowMovement rowMovement) { this.setRowMovement(rowMovement); return this; diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/PartitionDefinition.java b/src/main/java/net/sf/jsqlparser/statement/create/table/PartitionDefinition.java index 24d15dfd0..0f654326f 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/PartitionDefinition.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/PartitionDefinition.java @@ -14,15 +14,51 @@ import net.sf.jsqlparser.statement.select.PlainSelect; public class PartitionDefinition implements Serializable { + + public enum ValueOperator { + LESS_THAN("VALUES LESS THAN"), IN("VALUES IN"); + + private final String sql; + + ValueOperator(String sql) { + this.sql = sql; + } + + @Override + public String toString() { + return sql; + } + } + private String partitionName; private String partitionOperation; private List values; private String storageEngine; + private ValueOperator valueOperator; + private boolean maxValue; + private boolean subPartition; + private boolean storageKeyword; + private boolean storageEngineUseEquals = true; + private String comment; + private boolean commentUseEquals; + private String dataDirectory; + private boolean dataDirectoryUseEquals; + private String indexDirectory; + private boolean indexDirectoryUseEquals; + private String maxRows; + private boolean maxRowsUseEquals; + private String minRows; + private boolean minRowsUseEquals; + private String tablespace; + private boolean tablespaceUseEquals; + private List subPartitionDefinitions; + + public PartitionDefinition() {} public PartitionDefinition(String partitionName, String partitionOperation, List values, String storageEngine) { this.partitionName = partitionName; - this.partitionOperation = partitionOperation; + setPartitionOperation(partitionOperation); this.values = values; this.storageEngine = storageEngine; } @@ -41,6 +77,13 @@ public String getPartitionOperation() { public void setPartitionOperation(String partitionOperation) { this.partitionOperation = partitionOperation; + if (ValueOperator.LESS_THAN.toString().equalsIgnoreCase(partitionOperation)) { + valueOperator = ValueOperator.LESS_THAN; + } else if (ValueOperator.IN.toString().equalsIgnoreCase(partitionOperation)) { + valueOperator = ValueOperator.IN; + } else { + valueOperator = null; + } } public List getValues() { @@ -59,16 +102,186 @@ public void setStorageEngine(String storageEngine) { this.storageEngine = storageEngine; } + public ValueOperator getValueOperator() { + return valueOperator; + } + + public void setValueOperator(ValueOperator valueOperator) { + this.valueOperator = valueOperator; + this.partitionOperation = valueOperator != null ? valueOperator.toString() : null; + } + + public boolean isMaxValue() { + return maxValue; + } + + public void setMaxValue(boolean maxValue) { + this.maxValue = maxValue; + } + + public boolean isSubPartition() { + return subPartition; + } + + public void setSubPartition(boolean subPartition) { + this.subPartition = subPartition; + } + + public boolean isStorageKeyword() { + return storageKeyword; + } + + public void setStorageKeyword(boolean storageKeyword) { + this.storageKeyword = storageKeyword; + } + + public boolean isStorageEngineUseEquals() { + return storageEngineUseEquals; + } + + public void setStorageEngineUseEquals(boolean storageEngineUseEquals) { + this.storageEngineUseEquals = storageEngineUseEquals; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public boolean isCommentUseEquals() { + return commentUseEquals; + } + + public void setCommentUseEquals(boolean commentUseEquals) { + this.commentUseEquals = commentUseEquals; + } + + public String getDataDirectory() { + return dataDirectory; + } + + public void setDataDirectory(String dataDirectory) { + this.dataDirectory = dataDirectory; + } + + public boolean isDataDirectoryUseEquals() { + return dataDirectoryUseEquals; + } + + public void setDataDirectoryUseEquals(boolean dataDirectoryUseEquals) { + this.dataDirectoryUseEquals = dataDirectoryUseEquals; + } + + public String getIndexDirectory() { + return indexDirectory; + } + + public void setIndexDirectory(String indexDirectory) { + this.indexDirectory = indexDirectory; + } + + public boolean isIndexDirectoryUseEquals() { + return indexDirectoryUseEquals; + } + + public void setIndexDirectoryUseEquals(boolean indexDirectoryUseEquals) { + this.indexDirectoryUseEquals = indexDirectoryUseEquals; + } + + public String getMaxRows() { + return maxRows; + } + + public void setMaxRows(String maxRows) { + this.maxRows = maxRows; + } + + public boolean isMaxRowsUseEquals() { + return maxRowsUseEquals; + } + + public void setMaxRowsUseEquals(boolean maxRowsUseEquals) { + this.maxRowsUseEquals = maxRowsUseEquals; + } + + public String getMinRows() { + return minRows; + } + + public void setMinRows(String minRows) { + this.minRows = minRows; + } + + public boolean isMinRowsUseEquals() { + return minRowsUseEquals; + } + + public void setMinRowsUseEquals(boolean minRowsUseEquals) { + this.minRowsUseEquals = minRowsUseEquals; + } + + public String getTablespace() { + return tablespace; + } + + public void setTablespace(String tablespace) { + this.tablespace = tablespace; + } + + public boolean isTablespaceUseEquals() { + return tablespaceUseEquals; + } + + public void setTablespaceUseEquals(boolean tablespaceUseEquals) { + this.tablespaceUseEquals = tablespaceUseEquals; + } + + public List getSubPartitionDefinitions() { + return subPartitionDefinitions; + } + + public void setSubPartitionDefinitions( + List subPartitionDefinitions) { + this.subPartitionDefinitions = subPartitionDefinitions; + } + @Override public String toString() { StringBuilder b = new StringBuilder(); - b.append("PARTITION ").append(partitionName) - .append(" ").append(partitionOperation) - .append(" (").append(PlainSelect.getStringList(values)) - .append(")"); + b.append(subPartition ? "SUBPARTITION " : "PARTITION ").append(partitionName); + if (partitionOperation != null) { + b.append(" ").append(partitionOperation); + if (maxValue) { + b.append(" MAXVALUE"); + } else { + b.append(" (").append(PlainSelect.getStringList(values)).append(")"); + } + } if (storageEngine != null) { - b.append(" ENGINE = ").append(storageEngine); + if (storageKeyword) { + b.append(" STORAGE"); + } + appendOption(b, "ENGINE", storageEngine, storageEngineUseEquals); + } + appendOption(b, "COMMENT", comment, commentUseEquals); + appendOption(b, "DATA DIRECTORY", dataDirectory, dataDirectoryUseEquals); + appendOption(b, "INDEX DIRECTORY", indexDirectory, indexDirectoryUseEquals); + appendOption(b, "MAX_ROWS", maxRows, maxRowsUseEquals); + appendOption(b, "MIN_ROWS", minRows, minRowsUseEquals); + appendOption(b, "TABLESPACE", tablespace, tablespaceUseEquals); + if (subPartitionDefinitions != null && !subPartitionDefinitions.isEmpty()) { + b.append(" ").append(PlainSelect.getStringList(subPartitionDefinitions, true, true)); } return b.toString(); } + + private static void appendOption(StringBuilder builder, String name, String value, + boolean useEquals) { + if (value != null) { + builder.append(" ").append(name).append(useEquals ? " = " : " ").append(value); + } + } } 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 new file mode 100644 index 000000000..fa0ce42c0 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/TablePartitioning.java @@ -0,0 +1,280 @@ +/*- + * #%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.create.table; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.select.PlainSelect; + +/** + * MySQL table partitioning options used by {@code CREATE TABLE}. + */ +public class TablePartitioning implements Serializable { + + public enum Type { + HASH, KEY, RANGE, LIST + } + + private Type type; + private boolean linear; + private boolean columnsSyntax; + private Expression expression; + private ExpressionList columns; + private Integer algorithm; + private boolean algorithmUseEquals; + private Long partitions; + private TablePartitioning subPartitioning; + private List partitionDefinitions; + private List partitionOptions; + + public TablePartitioning() {} + + public TablePartitioning(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public boolean isLinear() { + return linear; + } + + public void setLinear(boolean linear) { + this.linear = linear; + } + + public boolean isColumnsSyntax() { + return columnsSyntax; + } + + public void setColumnsSyntax(boolean columnsSyntax) { + this.columnsSyntax = columnsSyntax; + } + + public Expression getExpression() { + return expression; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public ExpressionList getColumns() { + return columns; + } + + public void setColumns(ExpressionList columns) { + this.columns = columns; + } + + public Integer getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(Integer algorithm) { + this.algorithm = algorithm; + } + + public boolean isAlgorithmUseEquals() { + return algorithmUseEquals; + } + + public void setAlgorithmUseEquals(boolean algorithmUseEquals) { + this.algorithmUseEquals = algorithmUseEquals; + } + + public Long getPartitions() { + return partitions; + } + + public void setPartitions(Long partitions) { + this.partitions = partitions; + } + + public TablePartitioning getSubPartitioning() { + return subPartitioning; + } + + public void setSubPartitioning(TablePartitioning subPartitioning) { + this.subPartitioning = subPartitioning; + } + + public List getPartitionDefinitions() { + return partitionDefinitions; + } + + public void setPartitionDefinitions(List partitionDefinitions) { + this.partitionDefinitions = partitionDefinitions; + } + + public List getPartitionOptions() { + return partitionOptions; + } + + public void setPartitionOptions(List partitionOptions) { + this.partitionOptions = partitionOptions; + } + + public TablePartitioning withType(Type type) { + setType(type); + return this; + } + + public TablePartitioning withLinear(boolean linear) { + setLinear(linear); + return this; + } + + public TablePartitioning withColumnsSyntax(boolean columnsSyntax) { + setColumnsSyntax(columnsSyntax); + return this; + } + + public TablePartitioning withExpression(Expression expression) { + setExpression(expression); + return this; + } + + public TablePartitioning withColumns(ExpressionList columns) { + setColumns(columns); + return this; + } + + public TablePartitioning withAlgorithm(Integer algorithm) { + setAlgorithm(algorithm); + return this; + } + + public TablePartitioning withAlgorithmUseEquals(boolean algorithmUseEquals) { + setAlgorithmUseEquals(algorithmUseEquals); + return this; + } + + public TablePartitioning withPartitions(Long partitions) { + setPartitions(partitions); + return this; + } + + public TablePartitioning withSubPartitioning(TablePartitioning subPartitioning) { + setSubPartitioning(subPartitioning); + return this; + } + + public TablePartitioning withPartitionDefinitions( + List partitionDefinitions) { + setPartitionDefinitions(partitionDefinitions); + return this; + } + + public TablePartitioning withPartitionOptions(List partitionOptions) { + setPartitionOptions(partitionOptions); + return this; + } + + public TablePartitioning addColumns(Column... columns) { + ExpressionList collection = + Optional.ofNullable(getColumns()).orElseGet(ExpressionList::new); + Collections.addAll(collection, columns); + return withColumns(collection); + } + + public TablePartitioning addColumns(Collection columns) { + ExpressionList collection = + Optional.ofNullable(getColumns()).orElseGet(ExpressionList::new); + collection.addAll(columns); + return withColumns(collection); + } + + public TablePartitioning addPartitionDefinitions( + PartitionDefinition... partitionDefinitions) { + List collection = Optional.ofNullable(getPartitionDefinitions()) + .orElseGet(ArrayList::new); + Collections.addAll(collection, partitionDefinitions); + return withPartitionDefinitions(collection); + } + + public TablePartitioning addPartitionDefinitions( + Collection partitionDefinitions) { + List collection = Optional.ofNullable(getPartitionDefinitions()) + .orElseGet(ArrayList::new); + collection.addAll(partitionDefinitions); + return withPartitionDefinitions(collection); + } + + public TablePartitioning addPartitionOptions(String... partitionOptions) { + List collection = + Optional.ofNullable(getPartitionOptions()).orElseGet(ArrayList::new); + Collections.addAll(collection, partitionOptions); + return withPartitionOptions(collection); + } + + public TablePartitioning addPartitionOptions(Collection partitionOptions) { + List collection = + Optional.ofNullable(getPartitionOptions()).orElseGet(ArrayList::new); + collection.addAll(partitionOptions); + return withPartitionOptions(collection); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("PARTITION BY "); + appendMethod(builder); + if (partitions != null) { + builder.append(" PARTITIONS ").append(partitions); + } + if (subPartitioning != null) { + builder.append(" SUBPARTITION BY "); + subPartitioning.appendMethod(builder); + if (subPartitioning.getPartitions() != null) { + builder.append(" SUBPARTITIONS ").append(subPartitioning.getPartitions()); + } + } + if (partitionDefinitions != null && !partitionDefinitions.isEmpty()) { + builder.append(" ") + .append(PlainSelect.getStringList(partitionDefinitions, true, true)); + } + if (partitionOptions != null && !partitionOptions.isEmpty()) { + builder.append(" ").append(PlainSelect.getStringList(partitionOptions, false, false)); + } + return builder.toString(); + } + + private void appendMethod(StringBuilder builder) { + if (linear) { + builder.append("LINEAR "); + } + builder.append(type); + if (algorithm != null) { + builder.append(" ALGORITHM"); + builder.append(algorithmUseEquals ? " = " : " ").append(algorithm); + } + if (columnsSyntax) { + builder.append(" COLUMNS"); + } + if (expression != null) { + builder.append(" (").append(expression).append(")"); + } else if (columns != null) { + builder.append(" ").append(PlainSelect.getStringList(columns, true, true)); + } + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java index 1d9fc85f3..391b08889 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java @@ -96,6 +96,9 @@ public void deParse(CreateTable createTable) { if (!"".equals(params)) { builder.append(' ').append(params); } + if (createTable.getPartitioning() != null) { + builder.append(' ').append(createTable.getPartitioning()); + } if (createTable.getRowMovement() != null) { builder.append(' ').append(createTable.getRowMovement().getMode().toString()) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index d52b7a9c2..a400d926b 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -1429,6 +1429,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1515,7 +1516,9 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1534,10 +1537,12 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1579,6 +1584,7 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= @@ -1649,11 +1655,14 @@ String NonReservedWord() : | tk= | tk= | tk= + | tk= | tk= | tk= | tk= | tk= | tk= + | tk= + | tk= | tk= | tk= | tk= @@ -11443,6 +11452,7 @@ CreateTable CreateTable(boolean isUsingOrReplace): RowMovement rowMovement = null; String tableColumn = null; List columns = new ArrayList(); + TablePartitioning partitioning = null; } { { createTable.setOrReplace(isUsingOrReplace);} @@ -11484,7 +11494,10 @@ CreateTable CreateTable(boolean isUsingOrReplace): ) ) ] - ( LOOKAHEAD(2, { getToken(1).kind != K_AS }) parameter=CreateParameter() { tableOptions.addAll(parameter); } )* + ( LOOKAHEAD(2, { getToken(1).kind != K_AS + && !(getToken(1).kind == K_PARTITION && getToken(2).kind == K_BY) }) + parameter=CreateParameter() { tableOptions.addAll(parameter); } )* + [ partitioning=CreateTablePartitioning() { createTable.setPartitioning(partitioning); } ] // see https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_7002.htm#i2126725 // table properties , all these are optional @@ -11973,6 +11986,9 @@ List CreateParameter(): // "WITH PARSER" option, and the FULLTEXT / SPATIAL index types of CREATE INDEX. | tk= | tk= | tk= | tk= | tk= | tk= | tk= + | tk= | tk= | tk= | tk= + | tk= | tk= | tk= + | tk= | tk= | tk="=" ) { param.add(tk.image); } @@ -12437,49 +12453,273 @@ void IndexOption(List list) : ) } -List PartitionDefinitions(): +TablePartitioning CreateTablePartitioning(): { - Token tk; - List partitionDefinitions = new ArrayList(); - PartitionDefinition partitionDef = null; + TablePartitioning partitioning = null; + TablePartitioning subPartitioning = null; + Expression expression = null; + ExpressionList columns = null; + List partitionDefinitions = null; + List parameter = null; + List partitionOptions = new ArrayList(); + Token tk = null; +} +{ + + ( + partitioning=HashOrKeyPartitioning() + | + { partitioning = new TablePartitioning(TablePartitioning.Type.RANGE); } + ( + "(" expression=Expression() ")" { partitioning.setExpression(expression); } + | + "(" columns=ColumnList() ")" + { + partitioning.setColumnsSyntax(true); + partitioning.setColumns(columns); + } + ) + | + { partitioning = new TablePartitioning(TablePartitioning.Type.LIST); } + ( + "(" expression=Expression() ")" { partitioning.setExpression(expression); } + | + "(" columns=ColumnList() ")" + { + partitioning.setColumnsSyntax(true); + partitioning.setColumns(columns); + } + ) + ) + [ LOOKAHEAD({ getToken(1).kind == K_PARTITIONS }) + tk= { partitioning.setPartitions(Long.valueOf(tk.image)); } ] + [ + LOOKAHEAD({ getToken(1).kind == K_SUBPARTITION }) + subPartitioning=HashOrKeyPartitioning() + [ LOOKAHEAD({ getToken(1).kind == K_SUBPARTITIONS }) + tk= + { subPartitioning.setPartitions(Long.valueOf(tk.image)); } ] + { partitioning.setSubPartitioning(subPartitioning); } + ] + [ LOOKAHEAD(2) partitionDefinitions=PartitionDefinitions() + { partitioning.setPartitionDefinitions(partitionDefinitions); } ] + ( LOOKAHEAD(2, { getToken(1).kind != K_AS }) + parameter=CreateParameter() { partitionOptions.addAll(parameter); } )* + { + if (!partitionOptions.isEmpty()) { + partitioning.setPartitionOptions(partitionOptions); + } + return partitioning; + } +} + +TablePartitioning HashOrKeyPartitioning(): +{ + TablePartitioning partitioning = new TablePartitioning(); + Expression expression = null; + ExpressionList columns = null; + Token tk = null; + boolean useEquals = false; +} +{ + [ { partitioning.setLinear(true); } ] + ( + "(" expression=Expression() ")" + { + partitioning.setType(TablePartitioning.Type.HASH); + partitioning.setExpression(expression); + } + | + + [ [ "=" { useEquals = true; } ] tk= + { + partitioning.setAlgorithm(Integer.valueOf(tk.image)); + partitioning.setAlgorithmUseEquals(useEquals); + } + ] + "(" [ columns=ColumnList() ] ")" + { + partitioning.setType(TablePartitioning.Type.KEY); + if (columns == null) { + columns = new ExpressionList(); + } + partitioning.setColumns(columns); + } + ) + { + return partitioning; + } +} + +List PartitionValues(): +{ + List values = new ArrayList(); + Expression expression = null; +} +{ + ( + LOOKAHEAD({ getToken(1).kind == K_MAXVALUE }) + { values.add("MAXVALUE"); } + | expression=Expression() { values.add(expression.toString()); } + ) + ( + "," + ( + LOOKAHEAD({ getToken(1).kind == K_MAXVALUE }) + { values.add("MAXVALUE"); } + | expression=Expression() { values.add(expression.toString()); } + ) + )* + { + return values; + } +} + +PartitionDefinition PartitionDefinition(): +{ + PartitionDefinition partitionDefinition = new PartitionDefinition(); + List values = null; + List subPartitionDefinitions = null; String partitionName = null; - String partitionOperation = null; - String storageEngine = null; - Expression exp = null; } { - "(" + partitionName=RelObjectName() + { partitionDefinition.setPartitionName(partitionName); } + [ + ( - - partitionName=RelObjectName() + { - List values = new ArrayList(); + partitionDefinition.setValueOperator( + PartitionDefinition.ValueOperator.LESS_THAN); } - ( - - ( - "(" exp = Expression() ")"{ - values.add(exp.toString()); - } - | { values.add("MAXVALUE"); } - ) { - partitionOperation = "VALUES LESS THAN"; + + { + partitionDefinition.setMaxValue(true); + partitionDefinition.setValues(Collections.singletonList("MAXVALUE")); } + | + "(" values=PartitionValues() ")" { partitionDefinition.setValues(values); } ) - [ "ENGINE" "=" tk= { storageEngine = tk.image; } ] + | + { - partitionDef = new PartitionDefinition(partitionName, partitionOperation, values, storageEngine); - partitionDefinitions.add(partitionDef); + partitionDefinition.setValueOperator(PartitionDefinition.ValueOperator.IN); } - [ "," ] - )* + "(" values=PartitionValues() ")" { partitionDefinition.setValues(values); } + ) + ] + ( PartitionDefinitionOption(partitionDefinition) )* + [ subPartitionDefinitions=SubpartitionDefinitions() + { partitionDefinition.setSubPartitionDefinitions(subPartitionDefinitions); } ] + { + return partitionDefinition; + } +} + +List PartitionDefinitions(): +{ + List partitionDefinitions = new ArrayList(); + PartitionDefinition partitionDefinition = null; +} +{ + "(" partitionDefinition=PartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } + ( "," partitionDefinition=PartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } )* ")" { return partitionDefinitions; } } +PartitionDefinition SubpartitionDefinition(): +{ + PartitionDefinition partitionDefinition = new PartitionDefinition(); + String partitionName = null; +} +{ + partitionName=RelObjectName() + { + partitionDefinition.setPartitionName(partitionName); + partitionDefinition.setSubPartition(true); + } + ( PartitionDefinitionOption(partitionDefinition) )* + { + return partitionDefinition; + } +} + +List SubpartitionDefinitions(): +{ + List partitionDefinitions = new ArrayList(); + PartitionDefinition partitionDefinition = null; +} +{ + "(" partitionDefinition=SubpartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } + ( "," partitionDefinition=SubpartitionDefinition() + { partitionDefinitions.add(partitionDefinition); } )* + ")" + { + return partitionDefinitions; + } +} + +void PartitionDefinitionOption(PartitionDefinition partitionDefinition): +{ + Token tk = null; + String value = null; + boolean useEquals = false; +} +{ + ( + [ { partitionDefinition.setStorageKeyword(true); } ] + [ "=" { useEquals = true; } ] value=RelObjectName() + { + partitionDefinition.setStorageEngine(value); + partitionDefinition.setStorageEngineUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setComment(tk.image); + partitionDefinition.setCommentUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setDataDirectory(tk.image); + partitionDefinition.setDataDirectoryUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setIndexDirectory(tk.image); + partitionDefinition.setIndexDirectoryUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setMaxRows(tk.image); + partitionDefinition.setMaxRowsUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] tk= + { + partitionDefinition.setMinRows(tk.image); + partitionDefinition.setMinRowsUseEquals(useEquals); + } + | + [ "=" { useEquals = true; } ] value=RelObjectName() + { + partitionDefinition.setTablespace(value); + partitionDefinition.setTablespaceUseEquals(useEquals); + } + ) +} + List PartitionNamesList() : { Token tk; diff --git a/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTablePartitionTest.java b/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTablePartitionTest.java new file mode 100644 index 000000000..ce1a0f84c --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/MySQLCreateTablePartitionTest.java @@ -0,0 +1,175 @@ +/*- + * #%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.create; + +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.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.statement.create.table.CreateTable; +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 CREATE TABLE} partition syntax verified against MySQL 8.4.11. */ +public class MySQLCreateTablePartitionTest { + + private static final List MYSQL84_PARTITION_STATEMENTS = List.of( + "CREATE TABLE mysql_partition_hash (id INT) " + + "PARTITION BY HASH (id) PARTITIONS 4", + "CREATE TABLE mysql_partition_linear_hash (id INT) " + + "PARTITION BY LINEAR HASH (id) PARTITIONS 4", + "CREATE TABLE mysql_partition_key (id INT) " + + "PARTITION BY LINEAR KEY ALGORITHM = 1 (id) PARTITIONS 4", + "CREATE TABLE mysql_partition_range (id INT) PARTITION BY RANGE (id) " + + "(PARTITION p0 VALUES LESS THAN (10), " + + "PARTITION p1 VALUES LESS THAN (20), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE)", + "CREATE TABLE mysql_partition_range_columns (a INT, b INT) " + + "PARTITION BY RANGE COLUMNS (a, b) " + + "(PARTITION p0 VALUES LESS THAN (5, 12), " + + "PARTITION pmax VALUES LESS THAN (MAXVALUE, MAXVALUE))", + "CREATE TABLE mysql_partition_list (id INT) PARTITION BY LIST (id) " + + "(PARTITION p0 VALUES IN (1, 2), " + + "PARTITION p1 VALUES IN (3, 4))", + "CREATE TABLE mysql_partition_list_columns_multi " + + "(region VARCHAR (16), tier INT) " + + "PARTITION BY LIST COLUMNS (region, tier) " + + "(PARTITION p0 VALUES IN (('eu', 1), ('us', 2)), " + + "PARTITION p1 VALUES IN (('apac', 3)))", + "CREATE TABLE mysql_partition_sub_count (id INT, d DATE) " + + "PARTITION BY RANGE (YEAR(d)) " + + "SUBPARTITION BY HASH (id) SUBPARTITIONS 2 " + + "(PARTITION p0 VALUES LESS THAN (2025), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE)", + "CREATE TABLE mysql_partition_sub_explicit (id INT, d DATE) " + + "PARTITION BY RANGE (YEAR(d)) " + + "SUBPARTITION BY LINEAR KEY ALGORITHM = 2 (id) " + + "(PARTITION p0 VALUES LESS THAN (2025) " + + "(SUBPARTITION p0s0 ENGINE = InnoDB, " + + "SUBPARTITION p0s1 ENGINE = InnoDB), " + + "PARTITION pmax VALUES LESS THAN MAXVALUE " + + "(SUBPARTITION pmaxs0 ENGINE = InnoDB, " + + "SUBPARTITION pmaxs1 ENGINE = InnoDB))", + "CREATE TABLE mysql_partition_options (id INT) ENGINE = InnoDB " + + "PARTITION BY RANGE (id) " + + "(PARTITION p0 VALUES LESS THAN (10) ENGINE = InnoDB " + + "COMMENT = 'hot' MAX_ROWS = 100 MIN_ROWS = 1, " + + "PARTITION pmax VALUES LESS THAN MAXVALUE " + + "ENGINE InnoDB COMMENT 'cold')", + "CREATE TABLE mysql_partition_key_empty " + + "(id INT NOT NULL PRIMARY KEY, name VARCHAR (20)) " + + "PARTITION BY KEY () PARTITIONS 2", + "CREATE TABLE mysql_partition_hash_named (id INT) " + + "PARTITION BY HASH (id) PARTITIONS 2 " + + "(PARTITION p0 STORAGE ENGINE = InnoDB, " + + "PARTITION p1 ENGINE InnoDB)"); + + private static Stream mysql84PartitionStatements() { + return MYSQL84_PARTITION_STATEMENTS.stream(); + } + + @ParameterizedTest + @MethodSource("mysql84PartitionStatements") + public void testMySQL84CreateTablePartitions(String sql) throws JSQLParserException { + assertInstanceOf(CreateTable.class, assertSqlCanBeParsedAndDeparsed(sql)); + } + + @Test + public void testHashAndKeyPartitioningAst() throws JSQLParserException { + CreateTable hash = parse(MYSQL84_PARTITION_STATEMENTS.get(1)); + TablePartitioning hashPartitioning = hash.getPartitioning(); + assertEquals(TablePartitioning.Type.HASH, hashPartitioning.getType()); + assertTrue(hashPartitioning.isLinear()); + assertEquals("id", hashPartitioning.getExpression().toString()); + assertEquals(4L, hashPartitioning.getPartitions()); + + CreateTable key = parse(MYSQL84_PARTITION_STATEMENTS.get(2)); + TablePartitioning keyPartitioning = key.getPartitioning(); + assertEquals(TablePartitioning.Type.KEY, keyPartitioning.getType()); + assertTrue(keyPartitioning.isLinear()); + assertEquals(1, keyPartitioning.getAlgorithm()); + assertTrue(keyPartitioning.isAlgorithmUseEquals()); + assertEquals("id", keyPartitioning.getColumns().get(0).getColumnName()); + + CreateTable emptyKey = parse(MYSQL84_PARTITION_STATEMENTS.get(10)); + assertTrue(emptyKey.getPartitioning().getColumns().isEmpty()); + } + + @Test + public void testRangeAndListPartitionDefinitionsAst() throws JSQLParserException { + CreateTable range = parse(MYSQL84_PARTITION_STATEMENTS.get(4)); + TablePartitioning rangePartitioning = range.getPartitioning(); + assertEquals(TablePartitioning.Type.RANGE, rangePartitioning.getType()); + assertTrue(rangePartitioning.isColumnsSyntax()); + assertEquals("a", rangePartitioning.getColumns().get(0).getColumnName()); + assertEquals("b", rangePartitioning.getColumns().get(1).getColumnName()); + PartitionDefinition maxPartition = rangePartitioning.getPartitionDefinitions().get(1); + assertEquals(PartitionDefinition.ValueOperator.LESS_THAN, + maxPartition.getValueOperator()); + assertEquals(List.of("MAXVALUE", "MAXVALUE"), maxPartition.getValues()); + assertFalse(maxPartition.isMaxValue()); + + CreateTable list = parse(MYSQL84_PARTITION_STATEMENTS.get(5)); + PartitionDefinition listPartition = list.getPartitioning().getPartitionDefinitions().get(0); + assertEquals(PartitionDefinition.ValueOperator.IN, listPartition.getValueOperator()); + assertEquals(List.of("1", "2"), listPartition.getValues()); + } + + @Test + public void testSubPartitioningAst() throws JSQLParserException { + CreateTable createTable = parse(MYSQL84_PARTITION_STATEMENTS.get(8)); + TablePartitioning subPartitioning = createTable.getPartitioning().getSubPartitioning(); + assertNotNull(subPartitioning); + assertEquals(TablePartitioning.Type.KEY, subPartitioning.getType()); + assertTrue(subPartitioning.isLinear()); + assertEquals(2, subPartitioning.getAlgorithm()); + assertEquals("id", subPartitioning.getColumns().get(0).getColumnName()); + + PartitionDefinition first = createTable.getPartitioning().getPartitionDefinitions().get(0); + assertEquals(2, first.getSubPartitionDefinitions().size()); + assertTrue(first.getSubPartitionDefinitions().get(0).isSubPartition()); + assertEquals("InnoDB", first.getSubPartitionDefinitions().get(0).getStorageEngine()); + } + + @Test + public void testPartitionOptionsAst() throws JSQLParserException { + CreateTable createTable = parse(MYSQL84_PARTITION_STATEMENTS.get(9)); + PartitionDefinition first = createTable.getPartitioning().getPartitionDefinitions().get(0); + assertEquals("InnoDB", first.getStorageEngine()); + assertTrue(first.isStorageEngineUseEquals()); + assertEquals("'hot'", first.getComment()); + assertTrue(first.isCommentUseEquals()); + assertEquals("100", first.getMaxRows()); + assertEquals("1", first.getMinRows()); + + PartitionDefinition max = createTable.getPartitioning().getPartitionDefinitions().get(1); + assertTrue(max.isMaxValue()); + assertFalse(max.isStorageEngineUseEquals()); + assertFalse(max.isCommentUseEquals()); + + PartitionDefinition namedHash = parse(MYSQL84_PARTITION_STATEMENTS.get(11)) + .getPartitioning().getPartitionDefinitions().get(0); + assertTrue(namedHash.isStorageKeyword()); + } + + private static CreateTable parse(String sql) throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse(sql); + } +}