diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java b/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java index 5b0f97b4c..f258e855c 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonExpression.java @@ -18,7 +18,7 @@ import java.util.Map; public class JsonExpression extends ASTNodeAccessImpl implements Expression { - private final List> idents = new ArrayList<>(); + private final List> idents = new ArrayList<>(); private Expression expr; public JsonExpression() { @@ -29,7 +29,7 @@ public JsonExpression(Expression expr) { this.expr = expr; } - public JsonExpression(Expression expr, List> idents) { + public JsonExpression(Expression expr, List> idents) { this.expr = expr; this.idents.addAll(idents); } @@ -47,26 +47,26 @@ public void setExpression(Expression expr) { this.expr = expr; } - public void addIdent(String ident, String operator) { + public void addIdent(Expression ident, String operator) { idents.add(new AbstractMap.SimpleEntry<>(ident, operator)); } - public void addAllIdents(Collection> idents) { + public void addAllIdents(Collection> idents) { this.idents.addAll(idents); } - public List> getIdentList() { + public List> getIdentList() { return idents; } - public Map.Entry getIdent(int index) { + public Map.Entry getIdent(int index) { return idents.get(index); } @Deprecated - public List getIdents() { - ArrayList l = new ArrayList<>(); - for (Map.Entry ident : idents) { + public List getIdents() { + ArrayList l = new ArrayList<>(); + for (Map.Entry ident : idents) { l.add(ident.getKey()); } @@ -76,7 +76,7 @@ public List getIdents() { @Deprecated public List getOperators() { ArrayList l = new ArrayList<>(); - for (Map.Entry ident : idents) { + for (Map.Entry ident : idents) { l.add(ident.getValue()); } return l; @@ -86,7 +86,7 @@ public List getOperators() { public String toString() { StringBuilder b = new StringBuilder(); b.append(expr.toString()); - for (Map.Entry ident : idents) { + for (Map.Entry ident : idents) { b.append(ident.getValue()).append(ident.getKey()); } return b.toString(); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 2e257c8b0..3393d6f76 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -4706,6 +4706,7 @@ Expression ArrayExpression(Expression obj): { Expression PrimaryExpression() #PrimaryExpression: { Expression retval = null; + Expression expression = null; CastExpression castExpr = null; TimezoneExpression timezoneExpr = null; Expression timezoneRightExpr = null; @@ -4718,7 +4719,7 @@ Expression PrimaryExpression() #PrimaryExpression: boolean dateExpressionAllowed = true; ExpressionList list; - final List> jsonIdents = new ArrayList>(); + final List> jsonIdents = new ArrayList>(); } { [ { not=true; } | "!" { not=true; exclamationMarkNot=true; } ] @@ -4846,14 +4847,17 @@ Expression PrimaryExpression() #PrimaryExpression: [ LOOKAHEAD(2) ( LOOKAHEAD(2) ( - "->" ( token= | token=) { jsonIdents.add(new AbstractMap.SimpleEntry(token.image,"->")); } + token="->" expression=Expression() | - "->>" (token= | token=) { jsonIdents.add(new AbstractMap.SimpleEntry(token.image,"->>")); } + token="->>" expression=Expression() | - "#>" token= { jsonIdents.add(new AbstractMap.SimpleEntry(token.image,"#>")); } + token="#>" expression=Expression() | - "#>>" token= { jsonIdents.add(new AbstractMap.SimpleEntry(token.image,"#>>")); } + token="#>>" expression=Expression() ) + { + jsonIdents.add(new AbstractMap.SimpleEntry(expression, token.image )); + } )+ retval = JsonExpression(retval, jsonIdents) ] @@ -5083,8 +5087,9 @@ StructType StructType() #StruckType: } } -JsonExpression JsonExpression(Expression expr, List> idents) : { +JsonExpression JsonExpression(Expression expr, List> idents) : { JsonExpression result = new JsonExpression(expr, idents); + Expression expression; Token token; ColDataType type = null; CastExpression castExpr = null; @@ -5111,15 +5116,18 @@ JsonExpression JsonExpression(Expression expr, List> i } ( - LOOKAHEAD(2) ( - "->" (token= | token=) {result.addIdent(token.image,"->");} - | - "->>" (token= | token=) {result.addIdent(token.image,"->>");} - | - "#>" token= {result.addIdent(token.image,"#>");} - | - "#>>" token= {result.addIdent(token.image,"#>>");} - ) + LOOKAHEAD(2) ( + token="->" expression=Expression() + | + token="->>" expression=Expression() + | + token="#>" expression=Expression() + | + token="#>>" expression=Expression() + ) + { + result.addIdent( expression, token.image ); + } )* )* diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java index 860a69382..60336b62f 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonExpressionTest.java @@ -11,6 +11,8 @@ import net.sf.jsqlparser.JSQLParserException; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; @@ -108,4 +110,27 @@ void testParenthesedJsonExpressionsIssue1792() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @ParameterizedTest + @ValueSource(strings = { + "SELECT ( JSONB_AGG(variables) " + + " FILTER (WHERE variables IS NOT NULL) " + + " OVER (PARTITION BY deviceid ORDER BY time)->>-1 )::JSONB AS variables\n" + + "FROM devices\n" + + ";", + "SELECT ( JSONB_AGG(variables) " + + " FILTER (WHERE variables IS NOT NULL) " + + " OVER (PARTITION BY deviceid ORDER BY time)->>(0-1) )::JSONB AS variables\n" + + + "FROM devices\n" + + ";", + "SELECT ( JSONB_AGG(variables) " + + " FILTER (WHERE variables IS NOT NULL) " + + " OVER (PARTITION BY deviceid ORDER BY time)->>(jsonb_array_length(JSONB_AGG(variables) FILTER (WHERE variables IS NOT NULL) OVER (PARTITION BY deviceid ORDER BY time))-1) )::JSONB AS variables\n" + + + "FROM devices\n" + + ";"}) + void testIssue2054(String sqlStr) throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java b/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java index 56d36d854..06d1e3d54 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/PostgresTest.java @@ -11,6 +11,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.JsonExpression; +import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statements; @@ -58,7 +59,7 @@ public void testJSonExpressionIssue1696() throws JSQLParserException { PlainSelect plainSelect = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sqlStr, true); SelectItem selectExpressionItem = plainSelect.getSelectItems().get(0); - Assertions.assertEquals("'key'", + Assertions.assertEquals(new StringValue("key"), selectExpressionItem.getExpression(JsonExpression.class).getIdent(0).getKey()); }