Skip to content

Commit 7afe218

Browse files
committed
fix: share JSON_TABLE column response parsing
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 60c8dc2 commit 7afe218

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
@@ -13167,70 +13167,30 @@ JsonTableFunction.JsonTablePassingClause JsonTablePassingClause() : {
1316713167
}
1316813168
}
1316913169

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

1323613196
JsonTableFunction.JsonTableWrapperClause JsonTableWrapperClause(boolean beforePathExpr) : {
@@ -13371,20 +13331,20 @@ JsonTableFunction.JsonTableColumnDefinition JsonTableColumnDefinition() : {
1337113331
quotesClause = JsonTableQuotesClause() { valueColumnDefinition.setQuotesClause(quotesClause); }
1337213332
]
1337313333
[
13374-
LOOKAHEAD( JsonTableOnEmptyBehavior() <K_ON> <K_EMPTY> )
13375-
behavior = JsonTableOnEmptyBehavior()
13334+
LOOKAHEAD( JsonTableOnResponseBehavior() <K_ON> <K_EMPTY> )
13335+
behavior = JsonTableOnResponseBehavior()
1337613336
<K_ON> <K_EMPTY>
1337713337
{ valueColumnDefinition.setOnEmptyBehavior(behavior); }
1337813338
]
1337913339
[
13380-
LOOKAHEAD( JsonQueryOnResponseBehavior() <K_ON> <K_ERROR> )
13381-
behavior = JsonQueryOnResponseBehavior()
13340+
LOOKAHEAD( JsonTableOnResponseBehavior() <K_ON> <K_ERROR> )
13341+
behavior = JsonTableOnResponseBehavior()
1338213342
<K_ON> <K_ERROR>
1338313343
{ valueColumnDefinition.setOnErrorBehavior(behavior); }
1338413344
]
1338513345
[
13386-
LOOKAHEAD( JsonTableOnEmptyBehavior() <K_ON> <K_EMPTY> )
13387-
behavior = JsonTableOnEmptyBehavior()
13346+
LOOKAHEAD( JsonTableOnResponseBehavior() <K_ON> <K_EMPTY> )
13347+
behavior = JsonTableOnResponseBehavior()
1338813348
<K_ON> <K_EMPTY>
1338913349
{
1339013350
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)