Skip to content

Commit 4fe0ee7

Browse files
committed
fix: parse compound expressions in array constructors
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 7f91819 commit 4fe0ee7

2 files changed

Lines changed: 126 additions & 20 deletions

File tree

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

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
432432
// True when a fresh evaluation started at this token would pass all
433433
// first-token guards and run the same chain walk.
434434
private boolean isFunctionAheadChainStartEligible(Token t) {
435-
return !t.image.equals("{") && t.kind != K_APPROXIMATE && !isNonFunctionKeyword(t)
435+
return !t.image.equals("{") && !t.image.equals("[") && t.kind != K_APPROXIMATE && !isNonFunctionKeyword(t)
436436
&& t.kind != S_LONG && t.kind != S_DOUBLE && t.kind != S_HEX
437437
&& t.kind != S_CHAR_LITERAL && t.kind != OPENING_BRACKET
438438
&& t.kind != CLOSING_BRACKET && t.kind != EOF;
@@ -465,7 +465,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
465465
// First token must not be a literal, bracket, or EOF
466466
if (t.kind == S_LONG || t.kind == S_DOUBLE || t.kind == S_HEX
467467
|| t.kind == S_CHAR_LITERAL || t.kind == OPENING_BRACKET
468-
|| t.kind == CLOSING_BRACKET || t.kind == EOF) {
468+
|| t.kind == CLOSING_BRACKET || t.kind == EOF || "[".equals(t.image)) {
469469
return 0;
470470
}
471471
i++;
@@ -10939,7 +10939,7 @@ Expression PrimaryExpression() #PrimaryExpression:
1093910939

1094010940
| LOOKAHEAD(3, {!interrupted}) <K_ARRAY_LITERAL> [ "<" type=ColDataType() ">" ] retval=ArrayConstructor(true) { if (type!=null) ((ArrayConstructor) retval).setDataType(type); }
1094110941

10942-
| LOOKAHEAD(3, {!interrupted}) retval=ArrayConstructor(false)
10942+
| LOOKAHEAD(1, {!interrupted}) retval=ArrayConstructor(false)
1094310943

1094410944
| LOOKAHEAD(2, {!interrupted}) retval = NextValExpression()
1094510945

@@ -11242,29 +11242,25 @@ RangeExpression RangeExpression(Expression startExpression):
1124211242
}
1124311243

1124411244
ArrayConstructor ArrayConstructor(boolean arrayKeyword) : {
11245-
ExpressionList expList = new ExpressionList();
11246-
ArrayConstructor array = new ArrayConstructor(expList, arrayKeyword);
11245+
ExpressionList<Expression> expList = new ExpressionList<Expression>();
1124711246
Expression exp;
1124811247
} {
1124911248
"["
1125011249
[
11251-
(
11252-
LOOKAHEAD(3) exp = Expression() [ exp=RangeExpression(exp) ]
11253-
|
11254-
exp = ArrayConstructor(false)
11255-
) { expList.add(exp); }
11256-
11257-
(
11258-
","
11259-
(
11260-
LOOKAHEAD(3) exp = Expression() [ exp=RangeExpression(exp) ]
11261-
|
11262-
exp = ArrayConstructor(false)
11263-
){ expList.add(exp); }
11264-
)*
11250+
exp=ArrayElement() { expList.add(exp); }
11251+
( "," exp=ArrayElement() { expList.add(exp); } )*
1126511252
]
1126611253
"]"
11267-
{ return array; }
11254+
{ return new ArrayConstructor(expList, arrayKeyword); }
11255+
}
11256+
11257+
Expression ArrayElement():
11258+
{
11259+
Expression expression;
11260+
}
11261+
{
11262+
expression=Expression() [ expression=RangeExpression(expression) ]
11263+
{ return expression; }
1126811264
}
1126911265

1127011266
MapExpression MapExpression() #MapExpression:
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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 static org.assertj.core.api.Assertions.assertThat;
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
import java.util.ArrayList;
16+
import java.util.List;
17+
import net.sf.jsqlparser.JSQLParserException;
18+
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
19+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
20+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
21+
import net.sf.jsqlparser.statement.Statement;
22+
import net.sf.jsqlparser.statement.select.PlainSelect;
23+
import net.sf.jsqlparser.util.TablesNamesFinder;
24+
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
25+
import net.sf.jsqlparser.util.deparser.SelectDeParser;
26+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.ValueSource;
30+
31+
class ArrayConstructorTest {
32+
@ParameterizedTest
33+
@ValueSource(strings = {"ARRAY['a', (1 + 0)::text]", "ARRAY[(1 + 2), 3]",
34+
"ARRAY[1, (2 + 3), 4]", "ARRAY[1, ((2 * 3) + 4)]",
35+
"ARRAY[(SELECT 1), 2]", "ARRAY[NULL, COALESCE(NULL, (2 + 3))]",
36+
"ARRAY[CASE WHEN 1 < 2 THEN (3 + 4) ELSE 5 END, 6]",
37+
"ARRAY[ARRAY[(1 + 2), 3], ARRAY[4, (5 + 6)]]",
38+
"ARRAY[[(1 + 2), 3], [4, (5 + 6)]]", "ARRAY[]::integer[]",
39+
"ARRAY[ROW(1, 2), ROW(3, 4)]", "ARRAY[(1 < 2), (3 > 4)]"})
40+
void parsesGeneralExpressionsAtEveryArrayPosition(String expression) throws Exception {
41+
for (boolean complex : List.of(false, true)) {
42+
Statement statement = CCJSqlParserUtil.parse("SELECT " + expression,
43+
p -> p.withDialect(Dialect.POSTGRESQL).withAllowComplexParsing(complex));
44+
roundTrip(statement);
45+
}
46+
}
47+
48+
@Test
49+
void retainsCastAndParenthesesInTheElementAst() throws Exception {
50+
PlainSelect select = (PlainSelect) parse("SELECT ARRAY['a', (1 + 0)::text]");
51+
ArrayConstructor array = assertInstanceOf(ArrayConstructor.class,
52+
select.getSelectItem(0).getExpression());
53+
assertEquals(2, array.getExpressions().size());
54+
CastExpression cast = assertInstanceOf(CastExpression.class, array.getExpressions().get(1));
55+
assertInstanceOf(ParenthesedExpressionList.class, cast.getLeftExpression());
56+
cast.setLeftExpression(new LongValue(42));
57+
assertEquals("SELECT ARRAY['a', 42::text]", select.toString());
58+
roundTrip(select);
59+
}
60+
61+
@Test
62+
void visitsElementsAndFindsTablesInScalarSubqueries() throws Exception {
63+
Statement statement = parse("SELECT ARRAY[1, (SELECT value FROM hidden), (2 + 3)]");
64+
assertThat(new TablesNamesFinder().getTables(statement)).containsExactly("hidden");
65+
List<Long> seen = new ArrayList<>();
66+
ExpressionDeParser expressions = new ExpressionDeParser() {
67+
@Override
68+
public <S> StringBuilder visit(LongValue value, S context) {
69+
seen.add(value.getValue());
70+
return getBuilder().append(value.getValue() + 10);
71+
}
72+
};
73+
StringBuilder output = new StringBuilder();
74+
statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null);
75+
assertEquals(List.of(1L, 2L, 3L), seen);
76+
assertEquals("SELECT ARRAY[11, (SELECT value FROM hidden), (12 + 13)]", output.toString());
77+
roundTrip(parse(output.toString()));
78+
}
79+
80+
@ParameterizedTest
81+
@ValueSource(strings = {"SELECT ARRAY[1,]", "SELECT ARRAY[,1]", "SELECT ARRAY[(1 +)]",
82+
"SELECT ARRAY[1, (2 + 3]", "SELECT ARRAY[[1,2],]"})
83+
void rejectsIncompleteElements(String sql) {
84+
assertThrows(JSQLParserException.class, () -> parse(sql));
85+
}
86+
87+
@ParameterizedTest
88+
@ValueSource(strings = {"SELECT [1, (2 + 3)]", "SELECT [(1 + 2), 3]",
89+
"SELECT ARRAY<INT64>[1, (2 + 3)]",
90+
"SELECT ARRAY[1:3]", "SELECT a[(1 + 2)] FROM t",
91+
"SELECT a.b[1:2].c FROM t", "SELECT ARRAY[ARRAY[], ARRAY[]]"})
92+
void preservesExistingArrayAndSubscriptForms(String sql) throws Exception {
93+
Statement statement = CCJSqlParserUtil.parse(sql);
94+
StringBuilder output = new StringBuilder();
95+
statement.accept(new StatementDeParser(output), null);
96+
assertEquals(statement.toString(), output.toString());
97+
assertEquals(output.toString(), CCJSqlParserUtil.parse(output.toString()).toString());
98+
}
99+
100+
private static Statement parse(String sql) throws JSQLParserException {
101+
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
102+
}
103+
104+
private static void roundTrip(Statement statement) throws Exception {
105+
StringBuilder output = new StringBuilder();
106+
statement.accept(new StatementDeParser(output), null);
107+
assertEquals(statement.toString(), output.toString());
108+
assertEquals(output.toString(), parse(output.toString()).toString());
109+
}
110+
}

0 commit comments

Comments
 (0)