From de805ab6dae4471216eae7c6cf8213b32ce454db Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Mon, 22 Nov 2021 14:09:54 +0700 Subject: [PATCH 01/12] Adjust Gradle to JUnit 5 Parallel Test execution Gradle Caching Explicitly request for latest JavaCC 7.0.10 --- build.gradle | 24 ++++++++++++++++++- gradle.properties | 12 ++++++++++ .../statement/select/SelectTest.java | 3 +++ .../statement/select/SpeedTest.java | 3 +++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index b555e3099..74c96f6d9 100644 --- a/build.gradle +++ b/build.gradle @@ -19,9 +19,13 @@ java.sourceCompatibility = JavaVersion.VERSION_1_8 repositories { gradlePluginPortal() mavenLocal() + mavenCentral() maven { url = uri('https://repo.maven.apache.org/maven2/') } + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { @@ -36,6 +40,12 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter + testImplementation 'org.mockito:mockito-junit-jupiter:4.1.0' + + // enforce latest version of JavaCC + javacc 'net.java.dev.javacc:javacc:7.0.10' + } compileJavacc { @@ -56,6 +66,18 @@ jacoco { } test { + useJUnitPlatform() + + // set heap size for the test JVM(s) + minHeapSize = "128m" + maxHeapSize = "1G" + + jvmArgs << [ + '-Djunit.jupiter.execution.parallel.enabled=true', + '-Djunit.jupiter.execution.parallel.config.strategy=dynamic', + '-Djunit.jupiter.execution.parallel.mode.default=concurrent' + ] + finalizedBy jacocoTestReport // report is always generated after tests run finalizedBy jacocoTestCoverageVerification } @@ -93,7 +115,7 @@ jacocoTestCoverageVerification { limit { counter = 'LINE' value = 'MISSEDCOUNT' - maximum = 5458 + maximum = 5500 } excludes = [ 'net.sf.jsqlparser.util.validation.*', diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..f1dda8d41 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError + +org.gradle.caching=true + +# Modularise your project and enable parallel build +org.gradle.parallel=true + +# Enable configure on demand. +org.gradle.configureondemand=true + diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index 5e4f51f8c..f738ad0a0 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -45,7 +45,10 @@ import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +@Execution(ExecutionMode.CONCURRENT) public class SelectTest { private final CCJSqlParserManager parserManager = new CCJSqlParserManager(); diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java index d6bfd09fe..8571617f3 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java @@ -23,7 +23,10 @@ import net.sf.jsqlparser.test.TestException; import net.sf.jsqlparser.util.TablesNamesFinder; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +@Execution(ExecutionMode.CONCURRENT) public class SpeedTest { private final static int NUM_REPS_500 = 500; From d5a6dcaa2a5b97e36f582415114951211cd91589 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Wed, 24 Nov 2021 14:17:57 +0700 Subject: [PATCH 02/12] Do not mark SpeedTest for concurrent execution --- src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java index 8571617f3..8bc748796 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; -@Execution(ExecutionMode.CONCURRENT) +//@Execution(ExecutionMode.CONCURRENT) public class SpeedTest { private final static int NUM_REPS_500 = 500; From a9d050386da28ff3ce795cdababdaeef2451da8e Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sun, 28 Nov 2021 14:13:00 +0700 Subject: [PATCH 03/12] Remove unused imports --- .../java/net/sf/jsqlparser/statement/select/SpeedTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java index 8bc748796..d6bfd09fe 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java @@ -23,10 +23,7 @@ import net.sf.jsqlparser.test.TestException; import net.sf.jsqlparser.util.TablesNamesFinder; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; -//@Execution(ExecutionMode.CONCURRENT) public class SpeedTest { private final static int NUM_REPS_500 = 500; From 6dfa05f7315de800b5dd6033dee6f6f9329698e2 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Mon, 22 Nov 2021 14:09:54 +0700 Subject: [PATCH 04/12] Adjust Gradle to JUnit 5 Parallel Test execution Gradle Caching Explicitly request for latest JavaCC 7.0.10 --- build.gradle | 24 ++++++++++++++++++- gradle.properties | 12 ++++++++++ .../statement/select/SelectTest.java | 3 +++ .../statement/select/SpeedTest.java | 3 +++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 gradle.properties diff --git a/build.gradle b/build.gradle index b555e3099..74c96f6d9 100644 --- a/build.gradle +++ b/build.gradle @@ -19,9 +19,13 @@ java.sourceCompatibility = JavaVersion.VERSION_1_8 repositories { gradlePluginPortal() mavenLocal() + mavenCentral() maven { url = uri('https://repo.maven.apache.org/maven2/') } + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { @@ -36,6 +40,12 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter + testImplementation 'org.mockito:mockito-junit-jupiter:4.1.0' + + // enforce latest version of JavaCC + javacc 'net.java.dev.javacc:javacc:7.0.10' + } compileJavacc { @@ -56,6 +66,18 @@ jacoco { } test { + useJUnitPlatform() + + // set heap size for the test JVM(s) + minHeapSize = "128m" + maxHeapSize = "1G" + + jvmArgs << [ + '-Djunit.jupiter.execution.parallel.enabled=true', + '-Djunit.jupiter.execution.parallel.config.strategy=dynamic', + '-Djunit.jupiter.execution.parallel.mode.default=concurrent' + ] + finalizedBy jacocoTestReport // report is always generated after tests run finalizedBy jacocoTestCoverageVerification } @@ -93,7 +115,7 @@ jacocoTestCoverageVerification { limit { counter = 'LINE' value = 'MISSEDCOUNT' - maximum = 5458 + maximum = 5500 } excludes = [ 'net.sf.jsqlparser.util.validation.*', diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..f1dda8d41 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,12 @@ +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError + +org.gradle.caching=true + +# Modularise your project and enable parallel build +org.gradle.parallel=true + +# Enable configure on demand. +org.gradle.configureondemand=true + diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index b09af7838..cd3c5f788 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -45,7 +45,10 @@ import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +@Execution(ExecutionMode.CONCURRENT) public class SelectTest { private final CCJSqlParserManager parserManager = new CCJSqlParserManager(); diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java index d6bfd09fe..8571617f3 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java @@ -23,7 +23,10 @@ import net.sf.jsqlparser.test.TestException; import net.sf.jsqlparser.util.TablesNamesFinder; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +@Execution(ExecutionMode.CONCURRENT) public class SpeedTest { private final static int NUM_REPS_500 = 500; From 8f0bfe63c51ede6c58fa70bdc0cefdbf2187c8ac Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Wed, 24 Nov 2021 14:17:57 +0700 Subject: [PATCH 05/12] Do not mark SpeedTest for concurrent execution --- src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java index 8571617f3..8bc748796 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; -@Execution(ExecutionMode.CONCURRENT) +//@Execution(ExecutionMode.CONCURRENT) public class SpeedTest { private final static int NUM_REPS_500 = 500; From 5cd09743db905559993fa09da99b0cc3858ac1e1 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Sun, 28 Nov 2021 14:13:00 +0700 Subject: [PATCH 06/12] Remove unused imports --- .../java/net/sf/jsqlparser/statement/select/SpeedTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java index 8bc748796..d6bfd09fe 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SpeedTest.java @@ -23,10 +23,7 @@ import net.sf.jsqlparser.test.TestException; import net.sf.jsqlparser.util.TablesNamesFinder; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; -//@Execution(ExecutionMode.CONCURRENT) public class SpeedTest { private final static int NUM_REPS_500 = 500; From be61ae28971703c8a5de275e7993b7b757c49bb2 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 7 Apr 2022 07:52:16 +0700 Subject: [PATCH 07/12] Improve JSON Functions Space around the `:` delimiter of JSON Functions --- src/test/java/net/sf/jsqlparser/test/TestUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/test/TestUtils.java b/src/test/java/net/sf/jsqlparser/test/TestUtils.java index 68ea44636..322250559 100644 --- a/src/test/java/net/sf/jsqlparser/test/TestUtils.java +++ b/src/test/java/net/sf/jsqlparser/test/TestUtils.java @@ -54,8 +54,9 @@ public class TestUtils { private static final Pattern SQL_SANITATION_PATTERN = Pattern.compile("(\\s+)", Pattern.MULTILINE); + // Assure SPACE around Syntax Characters private static final Pattern SQL_SANITATION_PATTERN2 - = Pattern.compile("\\s*([!/,()=+\\-*|\\]<>])\\s*", Pattern.MULTILINE); + = Pattern.compile("\\s*([!/,()=+\\-*|\\]<>:])\\s*", Pattern.MULTILINE); /** * @param statement @@ -265,7 +266,7 @@ public static String buildSqlString(final String originalSql, boolean laxDeparsi // redundant white space sanitizedSqlStr = SQL_SANITATION_PATTERN.matcher(sanitizedSqlStr).replaceAll(" "); - // replace some more stuff + // assure spacing around Syntax Characters sanitizedSqlStr = SQL_SANITATION_PATTERN2.matcher(sanitizedSqlStr).replaceAll("$1"); return sanitizedSqlStr.trim().toLowerCase(); } else { From 03a53aec047a31e63d5bfc3c344af5ca281244f8 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 7 Apr 2022 09:22:19 +0700 Subject: [PATCH 08/12] Improve JSON Functions Enforce `KEY` as `S_CHAR_LITERAL` Allow `Column` as `VALUE` Temporarily disable Postgres Syntax --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 114 ++++++++++-------- .../expression/JsonFunctionTest.java | 43 +++++-- 2 files changed, 93 insertions(+), 64 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index cf94bbb91..804c9b0bc 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3977,6 +3977,7 @@ JsonFunction JsonFunction() : { boolean usingValueKeyword = false; Token keyToken; Token valueToken = null; + Column column = null; JsonKeyValuePair keyValuePair; Expression expression; @@ -3988,64 +3989,73 @@ JsonFunction JsonFunction() : { ( ( "(" { result.setType( JsonFunctionType.OBJECT ); } +// LOOKAHEAD(2) +// ( +// // Postgres Specific Syntax: +// // SELECT json_object('{a, 1, b, 2}'); +// // SELECT json_object('{{a, 1}, {b, 2}}'); +// // SELECT json_object('{a, b}', '{1,2 }'); +// { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } +// keyToken = +// [ "," valueToken = ] +// { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken !=null ? valueToken.image : null, false, false ); result.add(keyValuePair); } +// ) +// | - ( - // --- First Element - LOOKAHEAD(2) - ( - // Postgres Specific Syntax: - // SELECT json_object('{a, 1, b, 2}'); - // SELECT json_object('{{a, 1}, {b, 2}}'); - // SELECT json_object('{a, b}', '{1,2 }'); - { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } - keyToken = - [ "," valueToken = ] - { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken !=null ? valueToken.image : null, false, false ); result.add(keyValuePair); } - ) - | ( - + // --- First Element // SQL2016 compliant Syntax [ "KEY" { usingKeyKeyword = true; } ] + keyToken = - ( keyToken = | keyToken = | keyToken = | keyToken = | keyToken = | keyToken = | keyToken = ) - ( ":" | "VALUE" { usingValueKeyword = true; } ) - ( valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - ) - - [ { keyValuePair.setUsingFormatJson( true ); } ] - - // --- Next Elements - ( "," { usingKeyKeyword = false; usingValueKeyword = false; } - [ "KEY" { usingKeyKeyword = true; } ] - keyToken = - ( ":" | "VALUE" { usingValueKeyword = true; } ) - // token = | | | | { result.setValue( token.image ); } - valueToken = { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - - [ { keyValuePair.setUsingFormatJson( true ); } ] - )* - )* - - [ - ( - { result.setOnNullType( JsonAggregateOnNullType.NULL ); } - ) - | - ( - { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } - ) - ] + ( + ( ":" | "VALUE" { usingValueKeyword = true; } ) + ( + column = Column() { keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + | + ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + ) + [ { keyValuePair.setUsingFormatJson( true ); } ] + + // --- Next Elements + ( "," { usingKeyKeyword = false; usingValueKeyword = false; } + [ "KEY" { usingKeyKeyword = true; } ] + keyToken = + ( ":" | "VALUE" { usingValueKeyword = true; } ) + ( + column = Column() { keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + | + ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + ) + [ { keyValuePair.setUsingFormatJson( true ); } ] + )* + ) +// | +// ( +// [ "," valueToken = ] +// { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken !=null ? valueToken.image : null, false, false ); result.add(keyValuePair); } +// ) + )? + + [ + ( + { result.setOnNullType( JsonAggregateOnNullType.NULL ); } + ) + | + ( + { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } + ) + ] - [ - ( - { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); } - ) - | - ( - { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); } - ) - ] + [ + ( + { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); } + ) + | + ( + { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); } + ) + ] ")" ) | diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java index 82b38737d..58e3ec312 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java @@ -131,24 +131,24 @@ public void testArrayAgg() throws JSQLParserException { @Test public void testObject() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( - "SELECT JSON_OBJECT( KEY foo VALUE bar, KEY foo VALUE bar) FROM dual ", true); - TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_OBJECT( foo:bar, foo:bar) FROM dual ", + "SELECT JSON_OBJECT( KEY 'foo' VALUE bar, KEY 'foo' VALUE bar) FROM dual ", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_OBJECT( 'foo' : bar, 'foo' : bar) FROM dual ", true); TestUtils.assertSqlCanBeParsedAndDeparsed( - "SELECT JSON_OBJECT( foo:bar, foo:bar FORMAT JSON) FROM dual ", true); + "SELECT JSON_OBJECT( 'foo':bar, 'foo':bar FORMAT JSON) FROM dual ", true); TestUtils.assertSqlCanBeParsedAndDeparsed( - "SELECT JSON_OBJECT( KEY foo VALUE bar, foo:bar FORMAT JSON, foo:bar NULL ON NULL) FROM dual ", + "SELECT JSON_OBJECT( KEY 'foo' VALUE bar, 'foo':bar FORMAT JSON, 'foo':bar NULL ON NULL) FROM dual ", true); TestUtils.assertSqlCanBeParsedAndDeparsed( - "SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL) FROM dual ", + "SELECT JSON_OBJECT( KEY 'foo' VALUE bar FORMAT JSON, 'foo':bar, 'foo':bar ABSENT ON NULL) FROM dual ", true); TestUtils.assertSqlCanBeParsedAndDeparsed( - "SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL WITH UNIQUE KEYS) FROM dual ", + "SELECT JSON_OBJECT( KEY 'foo' VALUE bar FORMAT JSON, 'foo':bar, 'foo':bar ABSENT ON NULL WITH UNIQUE KEYS) FROM dual ", true); TestUtils.assertSqlCanBeParsedAndDeparsed( - "SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL WITHOUT UNIQUE KEYS) FROM dual ", + "SELECT JSON_OBJECT( KEY 'foo' VALUE bar FORMAT JSON, 'foo':bar, 'foo':bar ABSENT ON NULL WITHOUT UNIQUE KEYS) FROM dual ", true); TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object(null on null)", true); @@ -158,6 +158,25 @@ public void testObject() throws JSQLParserException { TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object()", true); } + @Test + public void testObjectIssue1504() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT(key 'person' value tp.account) obj", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT(key 'person' value tp.account, key 'person' value tp.account) obj", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT( 'person' : tp.account) obj", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT( 'person' : tp.account, 'person' : tp.account) obj", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT( 'person' : '1', 'person' : '2') obj", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT( 'person' VALUE tp.person, 'account' VALUE tp.account) obj", true); + + } + @Test public void testArray() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( @@ -206,21 +225,21 @@ public void testIssue1260() throws JSQLParserException { @Test public void testIssue1371() throws JSQLParserException { - TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, 1, b, 2}')", true); - TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{{a, 1}, {b, 2}}')", true); - TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, b}', '{1,2 }')", true); +// TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, 1, b, 2}')", true); +// TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{{a, 1}, {b, 2}}')", true); +// TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, b}', '{1,2 }')", true); } @Test public void testJavaMethods() throws JSQLParserException { - String expressionStr = "JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL WITHOUT UNIQUE KEYS)"; + String expressionStr = "JSON_OBJECT( KEY 'foo' VALUE bar FORMAT JSON, 'foo':bar, 'foo':bar ABSENT ON NULL WITHOUT UNIQUE KEYS)"; JsonFunction jsonFunction = (JsonFunction) CCJSqlParserUtil.parseExpression(expressionStr); Assertions.assertEquals(JsonFunctionType.OBJECT, jsonFunction.getType()); Assertions.assertNotEquals(jsonFunction.withType(JsonFunctionType.POSTGRES_OBJECT), jsonFunction.getType()); Assertions.assertEquals(3, jsonFunction.getKeyValuePairs().size()); - Assertions.assertEquals(new JsonKeyValuePair("foo", "bar", true, true), jsonFunction.getKeyValuePair(0)); + Assertions.assertEquals(new JsonKeyValuePair("'foo'", "bar", true, true), jsonFunction.getKeyValuePair(0)); jsonFunction.setOnNullType(JsonAggregateOnNullType.NULL); Assertions.assertEquals(JsonAggregateOnNullType.ABSENT, jsonFunction.withOnNullType(JsonAggregateOnNullType.ABSENT).getOnNullType()); From 02cac90800c8822976554da8bf15047288ade3bb Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 7 Apr 2022 15:09:38 +0700 Subject: [PATCH 09/12] Improve JSON Functions Bring back Postgres Syntax Enable MySQL Syntax JSON_OBJECT(key, value [, key, value, ...]) Fix some more tests, where key was not a String --- .../jsqlparser/expression/JsonFunction.java | 23 ++++ .../expression/JsonFunctionType.java | 1 + .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 126 +++++++++--------- .../ExpressionVisitorAdapterTest.java | 2 +- .../expression/JsonFunctionTest.java | 14 +- .../validator/ExpressionValidatorTest.java | 2 +- 6 files changed, 101 insertions(+), 67 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java index 54d269533..9d09b9711 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java @@ -57,6 +57,10 @@ public void add(int i, JsonFunctionExpression expression) { expressions.add(i, expression); } + public boolean isEmpty() { + return keyValuePairs.isEmpty(); + } + public JsonAggregateOnNullType getOnNullType() { return onNullType; } @@ -122,6 +126,9 @@ public StringBuilder append(StringBuilder builder) { case POSTGRES_OBJECT: appendPostgresObject(builder); break; + case MYSQL_OBJECT: + appendMySqlObject(builder); + break; case ARRAY: appendArray(builder); break; @@ -200,6 +207,22 @@ public StringBuilder appendPostgresObject(StringBuilder builder) { return builder; } + public StringBuilder appendMySqlObject(StringBuilder builder) { + builder.append("JSON_OBJECT( "); + int i=0; + for (JsonKeyValuePair keyValuePair : keyValuePairs) { + if (i>0) { + builder.append(", "); + } + builder.append(keyValuePair.getKey()); + builder.append(", ").append(keyValuePair.getValue()); + i++; + } + builder.append(" ) "); + + return builder; + } + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public StringBuilder appendArray(StringBuilder builder) { builder.append("JSON_ARRAY( "); diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java index b044d191d..5aafdfc2b 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java @@ -18,4 +18,5 @@ public enum JsonFunctionType { OBJECT , ARRAY , POSTGRES_OBJECT + , MYSQL_OBJECT } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 804c9b0bc..4aaa4c6e2 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3729,6 +3729,8 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(JsonExpression()) retval=JsonExpression() + | LOOKAHEAD(Function()) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ] + | LOOKAHEAD(JsonFunction()) retval = JsonFunction() | LOOKAHEAD(JsonAggregateFunction()) retval = JsonAggregateFunction() @@ -3737,7 +3739,7 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(FullTextSearch()) retval = FullTextSearch() - | LOOKAHEAD(Function()) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ] + | LOOKAHEAD(2) retval = IntervalExpression() { dateExpressionAllowed = false; } @@ -3975,6 +3977,7 @@ JsonFunction JsonFunction() : { JsonFunction result = new JsonFunction(); boolean usingKeyKeyword = false; boolean usingValueKeyword = false; + boolean usingFormatJason = false; Token keyToken; Token valueToken = null; Column column = null; @@ -3989,73 +3992,72 @@ JsonFunction JsonFunction() : { ( ( "(" { result.setType( JsonFunctionType.OBJECT ); } -// LOOKAHEAD(2) -// ( -// // Postgres Specific Syntax: -// // SELECT json_object('{a, 1, b, 2}'); -// // SELECT json_object('{{a, 1}, {b, 2}}'); -// // SELECT json_object('{a, b}', '{1,2 }'); -// { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } -// keyToken = -// [ "," valueToken = ] -// { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken !=null ? valueToken.image : null, false, false ); result.add(keyValuePair); } -// ) -// | - ( - // --- First Element - // SQL2016 compliant Syntax - [ "KEY" { usingKeyKeyword = true; } ] - keyToken = - ( - ( ":" | "VALUE" { usingValueKeyword = true; } ) + // SQL2016 compliant Syntax ( - column = Column() { keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - | - ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - ) - [ { keyValuePair.setUsingFormatJson( true ); } ] - - // --- Next Elements - ( "," { usingKeyKeyword = false; usingValueKeyword = false; } [ "KEY" { usingKeyKeyword = true; } ] keyToken = - ( ":" | "VALUE" { usingValueKeyword = true; } ) - ( - column = Column() { keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - | - ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - ) - [ { keyValuePair.setUsingFormatJson( true ); } ] - )* - ) -// | -// ( -// [ "," valueToken = ] -// { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken !=null ? valueToken.image : null, false, false ); result.add(keyValuePair); } -// ) - )? - - [ - ( - { result.setOnNullType( JsonAggregateOnNullType.NULL ); } - ) - | - ( - { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } - ) - ] - [ - ( - { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); } - ) - | - ( - { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); } - ) - ] + ( LOOKAHEAD(2) + ( ":" | "," { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) + ( + column = Column() + | + ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) + ) + [ { usingFormatJason = true; } ] + )? { + if (valueToken !=null) { + keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); + keyValuePair.setUsingFormatJson( usingFormatJason ); + result.add(keyValuePair); + } else if (column !=null) { + keyValuePair = new JsonKeyValuePair( keyToken.image, column, 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; } ) + ( + column = Column() { keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + | + ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + ) + [ { keyValuePair.setUsingFormatJson( true ); } ] + )* + ) + )? + + [ + ( + { result.setOnNullType( JsonAggregateOnNullType.NULL ); } + ) + | + ( + { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } + ) + ] + + [ + ( + { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); } + ) + | + ( + { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); } + ) + ] + ) ")" ) | diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java index b5e87e524..89857100a 100644 --- a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java @@ -216,7 +216,7 @@ public void testAtTimeZoneExpression() throws JSQLParserException { public void testJsonFunction() throws JSQLParserException { ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter(); CCJSqlParserUtil - .parseExpression("JSON_OBJECT( KEY foo VALUE bar, KEY foo VALUE bar)") + .parseExpression("JSON_OBJECT( KEY 'foo' VALUE bar, KEY 'foo' VALUE bar)") .accept(adapter); CCJSqlParserUtil .parseExpression("JSON_ARRAY( (SELECT * from dual) )") diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java index 58e3ec312..d11b926b5 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java @@ -162,10 +162,13 @@ public void testObject() throws JSQLParserException { public void testObjectIssue1504() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( "SELECT JSON_OBJECT(key 'person' value tp.account) obj", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( "SELECT JSON_OBJECT(key 'person' value tp.account, key 'person' value tp.account) obj", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( "SELECT JSON_OBJECT( 'person' : tp.account) obj", true); + TestUtils.assertSqlCanBeParsedAndDeparsed( "SELECT JSON_OBJECT( 'person' : tp.account, 'person' : tp.account) obj", true); @@ -174,7 +177,12 @@ public void testObjectIssue1504() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( "SELECT JSON_OBJECT( 'person' VALUE tp.person, 'account' VALUE tp.account) obj", true); + } + @Test + public void testObjectMySQL() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT('person', tp.person, 'account', tp.account) obj", true); } @Test @@ -225,9 +233,9 @@ public void testIssue1260() throws JSQLParserException { @Test public void testIssue1371() throws JSQLParserException { -// TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, 1, b, 2}')", true); -// TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{{a, 1}, {b, 2}}')", true); -// TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, b}', '{1,2 }')", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, 1, b, 2}')", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{{a, 1}, {b, 2}}')", true); + TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, b}', '{1,2 }')", true); } @Test diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java index fc8cf0ec3..af8b48d79 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidatorTest.java @@ -226,7 +226,7 @@ public void testJsonFunctionExpression() throws JSQLParserException { public void testJsonAggregartFunctionExpression() throws JSQLParserException { validateNoErrors("SELECT JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name ) FROM mytbl", 1, EXPRESSIONS); - validateNoErrors("SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL) FROM mytbl", 1, + validateNoErrors("SELECT JSON_OBJECT( KEY 'foo' VALUE bar FORMAT JSON, 'foo':bar, 'foo':bar ABSENT ON NULL) FROM mytbl", 1, EXPRESSIONS); } From 2429335c6d9150a7cf33f37266b7654586e7b13f Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Thu, 7 Apr 2022 16:46:35 +0700 Subject: [PATCH 10/12] Appease Codacy --- .../expression/JsonFunctionTest.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java index d11b926b5..557e0a8aa 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java @@ -9,15 +9,10 @@ */ package net.sf.jsqlparser.expression; -import java.util.Objects; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; /** @@ -69,17 +64,17 @@ public void testObjectBuilder() throws JSQLParserException { JsonKeyValuePair keyValuePair2 = new JsonKeyValuePair("foo", "bar", false, false).withUsingKeyKeyword(true).withUsingValueKeyword(true).withUsingFormatJson(false); // this should work because we compare based on KEY only - assertEquals(keyValuePair1, keyValuePair2); + Assertions.assertEquals(keyValuePair1, keyValuePair2); // this must fail because all the properties are considered - assertFalse(Objects.equals(keyValuePair1.toString(), keyValuePair2.toString())); + Assertions.assertNotEquals(keyValuePair1.toString(), keyValuePair2.toString()); JsonKeyValuePair keyValuePair3 = new JsonKeyValuePair("foo", "bar", false, false).withUsingKeyKeyword(false).withUsingValueKeyword(false).withUsingFormatJson(false); - assertNotNull(keyValuePair3); - assertEquals(keyValuePair3, keyValuePair3); - assertFalse(Objects.equals(keyValuePair3, f)); + Assertions.assertNotNull(keyValuePair3); + Assertions.assertEquals(keyValuePair3, keyValuePair3); + Assertions.assertNotEquals(keyValuePair3, f); - assertTrue(keyValuePair3.hashCode() != 0); + Assertions.assertTrue(keyValuePair3.hashCode() != 0); f.add(keyValuePair2); } @@ -95,7 +90,7 @@ public void testArrayBuilder() throws JSQLParserException { JsonFunctionExpression expression2 = new JsonFunctionExpression(new NullValue()).withUsingFormatJson( true); - assertTrue(Objects.equals(expression1.toString(), expression2.toString())); + Assertions.assertEquals(expression1.toString(), expression2.toString()); f.add(expression1); f.add(expression2); From 7b03568fd1a21e84e831c463dde85722493b3a51 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 8 Apr 2022 15:25:37 +0700 Subject: [PATCH 11/12] Let JSON_OBJECT accept Expressions as value --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 24 +++++++------------ .../expression/JsonFunctionTest.java | 9 +++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4aaa4c6e2..353ee58b7 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3983,7 +3983,7 @@ JsonFunction JsonFunction() : { Column column = null; JsonKeyValuePair keyValuePair; - Expression expression; + Expression expression = null; JsonFunctionExpression functionExpression; } @@ -4002,21 +4002,15 @@ JsonFunction JsonFunction() : { ( LOOKAHEAD(2) ( ":" | "," { result.setType( JsonFunctionType.POSTGRES_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) ( - column = Column() - | - ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) + expression = Expression() ) [ { usingFormatJason = true; } ] )? { - if (valueToken !=null) { - keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); - keyValuePair.setUsingFormatJson( usingFormatJason ); - result.add(keyValuePair); - } else if (column !=null) { - keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); - keyValuePair.setUsingFormatJson( usingFormatJason ); - result.add(keyValuePair); - } else { + 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); @@ -4029,9 +4023,7 @@ JsonFunction JsonFunction() : { keyToken = ( ":" | "," { result.setType( JsonFunctionType.MYSQL_OBJECT ); } | "VALUE" { usingValueKeyword = true; } ) ( - column = Column() { keyValuePair = new JsonKeyValuePair( keyToken.image, column, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } - | - ( valueToken = | valueToken = | valueToken = | valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + expression = Expression() { keyValuePair = new JsonKeyValuePair( keyToken.image, expression, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } ) [ { keyValuePair.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 557e0a8aa..d27378658 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java @@ -153,6 +153,15 @@ public void testObject() throws JSQLParserException { TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object()", true); } + @Test + public void testObjectWithExpression() throws JSQLParserException { + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_OBJECT( KEY 'foo' VALUE cast( bar AS VARCHAR(40)), KEY 'foo' VALUE bar) FROM dual ", true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "SELECT JSON_ARRAYAGG(obj) FROM (SELECT trt.relevance_id,JSON_OBJECT('id',CAST(trt.id AS CHAR),'taskName',trt.task_name,'openStatus',trt.open_status,'taskSort',trt.task_sort) as obj FROM tb_review_task trt ORDER BY trt.task_sort ASC)", true); + } + @Test public void testObjectIssue1504() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed( From 6ca0e098230c3f984d10bd14e1c5e2307fefd3f0 Mon Sep 17 00:00:00 2001 From: Andreas Reichel Date: Fri, 8 Apr 2022 15:40:39 +0700 Subject: [PATCH 12/12] set Version = 4.4-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b0ca73358..127a97f70 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ plugins { } group = 'com.github.jsqlparser' -version = '4.3-SNAPSHOT' +version = '4.4-SNAPSHOT' description = 'JSQLParser library' java.sourceCompatibility = JavaVersion.VERSION_1_8