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
12 changes: 8 additions & 4 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1791,7 +1791,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
? ((NamedConstraint) index).getIndexName() : index.getName());
alterExp.setUk(index.getType().toUpperCase(Locale.ROOT).contains("KEY"));
alterExp.setUkTypeSpecified(index.getIndexKeyword() != null);
for (String option : new ArrayList<String>(index.getIndexSpec())) {
// Named constraints render their own options; only legacy indexes need projection.
for (String option : index instanceof NamedConstraint ? Collections.<String>emptyList()
: new ArrayList<String>(index.getIndexSpec())) {
if (option.toUpperCase(Locale.ROOT).startsWith("USING ")) {
alterExp.addParameters("USING");
alterExp.addParameters(option.substring("USING ".length()));
Expand Down Expand Up @@ -14594,7 +14596,8 @@ Index TableIndexSpec(boolean createContext):
columns=IndexColumnsWithParamsList()
TableIndexOptions(createContext, indexOptions)
{
if (createContext) {
// MySQL ALTER preserves both the constraint symbol and the index name.
if (createContext || Dialect.MYSQL.name().equals(getAsString(Feature.dialect))) {
index = new NamedConstraint()
.withIndexName(indexName)
.withType(typeToken.image
Expand Down Expand Up @@ -15584,7 +15587,7 @@ CheckConstraint CheckConstraintSpec(String constraintName):
CheckConstraint checkConstraint;
}
{
<K_CHECK> ( LOOKAHEAD(2) "(" exp = Expression() ")" )*
<K_CHECK> "(" exp = Expression() ")"
[ LOOKAHEAD(2)
[ <K_NOT> { enforced = false; } ]
<K_ENFORCED> { if (enforced == null) { enforced = true; } }
Expand Down Expand Up @@ -17071,7 +17074,8 @@ AlterExpression AlterExpressionAddAlterModify():
}
|
LOOKAHEAD({ alterExp.getOperation() == AlterOperation.ADD
&& Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
&& (Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
|| Dialect.MYSQL.name().equals(getAsString(Feature.dialect)))
&& (getToken(1).kind == K_CONSTRAINT || getToken(1).kind == K_PRIMARY
|| getToken(1).kind == K_UNIQUE || getToken(1).kind == K_FOREIGN
|| getToken(1).kind == K_CHECK || getToken(1).kind == K_EXCLUDE) })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*-
* #%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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.create.table.CheckConstraint;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.Index;
import net.sf.jsqlparser.statement.create.table.NamedConstraint;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
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 MySqlAlterConstraintTest {
@ParameterizedTest
@ValueSource(strings = {
"CONSTRAINT uq UNIQUE KEY idx (id)",
"CONSTRAINT uq UNIQUE INDEX idx USING BTREE (id DESC) COMMENT 'unique id'",
"CONSTRAINT `unique id` UNIQUE KEY `index id` (id)",
"CONSTRAINT UNIQUE KEY idx (id)",
"CONSTRAINT uq UNIQUE (id)",
"UNIQUE KEY idx (id)",
"CONSTRAINT pk PRIMARY KEY (id)",
"CONSTRAINT PRIMARY KEY (id)",
"CHECK (id > 0)",
"CONSTRAINT CHECK (id > 0) NOT ENFORCED",
"CONSTRAINT positive CHECK (id > 0) ENFORCED",
"CONSTRAINT fk FOREIGN KEY parent_idx (id) REFERENCES parent (id) ON DELETE CASCADE"
})
void createAndAlterExposeTheSameConstraint(String definition) throws JSQLParserException {
CreateTable create = (CreateTable) CCJSqlParserUtil.parse(
"CREATE TABLE t (id INT, " + definition + ")", p -> p.withDialect(Dialect.MYSQL));
Alter alter = parse("ALTER TABLE t ADD " + definition + ", ADD COLUMN other INT");
Index actual = alter.getAlterExpressions().get(0).getIndex();
assertEquals(create.getIndexes().get(0).getClass(), actual.getClass());
assertEquals(create.getIndexes().get(0).toString(), actual.toString());
assertEquals(2, alter.getAlterExpressions().size());
assertRoundTrip(alter);
}

@Test
void constraintAndIndexNamesCanBeEditedIndependently() throws JSQLParserException {
Alter alter = parse("ALTER TABLE t ADD CONSTRAINT uq UNIQUE KEY idx (id)");
NamedConstraint unique = assertInstanceOf(NamedConstraint.class,
alter.getAlterExpressions().get(0).getIndex());
assertEquals("uq", unique.getName());
assertEquals("idx", unique.getIndexName());
assertEquals(Index.Kind.UNIQUE, unique.getKind());
unique.setName("new_constraint");
unique.setIndexName("new_index");
assertEquals("ALTER TABLE t ADD CONSTRAINT new_constraint UNIQUE KEY new_index (id)",
alter.toString());
assertRoundTrip(alter);
}

@Test
void unnamedCheckRetainsKeywordEnforcementAndExpressionVisitor() throws JSQLParserException {
Alter alter = parse("ALTER TABLE t ADD CONSTRAINT CHECK (id > 0) NOT ENFORCED");
CheckConstraint check = assertInstanceOf(CheckConstraint.class,
alter.getAlterExpressions().get(0).getIndex());
assertTrue(check.isUseConstraintKeyword());
assertEquals(Boolean.FALSE, check.getEnforced());
StringBuilder buffer = new StringBuilder();
ExpressionDeParser expressions = new ExpressionDeParser() {
@Override
public <S> StringBuilder visit(LongValue value, S context) {
return getBuilder().append(10);
}
};
alter.accept(new StatementDeParser(expressions, new SelectDeParser(), buffer), null);
assertEquals("ALTER TABLE t ADD CONSTRAINT CHECK (id > 10) NOT ENFORCED",
buffer.toString());
assertRoundTrip(parse(buffer.toString()));
}

@Test
void sharedIndexStillProvidesLegacyPrimaryKeyColumns() throws JSQLParserException {
AlterExpression primary = parse("ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY (id)")
.getAlterExpressions().get(0);
assertEquals(List.of("id"), primary.getPkColumns());
}

@ParameterizedTest
@ValueSource(strings = {"CHECK", "CHECK ()", "CONSTRAINT CHECK (id > 0) (id < 10)",
"CONSTRAINT uq UNIQUE KEY idx ()"})
void rejectsIncompleteOrRepeatedConstraintBodies(String definition) {
assertThrows(JSQLParserException.class, () -> parse("ALTER TABLE t ADD " + definition));
}

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 buffer = new StringBuilder();
alter.accept(new StatementDeParser(buffer), null);
assertEquals(alter.toString(), buffer.toString());
assertEquals(alter.toString(), parse(buffer.toString()).toString());
}
}
Loading