Skip to content

Commit 52e81a6

Browse files
authored
fix: share JSON_TABLE column response parsing (#2683)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 0031943 commit 52e81a6

2 files changed

Lines changed: 124 additions & 58 deletions

File tree

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

Lines changed: 18 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -13168,70 +13168,30 @@ JsonTableFunction.JsonTablePassingClause JsonTablePassingClause() : {
1316813168
}
1316913169
}
1317013170

13171+
/** Compatibility entry point; both column response clauses use the same behaviors. */
1317113172
JsonFunction.JsonOnResponseBehavior JsonTableOnEmptyBehavior() : {
13172-
JsonFunction.JsonOnResponseBehavior behavior = null;
13173+
JsonFunction.JsonOnResponseBehavior behavior;
13174+
}
13175+
{
13176+
behavior = JsonTableOnResponseBehavior()
13177+
{ return behavior; }
13178+
}
13179+
13180+
JsonFunction.JsonOnResponseBehavior JsonTableOnResponseBehavior() : {
13181+
JsonFunction.JsonOnResponseBehavior behavior;
1317313182
Expression expression;
13174-
Token token;
1317513183
}
1317613184
{
1317713185
(
13178-
<K_ERROR>
13179-
{
13180-
behavior = new JsonFunction.JsonOnResponseBehavior(
13181-
JsonFunction.JsonOnResponseBehaviorType.ERROR);
13182-
}
13183-
|
13184-
<K_NULL>
13185-
{
13186-
behavior = new JsonFunction.JsonOnResponseBehavior(
13187-
JsonFunction.JsonOnResponseBehaviorType.NULL);
13188-
}
13189-
|
13190-
<K_TRUE>
13191-
{
13192-
behavior = new JsonFunction.JsonOnResponseBehavior(
13193-
JsonFunction.JsonOnResponseBehaviorType.TRUE);
13194-
}
13195-
|
13196-
<K_FALSE>
13197-
{
13198-
behavior = new JsonFunction.JsonOnResponseBehavior(
13199-
JsonFunction.JsonOnResponseBehaviorType.FALSE);
13200-
}
13201-
|
1320213186
<K_DEFAULT> expression = Expression()
1320313187
{
1320413188
behavior = new JsonFunction.JsonOnResponseBehavior(
1320513189
JsonFunction.JsonOnResponseBehaviorType.DEFAULT, expression);
1320613190
}
1320713191
|
13208-
token = <S_IDENTIFIER>
13209-
{
13210-
if (!token.image.equalsIgnoreCase("EMPTY")) {
13211-
throw new ParseException(
13212-
"Expected EMPTY, ERROR, NULL or DEFAULT but found " + token.image);
13213-
}
13214-
}
13215-
(
13216-
LOOKAHEAD({ getToken(1).kind == S_IDENTIFIER && getToken(1).image.equalsIgnoreCase("OBJECT") })
13217-
JsonKeyword("OBJECT")
13218-
{
13219-
behavior = new JsonFunction.JsonOnResponseBehavior(
13220-
JsonFunction.JsonOnResponseBehaviorType.EMPTY_OBJECT);
13221-
}
13222-
|
13223-
[ <K_ARRAY_LITERAL> ]
13224-
{
13225-
behavior = new JsonFunction.JsonOnResponseBehavior(
13226-
JsonFunction.JsonOnResponseBehaviorType.EMPTY_ARRAY);
13227-
}
13228-
)
13192+
behavior = JsonQueryOnResponseBehavior()
1322913193
)
13230-
{
13231-
if (behavior != null) {
13232-
return behavior;
13233-
}
13234-
}
13194+
{ return behavior; }
1323513195
}
1323613196

1323713197
JsonTableFunction.JsonTableWrapperClause JsonTableWrapperClause(boolean beforePathExpr) : {
@@ -13372,20 +13332,20 @@ JsonTableFunction.JsonTableColumnDefinition JsonTableColumnDefinition() : {
1337213332
quotesClause = JsonTableQuotesClause() { valueColumnDefinition.setQuotesClause(quotesClause); }
1337313333
]
1337413334
[
13375-
LOOKAHEAD( JsonTableOnEmptyBehavior() <K_ON> <K_EMPTY> )
13376-
behavior = JsonTableOnEmptyBehavior()
13335+
LOOKAHEAD( JsonTableOnResponseBehavior() <K_ON> <K_EMPTY> )
13336+
behavior = JsonTableOnResponseBehavior()
1337713337
<K_ON> <K_EMPTY>
1337813338
{ valueColumnDefinition.setOnEmptyBehavior(behavior); }
1337913339
]
1338013340
[
13381-
LOOKAHEAD( JsonQueryOnResponseBehavior() <K_ON> <K_ERROR> )
13382-
behavior = JsonQueryOnResponseBehavior()
13341+
LOOKAHEAD( JsonTableOnResponseBehavior() <K_ON> <K_ERROR> )
13342+
behavior = JsonTableOnResponseBehavior()
1338313343
<K_ON> <K_ERROR>
1338413344
{ valueColumnDefinition.setOnErrorBehavior(behavior); }
1338513345
]
1338613346
[
13387-
LOOKAHEAD( JsonTableOnEmptyBehavior() <K_ON> <K_EMPTY> )
13388-
behavior = JsonTableOnEmptyBehavior()
13347+
LOOKAHEAD( JsonTableOnResponseBehavior() <K_ON> <K_EMPTY> )
13348+
behavior = JsonTableOnResponseBehavior()
1338913349
<K_ON> <K_EMPTY>
1339013350
{
1339113351
valueColumnDefinition.setOnEmptyBehavior(behavior);
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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.statement.select.PlainSelect;
16+
import net.sf.jsqlparser.statement.select.TableFunction;
17+
import net.sf.jsqlparser.test.TestUtils;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.params.ParameterizedTest;
20+
import org.junit.jupiter.params.provider.ValueSource;
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
import static org.junit.jupiter.api.Assertions.*;
26+
27+
class JsonTableResponseTest {
28+
@ParameterizedTest
29+
@ValueSource(strings = {
30+
"DEFAULT '9' ON ERROR",
31+
"DEFAULT '0' ON EMPTY DEFAULT '9' ON ERROR",
32+
"DEFAULT '9' ON ERROR DEFAULT '0' ON EMPTY",
33+
"NULL ON EMPTY DEFAULT '9' ON ERROR",
34+
"ERROR ON EMPTY DEFAULT '9' ON ERROR"
35+
})
36+
void defaultsRoundTripInEitherOrder(String response) throws JSQLParserException {
37+
String sql = sql("n INT PATH '$.n' " + response);
38+
for (Dialect dialect : new Dialect[] {null, Dialect.MYSQL, Dialect.ORACLE}) {
39+
PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(sql,
40+
true, parser -> {
41+
if (dialect != null) {
42+
parser.withDialect(dialect);
43+
}
44+
});
45+
TestUtils.assertSqlCanBeParsedAndDeparsed(select.toString(), false,
46+
parser -> {
47+
if (dialect != null) {
48+
parser.withDialect(dialect);
49+
}
50+
});
51+
}
52+
}
53+
54+
@Test
55+
void defaultExpressionsAreVisitedAndEditable() throws JSQLParserException {
56+
PlainSelect select = (PlainSelect) TestUtils.assertSqlCanBeParsedAndDeparsed(
57+
sql("n INT PATH '$.n' DEFAULT '0' ON EMPTY DEFAULT '9' ON ERROR"));
58+
JsonTableFunction table =
59+
(JsonTableFunction) ((TableFunction) select.getFromItem()).getFunction();
60+
JsonTableValueColumnDefinition column =
61+
(JsonTableValueColumnDefinition) table.getColumnsClause().getColumnDefinitions()
62+
.get(0);
63+
assertEquals(JsonFunction.JsonOnResponseBehaviorType.DEFAULT,
64+
column.getOnErrorBehavior().getType());
65+
assertEquals("9", ((StringValue) column.getOnErrorBehavior().getExpression()).getValue());
66+
List<String> visited = new ArrayList<>();
67+
table.accept(new ExpressionVisitorAdapter<Void>() {
68+
@Override
69+
public <S> Void visit(StringValue value, S context) {
70+
visited.add(value.getValue());
71+
if ("9".equals(value.getValue())) {
72+
value.setValue("7");
73+
}
74+
return null;
75+
}
76+
}, null);
77+
assertTrue(visited.containsAll(List.of("$.n", "0", "9")));
78+
TestUtils.assertStatementCanBeDeparsedAs(select,
79+
sql("n INT PATH '$.n' DEFAULT '0' ON EMPTY DEFAULT '7' ON ERROR"), true);
80+
column.setOnErrorBehavior(new JsonFunction.JsonOnResponseBehavior(
81+
JsonFunction.JsonOnResponseBehaviorType.DEFAULT, new StringValue("8")));
82+
column.setOnEmptyAfterOnError(true);
83+
TestUtils.assertStatementCanBeDeparsedAs(select,
84+
sql("n INT PATH '$.n' DEFAULT '8' ON ERROR DEFAULT '0' ON EMPTY"), true);
85+
TestUtils.assertSqlCanBeParsedAndDeparsed(select.toString());
86+
}
87+
88+
@Test
89+
void nestedDefaultsRoundTrip() throws JSQLParserException {
90+
TestUtils.assertSqlCanBeParsedAndDeparsed(sql(
91+
"NESTED PATH '$.items[*]' COLUMNS (n INT PATH '$.n' DEFAULT '9' ON ERROR)"));
92+
}
93+
94+
@ParameterizedTest
95+
@ValueSource(
96+
strings = {"NULL", "ERROR", "TRUE", "FALSE", "EMPTY", "EMPTY ARRAY", "EMPTY OBJECT"})
97+
void sharedNonDefaultResponsesRoundTrip(String behavior) throws JSQLParserException {
98+
TestUtils.assertSqlCanBeParsedAndDeparsed(sql("n PATH '$.n' " + behavior + " ON ERROR"));
99+
TestUtils.assertSqlCanBeParsedAndDeparsed(sql("n PATH '$.n' " + behavior + " ON EMPTY"));
100+
}
101+
102+
private static String sql(String column) {
103+
return "SELECT * FROM JSON_TABLE('[{\"n\":1},{},{\"n\":\"bad\"}]', '$[*]' COLUMNS ("
104+
+ column + ")) AS jt";
105+
}
106+
}

0 commit comments

Comments
 (0)