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
Expand Up @@ -40,6 +40,7 @@ public enum NationalCharacterType {
private String dataType;
private List<String> argumentsStringList;
private String characterSet;
private boolean useCharsetKeyword;
private IntervalQualifier intervalQualifier;
private List<Integer> arrayData = new ArrayList<Integer>();
private Signedness signedness;
Expand Down Expand Up @@ -173,6 +174,15 @@ public void setCharacterSet(String characterSet) {
this.characterSet = characterSet;
}

/** Whether the character set clause uses MySQL's CHARSET abbreviation. */
public boolean isUseCharsetKeyword() {
return useCharsetKeyword;
}

public void setUseCharsetKeyword(boolean useCharsetKeyword) {
this.useCharsetKeyword = useCharsetKeyword;
}

public IntervalQualifier getIntervalQualifier() {
return intervalQualifier;
}
Expand Down Expand Up @@ -338,7 +348,9 @@ public String toString() {
: (signedness != null ? " " + signedness : "")
+ (zerofill ? " ZEROFILL" : ""))
+ arraySpec.toString()
+ (characterSet != null ? " CHARACTER SET " + characterSet : "");
+ (characterSet != null
? (useCharsetKeyword ? " CHARSET " : " CHARACTER SET ") + characterSet
: "");
}

public ColDataType withDataType(String dataType) {
Expand Down Expand Up @@ -435,6 +447,7 @@ public final boolean equals(Object o) {
return dataType.equalsIgnoreCase(that.dataType)
&& Objects.equals(argumentsStringList, that.argumentsStringList)
&& Objects.equals(characterSet, that.characterSet)
&& useCharsetKeyword == that.useCharsetKeyword
&& Objects.equals(intervalQualifier, that.intervalQualifier)
&& Objects.equals(arrayData, that.arrayData)
&& signedness == that.signedness
Expand All @@ -452,6 +465,7 @@ public int hashCode() {
.reduce(0, (hash, c) -> 31 * hash + c);
result = 31 * result + Objects.hashCode(argumentsStringList);
result = 31 * result + Objects.hashCode(characterSet);
result = 31 * result + Boolean.hashCode(useCharsetKeyword);
result = 31 * result + Objects.hashCode(intervalQualifier);
result = 31 * result + Objects.hashCode(arrayData);
result = 31 * result + Objects.hashCode(signedness);
Expand Down
16 changes: 15 additions & 1 deletion src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -15281,7 +15281,8 @@ ColDataType ColDataType():
( LOOKAHEAD(1) typeModifier=MySqlTypeModifier()
{ colDataType.addTypeModifier(typeModifier); } )*
[ LOOKAHEAD(2) ( LOOKAHEAD(2) "[" {tk=null;} [ tk=<S_LONG> ] { array.add(tk!=null?Integer.valueOf(tk.image):null); } "]" )+ { colDataType.setArrayData(array); } ]
[ LOOKAHEAD(2) <K_CHARACTER> <K_SET> (tk=<S_IDENTIFIER> | tk=<K_BINARY>) { colDataType.setCharacterSet(tk.image); } ]
[ LOOKAHEAD({ (getToken(1).kind == K_CHARACTER && getToken(2).kind == K_SET)
|| isKeywordAhead("CHARSET") }) TypeCharacterSet(colDataType) ]

{
requireDdlSyntax(colDataType.getXmlTypeModifier() == null || argumentsStringList.isEmpty(),
Expand All @@ -15304,6 +15305,19 @@ ColDataType ColDataType():
}
}

/** Shared by column definitions, casts and other uses of ColDataType. */
void TypeCharacterSet(ColDataType type):
{
Token name;
boolean abbreviated = false;
}
{
( <K_CHARACTER> <K_SET>
| LOOKAHEAD({ isKeywordAhead("CHARSET") }) <S_IDENTIFIER> { abbreviated = true; } )
( name=<S_IDENTIFIER> | name=<K_BINARY> | name=<S_CHAR_LITERAL> | name=<S_QUOTED_IDENTIFIER> )
{ type.setCharacterSet(name.image); type.setUseCharsetKeyword(abbreviated); }
}

XmlTypeModifier XmlTypeModifier():
{
XmlTypeModifier.Kind kind = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*-
* #%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.create.table;

import net.sf.jsqlparser.expression.CastExpression;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
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;

import static org.junit.jupiter.api.Assertions.*;

class MySqlTypeCharacterSetTest {
private Statement parse(String sql) throws Exception {
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
}

@ParameterizedTest
@ValueSource(strings = {
"SELECT CAST('xx' AS CHAR(16) CHARSET BINARY)",
"SELECT CAST('xx' AS CHAR(16) CHARACTER SET 'utf8mb4')",
"SELECT CONVERT('xx' USING utf8mb4)",
"CREATE TABLE t (c VARCHAR(20) CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin')",
"CREATE TABLE t (c TEXT CHARSET utf8mb4)",
"CREATE TABLE t (c SET('a','b') CHARSET 'utf8mb4')",
"CREATE TABLE t (c BINARY(16) AS (CAST('xx' AS CHAR(16) CHARSET BINARY)))",
"ALTER TABLE t MODIFY c ENUM('a','b') CHARACTER SET 'utf8' COLLATE 'utf8_unicode_ci'",
"ALTER TABLE t ADD c CHAR(4) CHARSET utf8mb4, ADD d VARCHAR(10) CHARACTER SET utf8mb4"
})
void sharesTypeSyntaxAcrossCastsAndColumnDefinitions(String sql) throws Exception {
Statement statement = parse(sql);
StringBuilder output = new StringBuilder();
statement.accept(new StatementDeParser(output), null);
assertEquals(statement.toString(), output.toString());
assertEquals(statement.toString(), parse(output.toString()).toString());
}

@Test
void exposesCharsetAndRetainsAbbreviationWhenMutated() throws Exception {
CreateTable table = (CreateTable) parse("CREATE TABLE t (c VARCHAR(20) CHARSET 'utf8mb4')");
ColDataType type = table.getColumnDefinitions().get(0).getColDataType();
assertEquals("'utf8mb4'", type.getCharacterSet());
assertTrue(type.isUseCharsetKeyword());
type.setCharacterSet("latin1");
assertEquals("CREATE TABLE t (c VARCHAR (20) CHARSET latin1)", table.toString());
type.setUseCharsetKeyword(false);
assertEquals("CREATE TABLE t (c VARCHAR (20) CHARACTER SET latin1)", table.toString());
type.setCharacterSet(null);
assertEquals("CREATE TABLE t (c VARCHAR (20))", table.toString());
}

@Test
void preservesDefaultApiSpellingAndTypeEquality() throws Exception {
ColDataType legacy = new ColDataType("CHAR").withCharacterSet("binary");
assertEquals("CHAR CHARACTER SET binary", legacy.toString());
PlainSelect select = (PlainSelect) parse("SELECT CAST('a' AS CHAR CHARACTER SET binary)");
ColDataType parsed =
((CastExpression) select.getSelectItem(0).getExpression()).getColDataType();
assertEquals(legacy, parsed);
assertEquals(legacy.hashCode(), parsed.hashCode());
parsed.setUseCharsetKeyword(true);
assertNotEquals(legacy, parsed);
}

@ParameterizedTest
@ValueSource(strings = {"SELECT CAST('x' AS CHAR CHARSET)",
"SELECT CAST('x' AS CHAR CHARACTER SET)",
"SELECT CAST('x' AS CHAR CHARSET = utf8mb4)"})
void requiresCharsetName(String sql) {
assertThrows(Exception.class, () -> parse(sql));
}
}
Loading