From 822517899546f19cd04ac40952535e47eacfbe9e Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sat, 30 Nov 2024 06:35:37 +0700 Subject: [PATCH] fix: JSon functions with Column parameters - fixes #1753 Signed-off-by: Andreas Reichel --- build.gradle | 6 +- .../expression/JsonAggregateFunction.java | 11 +- .../expression/JsonKeyValuePair.java | 6 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 193 ++++++++++-------- .../expression/JsonFunctionTest.java | 13 ++ 5 files changed, 138 insertions(+), 91 deletions(-) diff --git a/build.gradle b/build.gradle index 66d4d27f9..9816e74a4 100644 --- a/build.gradle +++ b/build.gradle @@ -95,9 +95,9 @@ dependencies { testImplementation 'com.h2database:h2:+' // for JaCoCo Reports - testImplementation 'org.junit.jupiter:junit-jupiter-api:+' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:+' - testImplementation 'org.junit.jupiter:junit-jupiter-params:+' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.3' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.3' // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter testImplementation 'org.mockito:mockito-junit-jupiter:+' diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java index 13e3ba296..20bfa272e 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java @@ -23,7 +23,7 @@ public class JsonAggregateFunction extends FilterOverImpl implements Expression private JsonFunctionType functionType; private Expression expression = null; private boolean usingKeyKeyword = false; - private String key; + private Object key; private boolean usingValueKeyword = false; private Object value; @@ -112,15 +112,15 @@ public JsonAggregateFunction withUsingKeyKeyword(boolean usingKeyKeyword) { return this; } - public String getKey() { + public Object getKey() { return key; } - public void setKey(String key) { + public void setKey(Object key) { this.key = key; } - public JsonAggregateFunction withKey(String key) { + public JsonAggregateFunction withKey(Object key) { this.setKey(key); return this; } @@ -188,6 +188,7 @@ public T accept(ExpressionVisitor expressionVisitor, S context) { public StringBuilder append(StringBuilder builder) { switch (functionType) { case OBJECT: + case MYSQL_OBJECT: appendObject(builder); break; case ARRAY: @@ -209,6 +210,8 @@ public StringBuilder appendObject(StringBuilder builder) { builder.append("KEY "); } builder.append(key).append(" VALUE ").append(value); + } else if (functionType == JsonFunctionType.MYSQL_OBJECT) { + builder.append(key).append(", ").append(value); } else { builder.append(key).append(":").append(value); } diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java index f1119071d..82c8a355a 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java @@ -18,13 +18,13 @@ */ public class JsonKeyValuePair implements Serializable { - private final String key; + private final Object key; private final Object value; private boolean usingKeyKeyword = false; private boolean usingValueKeyword = false; private boolean usingFormatJson = false; - public JsonKeyValuePair(String key, Object value, boolean usingKeyKeyword, + public JsonKeyValuePair(Object key, Object value, boolean usingKeyKeyword, boolean usingValueKeyword) { this.key = Objects.requireNonNull(key, "The KEY of the Pair must not be null"); this.value = value; @@ -93,7 +93,7 @@ public boolean equals(Object obj) { return Objects.equals(this.key, other.key); } - public String getKey() { + public Object getKey() { return key; } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4d26413b5..ebfa58af6 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -5193,101 +5193,109 @@ JsonFunction JsonFunction() : { Column column = null; JsonKeyValuePair keyValuePair; + Object key = null; Expression expression = null; JsonFunctionExpression functionExpression; } { ( - ( - ( - "(" { result.setType( JsonFunctionType.OBJECT ); } - ( + ( + "(" { result.setType( JsonFunctionType.OBJECT ); } + ( + // SQL2016 compliant Syntax + LOOKAHEAD(2) ( + LOOKAHEAD(2) ( + "KEY" { usingKeyKeyword = true; } ( keyToken = { key = keyToken.image; } | key = Column() ) + ) + | + keyToken = { key = keyToken.image; } + | + key = Column() + ) + + ( LOOKAHEAD(2) + ( ":" | "," { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) ( - // SQL2016 compliant Syntax - ( - [ "KEY" { usingKeyKeyword = true; } ] - keyToken = + expression = Expression() + ) + [ { usingFormatJason = true; } ] + )? + { + if (expression !=null) { + keyValuePair = new JsonKeyValuePair( key, expression, usingKeyKeyword, usingValueKeyword ); + keyValuePair.setUsingFormatJson( usingFormatJason ); + result.add(keyValuePair); + } else { + result.setType( JsonFunctionType.POSTGRES_OBJECT ); + keyValuePair = new JsonKeyValuePair( key, null, false, false ); + result.add(keyValuePair); + } + } - ( LOOKAHEAD(2) - ( ":" | "," { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) - ( - expression = Expression() - ) - [ { usingFormatJason = true; } ] - )? { - if (expression !=null) { - keyValuePair = new JsonKeyValuePair( keyToken.image, expression, usingKeyKeyword, usingValueKeyword ); - keyValuePair.setUsingFormatJson( usingFormatJason ); - result.add(keyValuePair); - } else { - result.setType( JsonFunctionType.POSTGRES_OBJECT ); - keyValuePair = new JsonKeyValuePair( keyToken.image, null, false, false ); - result.add(keyValuePair); - } - } - - // --- Next Elements - ( "," { usingKeyKeyword = false; usingValueKeyword = false; } - [ "KEY" { usingKeyKeyword = true; } ] - keyToken = - ( ":" | "," { result.setType( JsonFunctionType.MYSQL_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) - ( - expression = Expression() { keyValuePair = new JsonKeyValuePair( keyToken.image, expression, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - ) - [ { keyValuePair.setUsingFormatJson( true ); } ] - )* + // --- Next Elements + ( "," { usingKeyKeyword = false; usingValueKeyword = false; } + ( + LOOKAHEAD(2) ( + "KEY" { usingKeyKeyword = true; } ( keyToken = { key = keyToken.image; } | key = Column() ) ) - )? + | + keyToken = { key = keyToken.image; } + | + key = Column() + ) + ( ":" | "," { result.setType( JsonFunctionType.MYSQL_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) + expression = Expression() { keyValuePair = new JsonKeyValuePair( key, expression, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + [ { keyValuePair.setUsingFormatJson( true ); } ] + )* + )? - [ - ( - { result.setOnNullType( JsonAggregateOnNullType.NULL ); } - ) - | - ( - { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } - ) - ] + [ + ( + { result.setOnNullType( JsonAggregateOnNullType.NULL ); } + ) + | + ( + { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } + ) + ] - [ - ( - { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); } - ) - | - ( - { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); } - ) - ] + [ + ( + { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); } + ) + | + ( + { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); } + ) + ] + ")" + ) + | + ( + { result.setType( JsonFunctionType.ARRAY ); } + "(" + ( + LOOKAHEAD(2) ( + { result.setOnNullType( JsonAggregateOnNullType.NULL ); } ) - ")" - ) - | - ( - { result.setType( JsonFunctionType.ARRAY ); } - "(" + | + expression=Expression() { functionExpression = new JsonFunctionExpression( expression ); result.add( functionExpression ); } + + [ LOOKAHEAD(2) { functionExpression.setUsingFormatJson( true ); } ] ( - LOOKAHEAD(2) ( - { result.setOnNullType( JsonAggregateOnNullType.NULL ); } - ) - | + "," expression=Expression() { functionExpression = new JsonFunctionExpression( expression ); result.add( functionExpression ); } - [ LOOKAHEAD(2) { functionExpression.setUsingFormatJson( true ); } ] - ( - "," - expression=Expression() { functionExpression = new JsonFunctionExpression( expression ); result.add( functionExpression ); } - [ LOOKAHEAD(2) { functionExpression.setUsingFormatJson( true ); } ] - )* )* + )* - [ - { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } - ] + [ + { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } + ] - ")" - ) - ) + ")" + ) ) { @@ -5298,6 +5306,7 @@ JsonFunction JsonFunction() : { JsonAggregateFunction JsonAggregateFunction() : { JsonAggregateFunction result = new JsonAggregateFunction(); Token token; + Object key; Expression expression; List expressionOrderByList = null; @@ -5312,10 +5321,32 @@ JsonAggregateFunction JsonAggregateFunction() : { ( ( "(" { result.setType( JsonFunctionType.OBJECT ); } - [ "KEY" { result.setUsingKeyKeyword( true ); } ] - ( token = | token = | token = | token = | token = | token = | token = ) { result.setKey( token.image ); } - ( ":" | "VALUE" {result.setUsingValueKeyword( true ); } ) - ( token = | token = ) { result.setValue( token.image ); } + ( + LOOKAHEAD(2) ( + "KEY" { result.setUsingKeyKeyword( true ); } + ( + ( token = | token = | token = | token = | token = ) + { + key = token.image; + } + | + key = Column() + ) + ) + | + ( token = | token = | token = | token = | token = ) + { + key = token.image; + } + | + key = Column() + ) + { + result.setKey( key ); + } + + ( ":" | "," { result.setType( JsonFunctionType.MYSQL_OBJECT ); } | "VALUE" {result.setUsingValueKeyword( true ); } ) + expression = Expression() { result.setValue( expression ); } [ { result.setUsingFormatJson( true ); } ] diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java index 1ce33d52e..ef1335e6c 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java @@ -131,6 +131,10 @@ public void testArrayAgg() throws JSQLParserException { @Test public void testObject() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "WITH Items AS (SELECT 'hello' AS key, 'world' AS value)\n" + + "SELECT JSON_OBJECT(key, value) AS json_data FROM Items", + true); TestUtils.assertSqlCanBeParsedAndDeparsed( "SELECT JSON_OBJECT( KEY 'foo' VALUE bar, KEY 'foo' VALUE bar) FROM dual ", true); TestUtils.assertSqlCanBeParsedAndDeparsed( @@ -280,4 +284,13 @@ public void testJavaMethods() throws JSQLParserException { Assertions.assertEquals(JsonAggregateUniqueKeysType.WITH, jsonFunction .withUniqueKeysType(JsonAggregateUniqueKeysType.WITH).getUniqueKeysType()); } + + @Test + void testIssue1753JSonObjectAggWithColumns() throws JSQLParserException { + String sqlStr = "SELECT JSON_OBJECTAGG( KEY q.foo VALUE q.bar) FROM dual"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr); + + sqlStr = "SELECT JSON_OBJECTAGG(foo, bar) FROM dual"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr); + } }