Skip to content

Commit 4d6bbf3

Browse files
authored
fix: reuse table constraints for MySQL ALTER TABLE (#2654)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 2fc6e3e commit 4d6bbf3

2 files changed

Lines changed: 127 additions & 4 deletions

File tree

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,7 +1791,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
17911791
? ((NamedConstraint) index).getIndexName() : index.getName());
17921792
alterExp.setUk(index.getType().toUpperCase(Locale.ROOT).contains("KEY"));
17931793
alterExp.setUkTypeSpecified(index.getIndexKeyword() != null);
1794-
for (String option : new ArrayList<String>(index.getIndexSpec())) {
1794+
// Named constraints render their own options; only legacy indexes need projection.
1795+
for (String option : index instanceof NamedConstraint ? Collections.<String>emptyList()
1796+
: new ArrayList<String>(index.getIndexSpec())) {
17951797
if (option.toUpperCase(Locale.ROOT).startsWith("USING ")) {
17961798
alterExp.addParameters("USING");
17971799
alterExp.addParameters(option.substring("USING ".length()));
@@ -14594,7 +14596,8 @@ Index TableIndexSpec(boolean createContext):
1459414596
columns=IndexColumnsWithParamsList()
1459514597
TableIndexOptions(createContext, indexOptions)
1459614598
{
14597-
if (createContext) {
14599+
// MySQL ALTER preserves both the constraint symbol and the index name.
14600+
if (createContext || Dialect.MYSQL.name().equals(getAsString(Feature.dialect))) {
1459814601
index = new NamedConstraint()
1459914602
.withIndexName(indexName)
1460014603
.withType(typeToken.image
@@ -15584,7 +15587,7 @@ CheckConstraint CheckConstraintSpec(String constraintName):
1558415587
CheckConstraint checkConstraint;
1558515588
}
1558615589
{
15587-
<K_CHECK> ( LOOKAHEAD(2) "(" exp = Expression() ")" )*
15590+
<K_CHECK> "(" exp = Expression() ")"
1558815591
[ LOOKAHEAD(2)
1558915592
[ <K_NOT> { enforced = false; } ]
1559015593
<K_ENFORCED> { if (enforced == null) { enforced = true; } }
@@ -17071,7 +17074,8 @@ AlterExpression AlterExpressionAddAlterModify():
1707117074
}
1707217075
|
1707317076
LOOKAHEAD({ alterExp.getOperation() == AlterOperation.ADD
17074-
&& Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
17077+
&& (Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
17078+
|| Dialect.MYSQL.name().equals(getAsString(Feature.dialect)))
1707517079
&& (getToken(1).kind == K_CONSTRAINT || getToken(1).kind == K_PRIMARY
1707617080
|| getToken(1).kind == K_UNIQUE || getToken(1).kind == K_FOREIGN
1707717081
|| getToken(1).kind == K_CHECK || getToken(1).kind == K_EXCLUDE) })
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.alter;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
15+
import static org.junit.jupiter.api.Assertions.assertTrue;
16+
17+
import java.util.List;
18+
import net.sf.jsqlparser.JSQLParserException;
19+
import net.sf.jsqlparser.expression.LongValue;
20+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
21+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
22+
import net.sf.jsqlparser.statement.create.table.CheckConstraint;
23+
import net.sf.jsqlparser.statement.create.table.CreateTable;
24+
import net.sf.jsqlparser.statement.create.table.Index;
25+
import net.sf.jsqlparser.statement.create.table.NamedConstraint;
26+
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
27+
import net.sf.jsqlparser.util.deparser.SelectDeParser;
28+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.ValueSource;
32+
33+
class MySqlAlterConstraintTest {
34+
@ParameterizedTest
35+
@ValueSource(strings = {
36+
"CONSTRAINT uq UNIQUE KEY idx (id)",
37+
"CONSTRAINT uq UNIQUE INDEX idx USING BTREE (id DESC) COMMENT 'unique id'",
38+
"CONSTRAINT `unique id` UNIQUE KEY `index id` (id)",
39+
"CONSTRAINT UNIQUE KEY idx (id)",
40+
"CONSTRAINT uq UNIQUE (id)",
41+
"UNIQUE KEY idx (id)",
42+
"CONSTRAINT pk PRIMARY KEY (id)",
43+
"CONSTRAINT PRIMARY KEY (id)",
44+
"CHECK (id > 0)",
45+
"CONSTRAINT CHECK (id > 0) NOT ENFORCED",
46+
"CONSTRAINT positive CHECK (id > 0) ENFORCED",
47+
"CONSTRAINT fk FOREIGN KEY parent_idx (id) REFERENCES parent (id) ON DELETE CASCADE"
48+
})
49+
void createAndAlterExposeTheSameConstraint(String definition) throws JSQLParserException {
50+
CreateTable create = (CreateTable) CCJSqlParserUtil.parse(
51+
"CREATE TABLE t (id INT, " + definition + ")", p -> p.withDialect(Dialect.MYSQL));
52+
Alter alter = parse("ALTER TABLE t ADD " + definition + ", ADD COLUMN other INT");
53+
Index actual = alter.getAlterExpressions().get(0).getIndex();
54+
assertEquals(create.getIndexes().get(0).getClass(), actual.getClass());
55+
assertEquals(create.getIndexes().get(0).toString(), actual.toString());
56+
assertEquals(2, alter.getAlterExpressions().size());
57+
assertRoundTrip(alter);
58+
}
59+
60+
@Test
61+
void constraintAndIndexNamesCanBeEditedIndependently() throws JSQLParserException {
62+
Alter alter = parse("ALTER TABLE t ADD CONSTRAINT uq UNIQUE KEY idx (id)");
63+
NamedConstraint unique = assertInstanceOf(NamedConstraint.class,
64+
alter.getAlterExpressions().get(0).getIndex());
65+
assertEquals("uq", unique.getName());
66+
assertEquals("idx", unique.getIndexName());
67+
assertEquals(Index.Kind.UNIQUE, unique.getKind());
68+
unique.setName("new_constraint");
69+
unique.setIndexName("new_index");
70+
assertEquals("ALTER TABLE t ADD CONSTRAINT new_constraint UNIQUE KEY new_index (id)",
71+
alter.toString());
72+
assertRoundTrip(alter);
73+
}
74+
75+
@Test
76+
void unnamedCheckRetainsKeywordEnforcementAndExpressionVisitor() throws JSQLParserException {
77+
Alter alter = parse("ALTER TABLE t ADD CONSTRAINT CHECK (id > 0) NOT ENFORCED");
78+
CheckConstraint check = assertInstanceOf(CheckConstraint.class,
79+
alter.getAlterExpressions().get(0).getIndex());
80+
assertTrue(check.isUseConstraintKeyword());
81+
assertEquals(Boolean.FALSE, check.getEnforced());
82+
StringBuilder buffer = new StringBuilder();
83+
ExpressionDeParser expressions = new ExpressionDeParser() {
84+
@Override
85+
public <S> StringBuilder visit(LongValue value, S context) {
86+
return getBuilder().append(10);
87+
}
88+
};
89+
alter.accept(new StatementDeParser(expressions, new SelectDeParser(), buffer), null);
90+
assertEquals("ALTER TABLE t ADD CONSTRAINT CHECK (id > 10) NOT ENFORCED",
91+
buffer.toString());
92+
assertRoundTrip(parse(buffer.toString()));
93+
}
94+
95+
@Test
96+
void sharedIndexStillProvidesLegacyPrimaryKeyColumns() throws JSQLParserException {
97+
AlterExpression primary = parse("ALTER TABLE t ADD CONSTRAINT pk PRIMARY KEY (id)")
98+
.getAlterExpressions().get(0);
99+
assertEquals(List.of("id"), primary.getPkColumns());
100+
}
101+
102+
@ParameterizedTest
103+
@ValueSource(strings = {"CHECK", "CHECK ()", "CONSTRAINT CHECK (id > 0) (id < 10)",
104+
"CONSTRAINT uq UNIQUE KEY idx ()"})
105+
void rejectsIncompleteOrRepeatedConstraintBodies(String definition) {
106+
assertThrows(JSQLParserException.class, () -> parse("ALTER TABLE t ADD " + definition));
107+
}
108+
109+
private static Alter parse(String sql) throws JSQLParserException {
110+
return (Alter) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
111+
}
112+
113+
private static void assertRoundTrip(Alter alter) throws JSQLParserException {
114+
StringBuilder buffer = new StringBuilder();
115+
alter.accept(new StatementDeParser(buffer), null);
116+
assertEquals(alter.toString(), buffer.toString());
117+
assertEquals(alter.toString(), parse(buffer.toString()).toString());
118+
}
119+
}

0 commit comments

Comments
 (0)