Skip to content

Commit 0f58f7f

Browse files
authored
feat: model MySQL JSON_TABLE column collations (#2686)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 37b866b commit 0f58f7f

3 files changed

Lines changed: 109 additions & 0 deletions

File tree

‎src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ public static class JsonTableValueColumnDefinition extends JsonTableColumnDefini
474474
private String columnName;
475475
private boolean forOrdinality;
476476
private ColDataType dataType;
477+
private String collation;
477478
private boolean formatJson;
478479
private boolean exists;
479480
private boolean onEmptyAfterOnError;
@@ -526,6 +527,16 @@ public JsonTableValueColumnDefinition setDataType(ColDataType dataType) {
526527
return this;
527528
}
528529

530+
/** Returns the column collation, including any identifier quoting, or null if absent. */
531+
public String getCollation() {
532+
return collation;
533+
}
534+
535+
public JsonTableValueColumnDefinition setCollation(String collation) {
536+
this.collation = collation;
537+
return this;
538+
}
539+
529540
public boolean isFormatJson() {
530541
return formatJson;
531542
}
@@ -623,6 +634,9 @@ public String toString() {
623634
if (dataType != null) {
624635
builder.append(" ").append(dataType);
625636
}
637+
if (collation != null) {
638+
builder.append(" COLLATE ").append(collation);
639+
}
626640
if (formatJson) {
627641
builder.append(" FORMAT JSON");
628642
if (encoding != null) {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13264,6 +13264,8 @@ JsonTableFunction.JsonTableColumnDefinition JsonTableColumnDefinition() : {
1326413264
JsonTableFunction.JsonTableWrapperClause wrapperClause;
1326513265
JsonTableFunction.JsonTableQuotesClause quotesClause;
1326613266
String encoding;
13267+
String collation;
13268+
Token collationLiteral;
1326713269
}
1326813270
{
1326913271
(
@@ -13303,6 +13305,13 @@ JsonTableFunction.JsonTableColumnDefinition JsonTableColumnDefinition() : {
1330313305
getToken(1).image.equalsIgnoreCase("ALLOW")
1330413306
|| getToken(1).image.equalsIgnoreCase("DISALLOW"))) } )
1330513307
dataType = ColDataType() { valueColumnDefinition.setDataType(dataType); }
13308+
[ LOOKAHEAD({ Dialect.MYSQL.name().equals(getAsString(Feature.dialect))
13309+
&& getToken(1).kind == K_COLLATE })
13310+
<K_COLLATE>
13311+
( collation = RelObjectName()
13312+
| collationLiteral = <S_CHAR_LITERAL> { collation = collationLiteral.image; } )
13313+
{ valueColumnDefinition.setCollation(collation); }
13314+
]
1330613315
]
1330713316
[
1330813317
<K_FORMAT> <K_JSON> { valueColumnDefinition.setFormatJson(true); }
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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.expression;
11+
12+
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.expression.JsonTableFunction.JsonTableValueColumnDefinition;
14+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
15+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
16+
import net.sf.jsqlparser.statement.create.table.ColDataType;
17+
import net.sf.jsqlparser.statement.select.PlainSelect;
18+
import net.sf.jsqlparser.statement.select.TableFunction;
19+
import net.sf.jsqlparser.test.TestUtils;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
24+
import static org.junit.jupiter.api.Assertions.*;
25+
26+
class JsonTableCollationTest {
27+
@ParameterizedTest
28+
@ValueSource(strings = {
29+
"n VARCHAR(10) COLLATE utf8mb4_bin PATH '$.n'",
30+
"n VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin PATH '$.n'",
31+
"n VARCHAR(10) COLLATE `utf8mb4_bin` PATH '$.n'",
32+
"n VARCHAR(10) COLLATE 'utf8mb4_bin' PATH '$.n'",
33+
"n VARCHAR(10) COLLATE \"utf8mb4_bin\" PATH '$.n'",
34+
"NESTED PATH '$.items[*]' COLUMNS (n VARCHAR(10) COLLATE utf8mb4_bin PATH '$.n')"
35+
})
36+
void collatedColumnsRoundTrip(String column) throws JSQLParserException {
37+
PlainSelect select = parse(sql(column));
38+
parse(select.toString());
39+
}
40+
41+
@Test
42+
void collationCanBeInspectedEditedRemovedAndConstructed() throws JSQLParserException {
43+
PlainSelect select = parse(sql("n VARCHAR(10) COLLATE utf8mb4_bin PATH '$.n'"));
44+
JsonTableFunction table =
45+
(JsonTableFunction) ((TableFunction) select.getFromItem()).getFunction();
46+
JsonTableValueColumnDefinition column =
47+
(JsonTableValueColumnDefinition) table.getColumnsClause().getColumnDefinitions()
48+
.get(0);
49+
assertEquals("utf8mb4_bin", column.getCollation());
50+
assertEquals("VARCHAR", column.getDataType().getBaseTypeName());
51+
column.setCollation("utf8mb4_general_ci");
52+
TestUtils.assertStatementCanBeDeparsedAs(select,
53+
sql("n VARCHAR(10) COLLATE utf8mb4_general_ci PATH '$.n'"), true);
54+
parse(select.toString());
55+
column.setCollation(null);
56+
TestUtils.assertStatementCanBeDeparsedAs(select, sql("n VARCHAR(10) PATH '$.n'"), true);
57+
parse(select.toString());
58+
JsonTableValueColumnDefinition constructed = new JsonTableValueColumnDefinition()
59+
.setColumnName("v")
60+
.setDataType(new ColDataType("VARCHAR").addArgumentsStringList("10"))
61+
.setCollation("utf8mb4_bin").setPathExpression(new StringValue("$.v"));
62+
table.getColumnsClause().getColumnDefinitions().set(0, constructed);
63+
TestUtils.assertStatementCanBeDeparsedAs(select,
64+
sql("v VARCHAR(10) COLLATE utf8mb4_bin PATH '$.v'"), true);
65+
parse(select.toString());
66+
}
67+
68+
@Test
69+
void columnCollationRequiresMySqlDialect() {
70+
String sql = sql("n VARCHAR(10) COLLATE utf8mb4_bin PATH '$.n'");
71+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql));
72+
assertThrows(JSQLParserException.class,
73+
() -> CCJSqlParserUtil.parse(sql,
74+
parser -> parser.withDialect(Dialect.POSTGRESQL)));
75+
}
76+
77+
private static PlainSelect parse(String sql) throws JSQLParserException {
78+
return (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true,
79+
parser -> parser.withDialect(Dialect.MYSQL));
80+
}
81+
82+
private static String sql(String column) {
83+
return "SELECT * FROM JSON_TABLE('[{\"n\":\"ab\"}]', '$[*]' COLUMNS (" + column
84+
+ ")) AS jt";
85+
}
86+
}

0 commit comments

Comments
 (0)