Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*-
* #%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 java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.statement.select.OrderByElement;

/** MySQL ALTER TABLE ORDER BY, using the same mutable order elements as query ordering. */
public class AlterExpressionOrderBy extends AlterExpression {
private final List<OrderByElement> orderByElements = new ArrayList<>();

public AlterExpressionOrderBy() {
setOperation(AlterOperation.ORDER_BY);
}

public List<OrderByElement> getOrderByElements() {
return orderByElements;
}

@Override
protected void appendBody(StringBuilder builder) {
appendOrderBy(builder, builder::append);
}

public StringBuilder appendTo(StringBuilder builder, Consumer<Expression> expressionPrinter) {
appendOrderBy(builder, expressionPrinter);
appendCommonTail(builder);
return builder;
}

private void appendOrderBy(StringBuilder builder, Consumer<Expression> expressionPrinter) {
builder.append("ORDER BY ");
for (int i = 0; i < orderByElements.size(); i++) {
if (i > 0) {
builder.append(", ");
}
OrderByElement element = orderByElements.get(i);
expressionPrinter.accept(element.getExpression());
if (!element.isAsc()) {
builder.append(" DESC");
} else if (element.isAscDescPresent()) {
builder.append(" ASC");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.Locale;

public enum AlterOperation {
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, DROP_CHECK, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY, ALTER_RELATION;
ADD, ALTER, DROP, DROP_PRIMARY_KEY, DROP_UNIQUE, DROP_FOREIGN_KEY, DROP_CHECK, MODIFY, CHANGE, CONVERT, COLLATE, ALGORITHM, RENAME, RENAME_TABLE, RENAME_INDEX, RENAME_KEY, RENAME_CONSTRAINT, COMMENT, COMMENT_WITH_EQUAL_SIGN, UNSPECIFIC, ADD_PARTITION, DROP_PARTITION, ATTACH_PARTITION, DETACH_PARTITION, DISCARD_PARTITION, IMPORT_PARTITION, TRUNCATE_PARTITION, COALESCE_PARTITION, REORGANIZE_PARTITION, EXCHANGE_PARTITION, ANALYZE_PARTITION, CHECK_PARTITION, OPTIMIZE_PARTITION, REBUILD_PARTITION, REPAIR_PARTITION, REMOVE_PARTITIONING, PARTITION_BY, SET_TABLE_OPTION, ENGINE, FORCE, KEY_BLOCK_SIZE, LOCK, DISCARD_TABLESPACE, IMPORT_TABLESPACE, DISABLE_KEYS, ENABLE_KEYS, ENABLE_ROW_LEVEL_SECURITY, DISABLE_ROW_LEVEL_SECURITY, FORCE_ROW_LEVEL_SECURITY, NO_FORCE_ROW_LEVEL_SECURITY, ALTER_PRIMARY_KEY, ALTER_RELATION, ORDER_BY;

public static AlterOperation from(String operation) {
return Enum.valueOf(AlterOperation.class, operation.toUpperCase(Locale.ROOT));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
public class TableOption implements Serializable {

public enum Kind {
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, STORAGE_PARAMETERS, WITHOUT_OIDS, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, OTHER
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, STORAGE_PARAMETERS, WITHOUT_OIDS, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, ROW_FORMAT, OTHER
}

private Kind kind = Kind.OTHER;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public static void visit(CreateIndex createIndex, Consumer<Expression> expressio
/** Visits the structured definitions and expressions belonging to a single ALTER action. */
public static void visit(AlterExpression action, Consumer<Expression> expressions,
Consumer<Table> tables) {
if (action instanceof net.sf.jsqlparser.statement.alter.AlterExpressionOrderBy) {
((net.sf.jsqlparser.statement.alter.AlterExpressionOrderBy) action).getOrderByElements()
.forEach(element -> accept(element.getExpression(), expressions));
}
if (action instanceof net.sf.jsqlparser.statement.alter.RelationAlterAction) {
net.sf.jsqlparser.statement.alter.RelationAlterAction relation =
(net.sf.jsqlparser.statement.alter.RelationAlterAction) action;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public void deParse(Alter alter) {
}

private void deParseAction(AlterExpression action) {
if (action instanceof net.sf.jsqlparser.statement.alter.AlterExpressionOrderBy) {
((net.sf.jsqlparser.statement.alter.AlterExpressionOrderBy) action).appendTo(builder,
expression -> expression.accept(expressionVisitor, null));
return;
}
if (action instanceof RelationAlterAction) {
((RelationAlterAction) action).appendTo(builder,
expression -> expression.accept(expressionVisitor, null));
Expand Down
38 changes: 37 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
int kind = getToken(1).kind;
if (kind == K_ENGINE || kind == K_COLLATE || kind == K_COMMENT
|| isMySqlScalarTableOptionAhead() || kind == K_UNION
|| isMySqlStatisticsOptionAhead()) {
|| isMySqlStatisticsOptionAhead() || isKeywordAhead("ROW_FORMAT")) {
return true;
}
if (kind == K_CHARACTER && getToken(2).kind == K_SET
Expand Down Expand Up @@ -15480,6 +15480,13 @@ TableOption MySqlTableOption(): {
"Expected DEFAULT, 0, or 1 for " + name);
}
|
LOOKAHEAD({ isKeywordAhead("ROW_FORMAT") }) ContextualKeyword("ROW_FORMAT")
{ name = "ROW_FORMAT"; kind = TableOption.Kind.ROW_FORMAT; }
[ "=" { useEquals = true; } ] value=RelObjectName() {
requireDdlSyntax(Arrays.asList("DEFAULT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT", "COMPACT")
.contains(value.toUpperCase(Locale.ROOT)), "Invalid MySQL ROW_FORMAT");
}
|
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) option=MySqlScalarTableOption()
{ return option; }
|
Expand Down Expand Up @@ -15554,6 +15561,30 @@ TableOption MySqlScalarTableOption():
{ return new TableOption(kind, name, value, useEquals); }
}

/** Statistics and row format share CREATE's validation and option nodes. */
AlterExpressionTableOption AlterSharedTableOption():
{ TableOption option; AlterExpressionTableOption action = new AlterExpressionTableOption(); }
{
option=MySqlTableOption()
{ action.setStructuredTableOption(option); return action; }
}

AlterExpressionOrderBy MySqlAlterOrderBy():
{ AlterExpressionOrderBy action = new AlterExpressionOrderBy(); OrderByElement element; }
{
<K_ORDER> <K_BY> element=MySqlAlterOrderColumn() { action.getOrderByElements().add(element); }
( LOOKAHEAD(1) "," element=MySqlAlterOrderColumn() { action.getOrderByElements().add(element); } )*
{ return action; }
}

OrderByElement MySqlAlterOrderColumn():
{ OrderByElement element = new OrderByElement(); String name; Token order; }
{
name=RelObjectName() { element.setExpression(createColumn(name)); }
[ LOOKAHEAD(2) ( order=<K_ASC> | order=<K_DESC> ) { element.setAsc(order.kind == K_ASC); element.setAscDescPresent(true); } ]
{ return element; }
}

AlterExpressionTableOption AlterScalarTableOption():
{
TableOption option;
Expand Down Expand Up @@ -18068,6 +18099,11 @@ AlterExpression AlterExpression():
{

(
LOOKAHEAD(<K_ORDER> <K_BY>) alterExp=MySqlAlterOrderBy()
|
LOOKAHEAD({ isMySqlStatisticsOptionAhead() || isKeywordAhead("ROW_FORMAT") })
alterExp=AlterSharedTableOption()
|
LOOKAHEAD({ isPostgreSqlRelationActionAhead() }) alterExp=PostgreSqlRelationAction() {
requireDdlSyntax(((RelationAlterAction) alterExp).getColumnNumber() == null,
"ALTER TABLE expects a column name, not an ordinal");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*-
* #%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 org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.TableOption;
import net.sf.jsqlparser.util.TableDefinitionTraversal;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class MySqlAlterOrderingOptionsTest {
@ParameterizedTest
@ValueSource(strings = {"ORDER BY id DESC, label ASC", "ORDER BY id, label",
"ENGINE = InnoDB, ROW_FORMAT = DYNAMIC, STATS_PERSISTENT = DEFAULT",
"STATS_AUTO_RECALC = 1, STATS_SAMPLE_PAGES = 16",
"ENGINE = InnoDB, ORDER BY id DESC, label ASC", "ROW_FORMAT COMPACT, ORDER BY id DESC"})
void actionAndOrderListBoundariesRemainSeparate(String actions) throws JSQLParserException {
Alter alter = parse("ALTER TABLE t " + actions);
assertEquals("ALTER TABLE t " + actions, alter.toString());
alter.getAlterExpressions().forEach(
action -> assertNotEquals(AlterOperation.UNSPECIFIC, action.getOperation()));
assertRoundTrip(alter);
assertEquals(2, CCJSqlParserUtil
.parseStatements(alter + "; SELECT 1", p -> p.withDialect(Dialect.MYSQL)).size());
}

@Test
void orderElementsAreMutableAndVisited() throws JSQLParserException {
Alter alter = parse("ALTER TABLE t ORDER BY id DESC, label ASC");
AlterExpressionOrderBy order = (AlterExpressionOrderBy) alter.getAlterExpressions().get(0);
assertEquals(2, order.getOrderByElements().size());
order.getOrderByElements().get(0).setExpression(new Column("new_id"));
order.getOrderByElements().get(0).setAsc(true);
List<Expression> visited = new ArrayList<>();
TableDefinitionTraversal.visit(order, visited::add, ignored -> {
});
assertEquals(2, visited.size());
assertEquals("ALTER TABLE t ORDER BY new_id ASC, label ASC", alter.toString());
assertRoundTrip(alter);
}

@Test
void rowFormatAndStatisticsShareCreateOptions() throws JSQLParserException {
Alter alter = parse("ALTER TABLE t ROW_FORMAT = DYNAMIC, STATS_PERSISTENT = DEFAULT");
TableOption rowFormat = ((AlterExpressionTableOption) alter.getAlterExpressions().get(0))
.getStructuredTableOption();
TableOption statistics = ((AlterExpressionTableOption) alter.getAlterExpressions().get(1))
.getStructuredTableOption();
assertEquals(TableOption.Kind.ROW_FORMAT, rowFormat.getKind());
assertEquals(TableOption.Kind.STATS_PERSISTENT, statistics.getKind());
rowFormat.setValue("COMPACT");
statistics.setValue("1");
assertEquals("ALTER TABLE t ROW_FORMAT = COMPACT, STATS_PERSISTENT = 1", alter.toString());
CreateTable create =
(CreateTable) CCJSqlParserUtil.parse("CREATE TABLE t (id INT) ROW_FORMAT = COMPACT",
p -> p.withDialect(Dialect.MYSQL));
assertEquals(rowFormat.toString(),
create.getTableOption(TableOption.Kind.ROW_FORMAT).orElseThrow().toString());
assertRoundTrip(alter);
}

@Test
void invalidOptionsAndOrderExpressionsFail() {
for (String actions : new String[] {"ROW_FORMAT = invalid", "STATS_PERSISTENT = 2",
"ORDER BY id + 1", "ORDER BY id, ENGINE = InnoDB",
"ORDER BY id, ADD COLUMN x INT"}) {
assertThrows(JSQLParserException.class, () -> parse("ALTER TABLE t " + actions));
}
}

private static Alter parse(String sql) throws JSQLParserException {
return (Alter) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
}

private static void assertRoundTrip(Alter alter) throws JSQLParserException {
StringBuilder sql = new StringBuilder();
alter.accept(new StatementDeParser(sql), null);
assertEquals(alter.toString(), sql.toString());
assertEquals(alter.toString(), parse(sql.toString()).toString());
}
}
Loading