Skip to content

Commit d4dee07

Browse files
committed
fix: parse every MySQL character set form of ALTER TABLE CONVERT TO
CONVERT TO accepted only CHARACTER SET followed by an unquoted name, and COLLATE only an unquoted name. CONVERT TO CHARSET, CHARACTER SET DEFAULT, the binary character set and quoted names failed, although MySQL accepts them all and column types already took the same names. CONVERT TO now reads its character set and collation with the name rule of column types, which the new CharacterSetName production shares, and also accepts DEFAULT. AlterExpression.isUseCharsetKeyword() keeps the CHARSET spelling, so CONVERT TO CHARSET, DEFAULT CHARSET and CHARSET table options print as written instead of as CHARACTER SET. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent dd03d6f commit d4dee07

3 files changed

Lines changed: 125 additions & 6 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public enum TableRenameKeyword {
7272
private List<String> parameters;
7373

7474
private ConvertType convertType;
75+
private boolean useCharsetKeyword;
7576
private boolean hasEqualForCharacterSet;
7677
private boolean hasEqualForCollate;
7778

@@ -956,6 +957,15 @@ public void setConvertType(ConvertType convertType) {
956957
this.convertType = convertType;
957958
}
958959

960+
/** Whether the character set clause uses MySQL's CHARSET abbreviation. */
961+
public boolean isUseCharsetKeyword() {
962+
return useCharsetKeyword;
963+
}
964+
965+
public void setUseCharsetKeyword(boolean useCharsetKeyword) {
966+
this.useCharsetKeyword = useCharsetKeyword;
967+
}
968+
959969
public String getCharacterSet() {
960970
return characterSet;
961971
}
@@ -1485,15 +1495,16 @@ protected void toStringDropSpecial(StringBuilder b) {
14851495

14861496
protected void toStringConvert(StringBuilder b) {
14871497
if (operation == AlterOperation.CONVERT) {
1498+
String keyword = useCharsetKeyword ? "CHARSET " : "CHARACTER SET ";
14881499
if (convertType == ConvertType.CONVERT_TO) {
1489-
b.append("CONVERT TO CHARACTER SET ");
1500+
b.append("CONVERT TO ").append(keyword);
14901501
} else if (convertType == ConvertType.DEFAULT_CHARACTER_SET) {
1491-
b.append("DEFAULT CHARACTER SET ");
1502+
b.append("DEFAULT ").append(keyword);
14921503
if (hasEqualForCharacterSet) {
14931504
b.append("= ");
14941505
}
14951506
} else if (convertType == ConvertType.CHARACTER_SET) {
1496-
b.append("CHARACTER SET ");
1507+
b.append(keyword);
14971508
if (hasEqualForCharacterSet) {
14981509
b.append("= ");
14991510
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19131,6 +19131,7 @@ AlterExpression AlterSharedTableOption():
1913119131
charset.setConvertType(useDefault ? AlterExpression.ConvertType.DEFAULT_CHARACTER_SET
1913219132
: AlterExpression.ConvertType.CHARACTER_SET);
1913319133
charset.setCharacterSet(option.getValue());
19134+
charset.setUseCharsetKeyword(option.getName().toUpperCase(Locale.ROOT).endsWith("CHARSET"));
1913419135
charset.setHasEqualForCharacterSet(option.isUseEquals());
1913519136
if (collation != null) {
1913619137
charset.setCollation(collation.getValue());
@@ -19519,10 +19520,18 @@ void TypeCharacterSet(ColDataType type):
1951919520
{
1952019521
( <K_CHARACTER> <K_SET>
1952119522
| LOOKAHEAD({ isKeywordAhead("CHARSET") }) <S_IDENTIFIER> { abbreviated = true; } )
19522-
( name=<S_IDENTIFIER> | name=<K_BINARY> | name=<S_CHAR_LITERAL> | name=<S_QUOTED_IDENTIFIER> )
19523+
name=CharacterSetName()
1952319524
{ type.setCharacterSet(name.image); type.setUseCharsetKeyword(abbreviated); }
1952419525
}
1952519526

19527+
/** A MySQL character set or collation name: an identifier, BINARY or a quoted name. */
19528+
Token CharacterSetName():
19529+
{ Token name; }
19530+
{
19531+
( name=<S_IDENTIFIER> | name=<K_BINARY> | name=<S_CHAR_LITERAL> | name=<S_QUOTED_IDENTIFIER> )
19532+
{ return name; }
19533+
}
19534+
1952619535
XmlTypeModifier XmlTypeModifier():
1952719536
{
1952819537
XmlTypeModifier.Kind kind = null;
@@ -22081,8 +22090,11 @@ AlterExpression AlterExpression():
2208122090
alterExp.setOperation(AlterOperation.CONVERT);
2208222091
alterExp.setConvertType(AlterExpression.ConvertType.CONVERT_TO);
2208322092
}
22084-
<K_TO> <K_CHARACTER> <K_SET> tk=<S_IDENTIFIER> { alterExp.setCharacterSet(tk.image); }
22085-
[ LOOKAHEAD(2) <K_COLLATE> tk2=<S_IDENTIFIER> { alterExp.setCollation(tk2.image); }]
22093+
<K_TO>
22094+
( <K_CHARACTER> <K_SET>
22095+
| LOOKAHEAD({ isKeywordAhead("CHARSET") }) <S_IDENTIFIER> { alterExp.setUseCharsetKeyword(true); } )
22096+
( tk=<K_DEFAULT> | tk=CharacterSetName() ) { alterExp.setCharacterSet(tk.image); }
22097+
[ LOOKAHEAD(2) <K_COLLATE> tk2=CharacterSetName() { alterExp.setCollation(tk2.image); }]
2208622098
)
2208722099
|
2208822100
alterExp = AlterExpressionDiscardOrImport()
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.*;
13+
14+
import java.util.function.Consumer;
15+
import net.sf.jsqlparser.JSQLParserException;
16+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
17+
import net.sf.jsqlparser.parser.CCJSqlParser;
18+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
19+
import net.sf.jsqlparser.statement.Statement;
20+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.ValueSource;
24+
25+
class MySqlConvertToCharacterSetTest {
26+
private static final Consumer<CCJSqlParser> MYSQL = p -> p.withDialect(Dialect.MYSQL);
27+
28+
@ParameterizedTest
29+
@ValueSource(strings = {"ALTER TABLE t CONVERT TO CHARSET utf8mb4",
30+
"ALTER TABLE t CONVERT TO CHARSET utf8mb4 COLLATE utf8mb4_bin",
31+
"ALTER TABLE t CONVERT TO CHARACTER SET DEFAULT",
32+
"ALTER TABLE t CONVERT TO CHARACTER SET DEFAULT COLLATE utf8mb4_bin",
33+
"ALTER TABLE t CONVERT TO CHARACTER SET binary",
34+
"ALTER TABLE t CONVERT TO CHARACTER SET binary COLLATE binary",
35+
"ALTER TABLE t CONVERT TO CHARACTER SET 'utf8mb4'",
36+
"ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE 'utf8mb4_bin'",
37+
"ALTER TABLE t CONVERT TO CHARACTER SET `utf8mb4` COLLATE `utf8mb4_bin`",
38+
"ALTER TABLE t CONVERT TO CHARSET utf8mb4, ADD COLUMN c INT",
39+
"ALTER TABLE t DEFAULT CHARSET utf8mb4",
40+
"ALTER TABLE t CHARSET = utf8mb4 COLLATE = utf8mb4_bin"})
41+
void keepsEveryCharacterSetForm(String sql) throws JSQLParserException {
42+
for (Statement statement : new Statement[] {CCJSqlParserUtil.parse(sql),
43+
CCJSqlParserUtil.parse(sql, MYSQL)}) {
44+
assertEquals(sql, statement.toString());
45+
StringBuilder deparsed = new StringBuilder();
46+
statement.accept(new StatementDeParser(deparsed));
47+
assertEquals(sql, deparsed.toString());
48+
}
49+
assertEquals(2, CCJSqlParserUtil.parseStatements(sql + "; SELECT 1", MYSQL).size());
50+
}
51+
52+
@Test
53+
void exposesCharsetSpellingNameAndCollation() throws JSQLParserException {
54+
AlterExpression action =
55+
parseAction("ALTER TABLE t CONVERT TO CHARSET utf8mb4 COLLATE 'utf8mb4_bin'");
56+
assertEquals(AlterOperation.CONVERT, action.getOperation());
57+
assertEquals(AlterExpression.ConvertType.CONVERT_TO, action.getConvertType());
58+
assertTrue(action.isUseCharsetKeyword());
59+
assertEquals("utf8mb4", action.getCharacterSet());
60+
assertEquals("'utf8mb4_bin'", action.getCollation());
61+
62+
action.setUseCharsetKeyword(false);
63+
action.setCollation("utf8mb4_0900_ai_ci");
64+
assertEquals("CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci",
65+
action.toString());
66+
67+
AlterExpression standard = parseAction("ALTER TABLE t CONVERT TO CHARACTER SET DEFAULT");
68+
assertFalse(standard.isUseCharsetKeyword());
69+
assertEquals("DEFAULT", standard.getCharacterSet());
70+
assertNull(standard.getCollation());
71+
}
72+
73+
@Test
74+
void keepsCharsetSpellingOfTableOptions() throws JSQLParserException {
75+
AlterExpression action = parseAction("ALTER TABLE t DEFAULT CHARSET = utf8mb4");
76+
assertEquals(AlterExpression.ConvertType.DEFAULT_CHARACTER_SET, action.getConvertType());
77+
assertTrue(action.isUseCharsetKeyword());
78+
assertFalse(
79+
parseAction("ALTER TABLE t DEFAULT CHARACTER SET = utf8mb4").isUseCharsetKeyword());
80+
}
81+
82+
@ParameterizedTest
83+
@ValueSource(strings = {"ALTER TABLE t CONVERT TO CHARSET",
84+
"ALTER TABLE t CONVERT TO utf8mb4",
85+
"ALTER TABLE t CONVERT TO CHARSET = utf8mb4",
86+
"ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE"})
87+
void rejectsIncompleteForms(String sql) {
88+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql, MYSQL));
89+
}
90+
91+
private static AlterExpression parseAction(String sql) throws JSQLParserException {
92+
Alter alter = (Alter) CCJSqlParserUtil.parse(sql, MYSQL);
93+
assertEquals(1, alter.getAlterExpressions().size());
94+
return alter.getAlterExpressions().get(0);
95+
}
96+
}

0 commit comments

Comments
 (0)