Skip to content

Commit cbffe6b

Browse files
Fixes #1371 (#1377)
* Fixes #1371 Postgres specific JSON_OBJECT syntax supporting: SELECT json_object('{a, 1, b, 2}'); SELECT json_object('{{a, 1}, {b, 2}}'); SELECT json_object('{a, b}', '{1,2 }'); * Improve Test Coverage
1 parent a52db54 commit cbffe6b

File tree

7 files changed

+97
-29
lines changed

7 files changed

+97
-29
lines changed

src/main/java/net/sf/jsqlparser/expression/JsonFunction.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,14 @@ public StringBuilder append(StringBuilder builder) {
119119
case OBJECT:
120120
appendObject(builder);
121121
break;
122+
case POSTGRES_OBJECT:
123+
appendPostgresObject(builder);
124+
break;
122125
case ARRAY:
123126
appendArray(builder);
124127
break;
125128
default:
126129
// this should never happen really
127-
throw new UnsupportedOperationException("JSON Aggregate Function of the type "
128-
+ functionType.name() + " has not been implemented yet.");
129130
}
130131
return builder;
131132
}
@@ -184,6 +185,21 @@ public StringBuilder appendObject(StringBuilder builder) {
184185
return builder;
185186
}
186187

188+
189+
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
190+
public StringBuilder appendPostgresObject(StringBuilder builder) {
191+
builder.append("JSON_OBJECT( ");
192+
for (JsonKeyValuePair keyValuePair : keyValuePairs) {
193+
builder.append(keyValuePair.getKey());
194+
if (keyValuePair.getValue()!=null) {
195+
builder.append(", ").append(keyValuePair.getValue());
196+
}
197+
}
198+
builder.append(" ) ");
199+
200+
return builder;
201+
}
202+
187203
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
188204
public StringBuilder appendArray(StringBuilder builder) {
189205
builder.append("JSON_ARRAY( ");
@@ -203,10 +219,10 @@ public StringBuilder appendArray(StringBuilder builder) {
203219
builder.append(" NULL ON NULL ");
204220
break;
205221
case ABSENT:
206-
builder.append(" ABSENT On NULL ");
222+
builder.append(" ABSENT ON NULL ");
207223
break;
208224
default:
209-
// "ON NULL" was ommitted
225+
// "ON NULL" was omitted
210226
}
211227
}
212228
builder.append(") ");

src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717
public enum JsonFunctionType {
1818
OBJECT
1919
, ARRAY
20+
, POSTGRES_OBJECT
2021
}

src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class JsonKeyValuePair {
2727
public JsonKeyValuePair(String key, Object value, boolean usingKeyKeyword,
2828
boolean usingValueKeyword) {
2929
this.key = Objects.requireNonNull(key, "The KEY of the Pair must not be null");
30-
this.value = Objects.requireNonNull(value, "The VALUE of the Pair must not be null");
30+
this.value = value;
3131
this.usingKeyKeyword = usingKeyKeyword;
3232
this.usingValueKeyword = usingValueKeyword;
3333
}

src/main/java/net/sf/jsqlparser/parser/feature/Feature.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,11 @@ public enum Feature {
714714
*/
715715
allowSquareBracketQuotation(false),
716716

717+
/**
718+
allow parsing of RDBMS specific syntax by switching off SQL Standard Compliant Syntax
719+
*/
720+
allowPostgresSpecificSyntax(false),
721+
717722
// PERFORMANCE
718723

719724
/**

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3896,7 +3896,7 @@ JsonFunction JsonFunction() : {
38963896
boolean usingKeyKeyword = false;
38973897
boolean usingValueKeyword = false;
38983898
Token keyToken;
3899-
Token valueToken;
3899+
Token valueToken = null;
39003900
JsonKeyValuePair keyValuePair;
39013901

39023902
Expression expression;
@@ -3907,35 +3907,52 @@ JsonFunction JsonFunction() : {
39073907
(
39083908
(
39093909
( <K_JSON_OBJECT>
3910-
"(" { result.setType( JsonFunctionType.OBJECT ); }
3911-
3910+
"(" { result.setType( JsonFunctionType.OBJECT ); }
3911+
39123912
(
39133913
// --- First Element
3914-
[ "KEY" { usingKeyKeyword = true; } ]
3915-
( keyToken = <DT_ZONE> | keyToken = <S_DOUBLE> | keyToken = <S_LONG> | keyToken = <S_HEX> | keyToken = <S_CHAR_LITERAL> | keyToken = <S_IDENTIFIER> | keyToken = <S_QUOTED_IDENTIFIER> )
3916-
( ":" | "VALUE" { usingValueKeyword = true; } )
3917-
( valueToken = <S_IDENTIFIER> | valueToken = <S_QUOTED_IDENTIFIER> ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); }
3914+
LOOKAHEAD(2)
3915+
(
3916+
// Postgres Specific Syntax:
3917+
// SELECT json_object('{a, 1, b, 2}');
3918+
// SELECT json_object('{{a, 1}, {b, 2}}');
3919+
// SELECT json_object('{a, b}', '{1,2 }');
3920+
{ result.setType( JsonFunctionType.POSTGRES_OBJECT ); }
3921+
keyToken = <S_CHAR_LITERAL>
3922+
[ "," valueToken = <S_CHAR_LITERAL> ]
3923+
{ keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken !=null ? valueToken.image : null, false, false ); result.add(keyValuePair); }
3924+
)
3925+
|
3926+
(
3927+
3928+
// SQL2016 compliant Syntax
3929+
[ "KEY" { usingKeyKeyword = true; } ]
3930+
3931+
( keyToken = <DT_ZONE> | keyToken = <S_DOUBLE> | keyToken = <S_LONG> | keyToken = <S_HEX> | keyToken = <S_CHAR_LITERAL> | keyToken = <S_IDENTIFIER> | keyToken = <S_QUOTED_IDENTIFIER> )
3932+
( ":" | "VALUE" { usingValueKeyword = true; } )
3933+
( valueToken = <S_IDENTIFIER> | valueToken = <S_QUOTED_IDENTIFIER> ) { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); }
3934+
)
39183935

39193936
[ <K_FORMAT> <K_JSON> { keyValuePair.setUsingFormatJson( true ); } ]
39203937

39213938
// --- Next Elements
39223939
( "," { usingKeyKeyword = false; usingValueKeyword = false; }
39233940
[ "KEY" { usingKeyKeyword = true; } ]
3924-
keyToken = <S_IDENTIFIER>
3925-
( ":" | "VALUE" { usingValueKeyword = true; } )
3941+
keyToken = <S_IDENTIFIER>
3942+
( ":" | "VALUE" { usingValueKeyword = true; } )
39263943
// token = <DT_ZONE> | <S_DOUBLE> | <S_LONG> | <S_HEX> | <S_CHAR_LITERAL> { result.setValue( token.image ); }
39273944
valueToken = <S_IDENTIFIER> { keyValuePair = new JsonKeyValuePair( keyToken.image, valueToken.image, usingKeyKeyword, usingValueKeyword ); result.add(keyValuePair); }
39283945

39293946
[ <K_FORMAT> <K_JSON> { keyValuePair.setUsingFormatJson( true ); } ]
39303947
)*
39313948
)*
39323949

3933-
[
3934-
(
3950+
[
3951+
(
39353952
<K_NULL> <K_ON> <K_NULL> { result.setOnNullType( JsonAggregateOnNullType.NULL ); }
39363953
)
39373954
|
3938-
(
3955+
(
39393956
<K_ABSENT> <K_ON> <K_NULL> { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); }
39403957
)
39413958
]
@@ -3949,7 +3966,6 @@ JsonFunction JsonFunction() : {
39493966
<K_WITHOUT> <K_UNIQUE> <K_KEYS> { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); }
39503967
)
39513968
]
3952-
39533969
")"
39543970
)
39553971
|

src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import java.util.Objects;
1414
import junit.framework.Assert;
1515
import net.sf.jsqlparser.JSQLParserException;
16+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1617
import net.sf.jsqlparser.test.TestUtils;
1718
import org.junit.Test;
19+
import org.junit.jupiter.api.Assertions;
1820

1921
/**
2022
*
@@ -138,6 +140,14 @@ public void testObject() throws JSQLParserException {
138140
"SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL) FROM dual ",
139141
true);
140142

143+
TestUtils.assertSqlCanBeParsedAndDeparsed(
144+
"SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL WITH UNIQUE KEYS) FROM dual ",
145+
true);
146+
147+
TestUtils.assertSqlCanBeParsedAndDeparsed(
148+
"SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL WITHOUT UNIQUE KEYS) FROM dual ",
149+
true);
150+
141151
TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object(null on null)", true);
142152

143153
TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object(absent on null)", true);
@@ -190,4 +200,30 @@ public void testIssue1260() throws JSQLParserException {
190200
")\n" +
191201
"from SYSIBM.DUAL", true);
192202
}
203+
204+
205+
@Test
206+
public void testIssue1371() throws JSQLParserException {
207+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, 1, b, 2}')", true);
208+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{{a, 1}, {b, 2}}')", true);
209+
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT json_object('{a, b}', '{1,2 }')", true);
210+
}
211+
212+
@Test
213+
public void testJavaMethods() throws JSQLParserException {
214+
String expressionStr= "JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL WITHOUT UNIQUE KEYS)";
215+
JsonFunction jsonFunction = (JsonFunction) CCJSqlParserUtil.parseExpression(expressionStr);
216+
217+
Assertions.assertEquals(JsonFunctionType.OBJECT, jsonFunction.getType());
218+
Assertions.assertNotEquals(jsonFunction.withType(JsonFunctionType.POSTGRES_OBJECT), jsonFunction.getType());
219+
220+
Assertions.assertEquals(3, jsonFunction.getKeyValuePairs().size());
221+
Assertions.assertEquals(new JsonKeyValuePair("foo", "bar", true, true), jsonFunction.getKeyValuePair(0));
222+
223+
jsonFunction.setOnNullType(JsonAggregateOnNullType.NULL);
224+
Assertions.assertEquals(JsonAggregateOnNullType.ABSENT, jsonFunction.withOnNullType(JsonAggregateOnNullType.ABSENT).getOnNullType());
225+
226+
jsonFunction.setUniqueKeysType(JsonAggregateUniqueKeysType.WITH);
227+
Assertions.assertEquals(JsonAggregateUniqueKeysType.WITH, jsonFunction.withUniqueKeysType(JsonAggregateUniqueKeysType.WITH).getUniqueKeysType());
228+
}
193229
}

src/test/java/net/sf/jsqlparser/test/TestUtils.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ public static void assertOracleHintExists(String sql, boolean assertDeparser, St
304304
OracleHint hint = ps.getOracleHint();
305305
assertNotNull(hint);
306306
assertEquals(hints[0], hint.getValue());
307-
} else {
307+
} else
308308
if (stmt.getSelectBody() instanceof SetOperationList) {
309309
SetOperationList setop = (SetOperationList) stmt.getSelectBody();
310310
for (int i = 0; i < setop.getSelects().size(); i++) {
@@ -318,22 +318,16 @@ public static void assertOracleHintExists(String sql, boolean assertDeparser, St
318318
}
319319
}
320320
}
321-
}
322321
} else if (statement instanceof Update) {
323322
Update stmt = (Update) statement;
324323
OracleHint hint = stmt.getOracleHint();
325324
assertNotNull(hint);
326325
assertEquals(hints[0], hint.getValue());
327326
} else if (statement instanceof Insert) {
328-
Insert stmt = (Insert) statement;
329-
OracleHint hint = stmt.getOracleHint();
330-
assertNotNull(hint);
331-
assertEquals(hints[0], hint.getValue());
332-
} else if (statement instanceof Update) {
333-
Update stmt = (Update) statement;
334-
OracleHint hint = stmt.getOracleHint();
335-
assertNotNull(hint);
336-
assertEquals(hints[0], hint.getValue());
327+
Insert stmt = (Insert) statement;
328+
OracleHint hint = stmt.getOracleHint();
329+
assertNotNull(hint);
330+
assertEquals(hints[0], hint.getValue());
337331
} else if (statement instanceof Delete) {
338332
Delete stmt = (Delete) statement;
339333
OracleHint hint = stmt.getOracleHint();

0 commit comments

Comments
 (0)