Skip to content

Commit 515e389

Browse files
committed
fix: refresh index classification when replacing its type
1 parent 0036c75 commit 515e389

2 files changed

Lines changed: 141 additions & 21 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/create/table/Index.java‎

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -217,36 +217,55 @@ public String getType() {
217217
return type;
218218
}
219219

220+
/**
221+
* Sets the rendered type and refreshes its classification, including when replacing an existing
222+
* type. Null and unrecognized types reset the classification to {@link Kind#OTHER}.
223+
*/
220224
public void setType(String string) {
221225
type = string;
222-
if (kind == Kind.OTHER && string != null) {
223-
String normalized = string.toUpperCase(java.util.Locale.ROOT);
224-
if (normalized.startsWith("PRIMARY")) {
225-
kind = Kind.PRIMARY_KEY;
226-
} else if (normalized.startsWith("UNIQUE")) {
227-
kind = Kind.UNIQUE;
228-
} else if (normalized.startsWith("FULLTEXT")) {
229-
kind = Kind.FULLTEXT;
230-
} else if (normalized.startsWith("SPATIAL")) {
231-
kind = Kind.SPATIAL;
232-
} else if (normalized.startsWith("FOREIGN")) {
233-
kind = Kind.FOREIGN_KEY;
234-
} else if (normalized.startsWith("CHECK")) {
235-
kind = Kind.CHECK;
236-
} else if (normalized.startsWith("EXCLUDE")) {
237-
kind = Kind.EXCLUDE;
238-
} else if (normalized.equals("DEFAULT")) {
239-
kind = Kind.DEFAULT;
240-
} else if (normalized.contains("INDEX") || normalized.contains("KEY")) {
241-
kind = Kind.INDEX;
242-
}
226+
kind = classifyType(string);
227+
}
228+
229+
private static Kind classifyType(String type) {
230+
if (type == null) {
231+
return Kind.OTHER;
232+
}
233+
String normalized = type.trim().toUpperCase(java.util.Locale.ROOT);
234+
String keyword = normalized.split("\\s+", 2)[0];
235+
switch (keyword) {
236+
case "PRIMARY":
237+
return Kind.PRIMARY_KEY;
238+
case "UNIQUE":
239+
return Kind.UNIQUE;
240+
case "FULLTEXT":
241+
return Kind.FULLTEXT;
242+
case "SPATIAL":
243+
return Kind.SPATIAL;
244+
case "FOREIGN":
245+
return Kind.FOREIGN_KEY;
246+
case "CHECK":
247+
return Kind.CHECK;
248+
case "EXCLUDE":
249+
return Kind.EXCLUDE;
250+
case "DEFAULT":
251+
return Kind.DEFAULT;
252+
default:
253+
return normalized.equals("INDEX") || normalized.equals("KEY")
254+
|| normalized.endsWith(" INDEX") || normalized.endsWith(" KEY")
255+
? Kind.INDEX
256+
: Kind.OTHER;
243257
}
244258
}
245259

246260
public Kind getKind() {
247261
return kind;
248262
}
249263

264+
/**
265+
* Sets classification metadata without changing the rendered type. This also supports index
266+
* declarations whose keyword is stored separately. A subsequent {@link #setType(String)}
267+
* derives the classification from the new type again.
268+
*/
250269
public void setKind(Kind kind) {
251270
this.kind = kind;
252271
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.create;
11+
12+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertNull;
15+
16+
import net.sf.jsqlparser.JSQLParserException;
17+
import net.sf.jsqlparser.statement.create.table.CheckConstraint;
18+
import net.sf.jsqlparser.statement.create.table.CreateTable;
19+
import net.sf.jsqlparser.statement.create.table.DefaultConstraint;
20+
import net.sf.jsqlparser.statement.create.table.ExcludeConstraint;
21+
import net.sf.jsqlparser.statement.create.table.Index;
22+
import net.sf.jsqlparser.statement.create.table.Index.Kind;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.CsvSource;
26+
import org.junit.jupiter.params.provider.NullAndEmptySource;
27+
import org.junit.jupiter.params.provider.ValueSource;
28+
29+
class IndexKindStateTest {
30+
31+
@ParameterizedTest
32+
@CsvSource({"PRIMARY KEY, PRIMARY_KEY", "unique key, UNIQUE", "UNIQUE INDEX, UNIQUE",
33+
"KEY, INDEX", "INDEX, INDEX", "FULLTEXT KEY, FULLTEXT", "SPATIAL INDEX, SPATIAL",
34+
"FOREIGN KEY, FOREIGN_KEY", "CHECK, CHECK", "EXCLUDE, EXCLUDE", "DEFAULT, DEFAULT",
35+
"BITMAP INDEX, INDEX"})
36+
void replacingTypeRefreshesEveryPreviousClassification(String type, Kind expected) {
37+
for (Kind previous : Kind.values()) {
38+
Index index = new Index().withType("UNIQUE").withKind(previous);
39+
index.setType(type);
40+
assertEquals(type, index.getType());
41+
assertEquals(expected, index.getKind(), previous + " -> " + type);
42+
}
43+
}
44+
45+
@ParameterizedTest
46+
@NullAndEmptySource
47+
@ValueSource(strings = {"CUSTOM", "UNIQUE_CUSTOM", "PRIMARY_CUSTOM", "MONKEY", "INDEXED"})
48+
void clearingOrReplacingWithCustomTypeClearsStaleKind(String type) {
49+
Index index = new Index().withType("UNIQUE").withType(type);
50+
assertEquals(type, index.getType());
51+
assertEquals(Kind.OTHER, index.getKind());
52+
}
53+
54+
@Test
55+
void classificationDoesNotRewriteSpellingOrDependOnLeadingWhitespace() {
56+
Index index = new Index().withType(" unique\tkey ");
57+
assertEquals(Kind.UNIQUE, index.getKind());
58+
assertEquals(" unique\tkey ", index.getType());
59+
}
60+
61+
@Test
62+
void explicitMetadataSupportsOmittedTypeAndSpecializedConstraints() {
63+
Index index = new Index().withKind(Kind.INDEX);
64+
assertNull(index.getType());
65+
assertEquals(Kind.INDEX, index.getKind());
66+
index.setType("UNIQUE");
67+
assertEquals(Kind.UNIQUE, index.getKind());
68+
assertEquals(Kind.CHECK, new CheckConstraint().getKind());
69+
assertEquals(Kind.EXCLUDE, new ExcludeConstraint().getKind());
70+
assertEquals(Kind.DEFAULT, new DefaultConstraint().getKind());
71+
}
72+
73+
// Both the original and rewritten SQL execute on MySQL 8.4 and PostgreSQL 18.
74+
@ParameterizedTest
75+
@CsvSource({"UNIQUE, PRIMARY KEY, PRIMARY_KEY", "PRIMARY KEY, UNIQUE, UNIQUE"})
76+
void rewritingPostgreSqlConstraintPreservesKindThroughDeparsing(String before, String after,
77+
Kind expected) throws JSQLParserException {
78+
CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed(
79+
"CREATE TABLE index_state (id INT, CONSTRAINT key_state " + before + " (id))");
80+
Index index = table.getIndexes().get(0);
81+
index.setType(after);
82+
assertEquals(expected, index.getKind());
83+
CreateTable reparsed = (CreateTable) assertSqlCanBeParsedAndDeparsed(table.toString());
84+
assertEquals(expected, reparsed.getIndexes().get(0).getKind());
85+
assertEquals("key_state", reparsed.getIndexes().get(0).getName());
86+
}
87+
88+
// MySQL KEY and INDEX are aliases; UNIQUE changes the constraint's meaning.
89+
@ParameterizedTest
90+
@CsvSource({"UNIQUE KEY, KEY, INDEX", "KEY, UNIQUE KEY, UNIQUE",
91+
"UNIQUE INDEX, INDEX, INDEX", "INDEX, UNIQUE INDEX, UNIQUE"})
92+
void rewritingMySqlIndexPreservesKindThroughDeparsing(String before, String after,
93+
Kind expected) throws JSQLParserException {
94+
CreateTable table = (CreateTable) assertSqlCanBeParsedAndDeparsed(
95+
"CREATE TABLE index_state (id INT, " + before + " key_state (id))");
96+
table.getIndexes().get(0).setType(after);
97+
assertEquals(expected, table.getIndexes().get(0).getKind());
98+
CreateTable reparsed = (CreateTable) assertSqlCanBeParsedAndDeparsed(table.toString());
99+
assertEquals(expected, reparsed.getIndexes().get(0).getKind());
100+
}
101+
}

0 commit comments

Comments
 (0)