Skip to content

Commit 83ca164

Browse files
committed
Keep JSON paths nested inside a subscript
`l[(SELECT c:a FROM t)]`, `l[f(x, c:a)]` and `l[CASE WHEN c:a = 1 ...]` failed: the subscript depth disabled the JSON path operator in every expression nested in the subscript. Only a `:` at the subscript's own top level, outside brackets, braces, parentheses and CASE, now separates slice bounds.
1 parent 4ac1d1b commit 83ca164

2 files changed

Lines changed: 50 additions & 11 deletions

File tree

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

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,31 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
203203
// and must not be taken as the JSON path operator
204204
private int mapKeyDepth = 0;
205205

206-
// depth of subscripts: a ":" there separates the bounds of a slice such as l[1:3]
207-
// and must not be taken as the JSON path operator
208-
private int subscriptDepth = 0;
206+
// opening brackets of the enclosing subscripts: a ":" at the top level of a subscript
207+
// separates the bounds of a slice such as l[1:3] and is not the JSON path operator
208+
private final Deque<Token> subscriptOpenings = new ArrayDeque<Token>();
209+
210+
/**
211+
* Whether the pending ":" is at the top level of the innermost subscript, i.e. not nested
212+
* in brackets, braces, parentheses or a CASE expression opened inside that subscript.
213+
*/
214+
private boolean isSliceColonAhead() {
215+
Token colon = getToken(1);
216+
if (subscriptOpenings.isEmpty() || !":".equals(colon.image)) {
217+
return false;
218+
}
219+
int depth = 0;
220+
for (Token t = subscriptOpenings.peek().next; t != null && t != colon; t = t.next) {
221+
if (t.kind == K_CASE || "(".equals(t.image) || "[".equals(t.image)
222+
|| "{".equals(t.image)) {
223+
depth++;
224+
} else if (t.kind == K_END || ")".equals(t.image) || "]".equals(t.image)
225+
|| "}".equals(t.image)) {
226+
depth--;
227+
}
228+
}
229+
return depth == 0;
230+
}
209231

210232
private Expression createOutputArgument(Expression argument) throws ParseException {
211233
Expression value = argument;
@@ -11358,14 +11380,15 @@ Expression ArrayExpression(Expression obj): {
1135811380
ArrayExpression ArraySubscript(Expression obj): {
1135911381
Expression idxExpr = null;
1136011382
RangeExpression slice = null;
11383+
Token opening;
1136111384
} {
11362-
"["
11385+
opening="["
1136311386
try {
11364-
{ subscriptDepth++; }
11387+
{ subscriptOpenings.push(opening); }
1136511388
[ LOOKAHEAD(3, { !":".equals(getToken(1).image) }) idxExpr = ArrayIndexExpression() ]
1136611389
[ slice = SliceTail(idxExpr) ]
1136711390
} finally {
11368-
subscriptDepth--;
11391+
subscriptOpenings.pop();
1136911392
}
1137011393
"]"
1137111394
{
@@ -11400,16 +11423,17 @@ RangeExpression SliceTail(Expression start): {
1140011423
ArrayConstructor Subscript() : {
1140111424
ExpressionList<Expression> expList = new ExpressionList<Expression>();
1140211425
Expression exp;
11426+
Token opening;
1140311427
} {
11404-
"["
11428+
opening="["
1140511429
try {
11406-
{ subscriptDepth++; }
11430+
{ subscriptOpenings.push(opening); }
1140711431
[
1140811432
exp=SubscriptElement() { expList.add(exp); }
1140911433
( "," exp=SubscriptElement() { expList.add(exp); } )*
1141011434
]
1141111435
} finally {
11412-
subscriptDepth--;
11436+
subscriptOpenings.pop();
1141311437
}
1141411438
"]"
1141511439
{ return new ArrayConstructor(expList, false); }
@@ -11680,7 +11704,7 @@ Expression PrimaryExpression() #PrimaryExpression:
1168011704
// Check for JSON operands
1168111705
[
1168211706
LOOKAHEAD(2, { !isPostgreSqlJsonAccess(getToken(1))
11683-
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0 && subscriptDepth == 0)
11707+
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0 && !isSliceColonAhead())
1168411708
|| !":".equals(getToken(1).image)) }) (
1168511709
LOOKAHEAD(2) (
1168611710
token="->"
@@ -12045,7 +12069,7 @@ JsonExpression JsonExpression(Expression expr, List<Map.Entry<Expression, String
1204512069

1204612070
(
1204712071
LOOKAHEAD(2, { !isPostgreSqlJsonAccess(getToken(1))
12048-
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0 && subscriptDepth == 0)
12072+
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0 && !isSliceColonAhead())
1204912073
|| !":".equals(getToken(1).image)) }) (
1205012074
token="->"
1205112075
|

‎src/test/java/net/sf/jsqlparser/expression/ArrayExpressionTest.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,19 @@ void testEmptyStepIsDropped() throws JSQLParserException {
185185
void testJsonPathOutsideSubscripts() throws JSQLParserException {
186186
assertInstanceOf(JsonExpression.class, CCJSqlParserUtil.parseExpression("c:a[1]"));
187187
}
188+
189+
@Test
190+
void testJsonPathNestedInSubscript() throws JSQLParserException {
191+
for (String sqlStr : new String[] {"SELECT l[(SELECT c : a FROM t2)] FROM t",
192+
"SELECT l[f(x, c : a)] FROM t", "SELECT l[CASE WHEN c : a = 1 THEN 1 END] FROM t",
193+
"SELECT f()[(SELECT c : a FROM t)]"}) {
194+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
195+
}
196+
197+
PlainSelect select = (PlainSelect) TestUtils
198+
.assertSqlCanBeParsedAndDeparsed("SELECT l[ARRAY[c : a]] FROM t", true);
199+
ArrayConstructor array = (ArrayConstructor) select.getSelectItem(0)
200+
.getExpression(Column.class).getArrayConstructor().getExpressions().get(0);
201+
assertInstanceOf(JsonExpression.class, array.getExpressions().get(0));
202+
}
188203
}

0 commit comments

Comments
 (0)