Skip to content

Commit fafeb79

Browse files
authored
fix: parse compound expressions in array constructors (#2672)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 0fa2a7f commit fafeb79

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
@@ -433,7 +433,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
433433
// True when a fresh evaluation started at this token would pass all
434434
// first-token guards and run the same chain walk.
435435
private boolean isFunctionAheadChainStartEligible(Token t) {
436-
return !t.image.equals("{") && t.kind != K_APPROXIMATE && !isNonFunctionKeyword(t)
436+
return !t.image.equals("{") && !t.image.equals("[") && t.kind != K_APPROXIMATE && !isNonFunctionKeyword(t)
437437
&& t.kind != S_LONG && t.kind != S_DOUBLE && t.kind != S_HEX
438438
&& t.kind != S_CHAR_LITERAL && t.kind != OPENING_BRACKET
439439
&& t.kind != CLOSING_BRACKET && t.kind != EOF;
@@ -466,7 +466,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
466466
// First token must not be a literal, bracket, or EOF
467467
if (t.kind == S_LONG || t.kind == S_DOUBLE || t.kind == S_HEX
468468
|| t.kind == S_CHAR_LITERAL || t.kind == OPENING_BRACKET
469-
|| t.kind == CLOSING_BRACKET || t.kind == EOF) {
469+
|| t.kind == CLOSING_BRACKET || t.kind == EOF || "[".equals(t.image)) {
470470
return 0;
471471
}
472472
i++;
@@ -10986,7 +10986,7 @@ Expression PrimaryExpression() #PrimaryExpression:
1098610986

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

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

1099110991
| LOOKAHEAD(2, {!interrupted}) retval = NextValExpression()
1099210992

@@ -11289,29 +11289,25 @@ RangeExpression RangeExpression(Expression startExpression):
1128911289
}
1129011290

1129111291
ArrayConstructor ArrayConstructor(boolean arrayKeyword) : {
11292-
ExpressionList expList = new ExpressionList();
11293-
ArrayConstructor array = new ArrayConstructor(expList, arrayKeyword);
11292+
ExpressionList<Expression> expList = new ExpressionList<Expression>();
1129411293
Expression exp;
1129511294
} {
1129611295
"["
1129711296
[
11298-
(
11299-
LOOKAHEAD(3) exp = Expression() [ exp=RangeExpression(exp) ]
11300-
|
11301-
exp = ArrayConstructor(false)
11302-
) { expList.add(exp); }
11303-
11304-
(
11305-
","
11306-
(
11307-
LOOKAHEAD(3) exp = Expression() [ exp=RangeExpression(exp) ]
11308-
|
11309-
exp = ArrayConstructor(false)
11310-
){ expList.add(exp); }
11311-
)*
11297+
exp=ArrayElement() { expList.add(exp); }
11298+
( "," exp=ArrayElement() { expList.add(exp); } )*
1131211299
]
1131311300
"]"
11314-
{ return array; }
11301+
{ return new ArrayConstructor(expList, arrayKeyword); }
11302+
}
11303+
11304+
Expression ArrayElement():
11305+
{
11306+
Expression expression;
11307+
}
11308+
{
11309+
expression=Expression() [ expression=RangeExpression(expression) ]
11310+
{ return expression; }
1131511311
}
1131611312

1131711313
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)