diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java index 41c480d93..54d269533 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java @@ -119,13 +119,14 @@ public StringBuilder append(StringBuilder builder) { case OBJECT: appendObject(builder); break; + case POSTGRES_OBJECT: + appendPostgresObject(builder); + break; case ARRAY: appendArray(builder); break; default: // this should never happen really - throw new UnsupportedOperationException("JSON Aggregate Function of the type " - + functionType.name() + " has not been implemented yet."); } return builder; } @@ -184,6 +185,21 @@ public StringBuilder appendObject(StringBuilder builder) { return builder; } + + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) + public StringBuilder appendPostgresObject(StringBuilder builder) { + builder.append("JSON_OBJECT( "); + for (JsonKeyValuePair keyValuePair : keyValuePairs) { + builder.append(keyValuePair.getKey()); + if (keyValuePair.getValue()!=null) { + builder.append(", ").append(keyValuePair.getValue()); + } + } + builder.append(" ) "); + + return builder; + } + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public StringBuilder appendArray(StringBuilder builder) { builder.append("JSON_ARRAY( "); @@ -203,10 +219,10 @@ public StringBuilder appendArray(StringBuilder builder) { builder.append(" NULL ON NULL "); break; case ABSENT: - builder.append(" ABSENT On NULL "); + builder.append(" ABSENT ON NULL "); break; default: - // "ON NULL" was ommitted + // "ON NULL" was omitted } } builder.append(") "); diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java index 750a32109..b044d191d 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java @@ -17,4 +17,5 @@ public enum JsonFunctionType { OBJECT , ARRAY + , POSTGRES_OBJECT } diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java index 7e52ffeb9..740b8eaf5 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java @@ -27,7 +27,7 @@ public class JsonKeyValuePair { public JsonKeyValuePair(String key, Object value, boolean usingKeyKeyword, boolean usingValueKeyword) { this.key = Objects.requireNonNull(key, "The KEY of the Pair must not be null"); - this.value = Objects.requireNonNull(value, "The VALUE of the Pair must not be null"); + this.value = value; this.usingKeyKeyword = usingKeyKeyword; this.usingValueKeyword = usingValueKeyword; } diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 9d6fe2eda..baaf40138 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -714,6 +714,11 @@ public enum Feature { */ allowSquareBracketQuotation(false), + /** + allow parsing of RDBMS specific syntax by switching off SQL Standard Compliant Syntax + */ + allowPostgresSpecificSyntax(false), + // PERFORMANCE /** diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index be9cd46d8..4b7194cf6 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -3910,7 +3910,7 @@ JsonFunction JsonFunction() : { boolean usingKeyKeyword = false; boolean usingValueKeyword = false; Token keyToken; - Token valueToken; + Token valueToken = null; JsonKeyValuePair keyValuePair; Expression expression; @@ -3921,22 +3921,39 @@ JsonFunction JsonFunction() : { ( ( ( - "(" { result.setType( JsonFunctionType.OBJECT ); } - + "(" { result.setType( JsonFunctionType.OBJECT ); } + ( // --- First Element - [ "KEY" { usingKeyKeyword = true; } ] - ( keyToken = | keyToken = | keyToken = | keyToken = | keyToken = | keyToken = | keyToken = ) - ( ":" | "VALUE" { usingValueKeyword = true; } ) - ( valueToken = | valueToken = ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } + 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); } + ) + | + ( + + // SQL2016 compliant Syntax + [ "KEY" { usingKeyKeyword = true; } ] + + ( 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; } ) + keyToken = + ( ":" | "VALUE" { usingValueKeyword = true; } ) // token = | | | | { result.setValue( token.image ); } valueToken = { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); } @@ -3944,12 +3961,12 @@ JsonFunction JsonFunction() : { )* )* - [ - ( + [ + ( { result.setOnNullType( JsonAggregateOnNullType.NULL ); } ) | - ( + ( { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); } ) ] @@ -3963,7 +3980,6 @@ JsonFunction JsonFunction() : { { 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 e15dd9197..1116666e4 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java @@ -13,8 +13,10 @@ import java.util.Objects; import junit.framework.Assert; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.test.TestUtils; import org.junit.Test; +import org.junit.jupiter.api.Assertions; /** * @@ -138,6 +140,14 @@ public void testObject() throws JSQLParserException { "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 ", + true); + + TestUtils.assertSqlCanBeParsedAndDeparsed( + "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); TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object(absent on null)", true); @@ -190,4 +200,30 @@ public void testIssue1260() throws JSQLParserException { ")\n" + "from SYSIBM.DUAL", true); } + + + @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); + } + + @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)"; + 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)); + + jsonFunction.setOnNullType(JsonAggregateOnNullType.NULL); + Assertions.assertEquals(JsonAggregateOnNullType.ABSENT, jsonFunction.withOnNullType(JsonAggregateOnNullType.ABSENT).getOnNullType()); + + jsonFunction.setUniqueKeysType(JsonAggregateUniqueKeysType.WITH); + Assertions.assertEquals(JsonAggregateUniqueKeysType.WITH, jsonFunction.withUniqueKeysType(JsonAggregateUniqueKeysType.WITH).getUniqueKeysType()); + } } diff --git a/src/test/java/net/sf/jsqlparser/test/TestUtils.java b/src/test/java/net/sf/jsqlparser/test/TestUtils.java index e3617f357..309f3fbcc 100644 --- a/src/test/java/net/sf/jsqlparser/test/TestUtils.java +++ b/src/test/java/net/sf/jsqlparser/test/TestUtils.java @@ -304,7 +304,7 @@ public static void assertOracleHintExists(String sql, boolean assertDeparser, St OracleHint hint = ps.getOracleHint(); assertNotNull(hint); assertEquals(hints[0], hint.getValue()); - } else { + } else if (stmt.getSelectBody() instanceof SetOperationList) { SetOperationList setop = (SetOperationList) stmt.getSelectBody(); for (int i = 0; i < setop.getSelects().size(); i++) { @@ -318,22 +318,16 @@ public static void assertOracleHintExists(String sql, boolean assertDeparser, St } } } - } } else if (statement instanceof Update) { Update stmt = (Update) statement; OracleHint hint = stmt.getOracleHint(); assertNotNull(hint); assertEquals(hints[0], hint.getValue()); } else if (statement instanceof Insert) { - Insert stmt = (Insert) statement; - OracleHint hint = stmt.getOracleHint(); - assertNotNull(hint); - assertEquals(hints[0], hint.getValue()); - } else if (statement instanceof Update) { - Update stmt = (Update) statement; - OracleHint hint = stmt.getOracleHint(); - assertNotNull(hint); - assertEquals(hints[0], hint.getValue()); + Insert stmt = (Insert) statement; + OracleHint hint = stmt.getOracleHint(); + assertNotNull(hint); + assertEquals(hints[0], hint.getValue()); } else if (statement instanceof Delete) { Delete stmt = (Delete) statement; OracleHint hint = stmt.getOracleHint();