Skip to content

Commit e8d6058

Browse files
authored
[feat/refactor] AllColumns and AllTableColumns-Support for JSON_OBJECT (#2323)
* [feat] JSON_OBJECT support for AllColumns and AllTableColumns * [feat] JSON_OBJECT support for AllColumns and AllTableColumns * [feat] JSON_OBJECT support for AllColumns and AllTableColumns * [feat] Split the parser syntax for JsonFunction, added more Tests, added Feature to disable Commas as key value separators * [feat] Disable Expression as JSON_OBJECT key value via feature flag * [chore] spotlessApply * [feat] Use append method from JsonKeyValuePair
1 parent e400444 commit e8d6058

File tree

11 files changed

+497
-249
lines changed

11 files changed

+497
-249
lines changed

build.gradle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,14 @@ configurations.configureEach {
131131
}
132132

133133
compileJavacc {
134-
arguments = [grammar_encoding: 'UTF-8', static: 'false', java_template_type: 'modern']
134+
arguments = [
135+
grammar_encoding: 'UTF-8',
136+
static: 'false',
137+
java_template_type: 'modern',
138+
// Comment this in to build the parser with tracing.
139+
DEBUG_PARSER: 'false',
140+
DEBUG_LOOKAHEAD: 'false'
141+
]
135142
}
136143

137144
java {

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

Lines changed: 59 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,47 @@
1515
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
1616

1717
/**
18+
* Represents a JSON-Function.<br>
19+
* Currently supported are the types in {@link JsonFunctionType}.<br>
20+
* <br>
21+
* For JSON_OBJECT the parameters are available from {@link #getKeyValuePairs()}<br>
22+
* <br>
23+
* For JSON_ARRAY the parameters are availble from {@link #getExpressions()}.<br>
24+
*
1825
* @author <a href="mailto:[email protected]">Andreas Reichel</a>
1926
*/
20-
2127
public class JsonFunction extends ASTNodeAccessImpl implements Expression {
2228
private final ArrayList<JsonKeyValuePair> keyValuePairs = new ArrayList<>();
2329
private final ArrayList<JsonFunctionExpression> expressions = new ArrayList<>();
2430
private JsonFunctionType functionType;
2531
private JsonAggregateOnNullType onNullType;
2632
private JsonAggregateUniqueKeysType uniqueKeysType;
2733

34+
private boolean isStrict = false;
35+
36+
public JsonFunction() {}
37+
38+
public JsonFunction(JsonFunctionType functionType) {
39+
this.functionType = functionType;
40+
}
41+
42+
/**
43+
* Returns the Parameters of an JSON_OBJECT<br>
44+
* The KeyValuePairs may not have both key and value set, in some cases only the Key is set.
45+
*
46+
* @see net.sf.jsqlparser.parser.feature.Feature#allowCommaAsKeyValueSeparator
47+
*
48+
* @return A List of KeyValuePairs, never NULL
49+
*/
2850
public ArrayList<JsonKeyValuePair> getKeyValuePairs() {
2951
return keyValuePairs;
3052
}
3153

54+
/**
55+
* Returns the parameters of JSON_ARRAY<br>
56+
*
57+
* @return A List of {@link JsonFunctionExpression}s, never NULL
58+
*/
3259
public ArrayList<JsonFunctionExpression> getExpressions() {
3360
return expressions;
3461
}
@@ -114,6 +141,19 @@ public JsonFunction withType(String typeName) {
114141
return this;
115142
}
116143

144+
public boolean isStrict() {
145+
return isStrict;
146+
}
147+
148+
public void setStrict(boolean strict) {
149+
isStrict = strict;
150+
}
151+
152+
public JsonFunction withStrict(boolean strict) {
153+
this.setStrict(strict);
154+
return this;
155+
}
156+
117157
@Override
118158
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
119159
return expressionVisitor.visit(this, context);
@@ -123,13 +163,9 @@ public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
123163
public StringBuilder append(StringBuilder builder) {
124164
switch (functionType) {
125165
case OBJECT:
126-
appendObject(builder);
127-
break;
128166
case POSTGRES_OBJECT:
129-
appendPostgresObject(builder);
130-
break;
131167
case MYSQL_OBJECT:
132-
appendMySqlObject(builder);
168+
appendObject(builder);
133169
break;
134170
case ARRAY:
135171
appendArray(builder);
@@ -148,35 +184,37 @@ public StringBuilder appendObject(StringBuilder builder) {
148184
if (i > 0) {
149185
builder.append(", ");
150186
}
151-
if (keyValuePair.isUsingValueKeyword()) {
152-
if (keyValuePair.isUsingKeyKeyword()) {
153-
builder.append("KEY ");
154-
}
155-
builder.append(keyValuePair.getKey()).append(" VALUE ")
156-
.append(keyValuePair.getValue());
157-
} else {
158-
builder.append(keyValuePair.getKey()).append(":").append(keyValuePair.getValue());
159-
}
160-
161-
if (keyValuePair.isUsingFormatJson()) {
162-
builder.append(" FORMAT JSON");
163-
}
187+
keyValuePair.append(builder);
164188
i++;
165189
}
166190

191+
appendOnNullType(builder);
192+
if (isStrict) {
193+
builder.append(" STRICT");
194+
}
195+
appendUniqueKeys(builder);
196+
197+
builder.append(" ) ");
198+
199+
return builder;
200+
}
201+
202+
private void appendOnNullType(StringBuilder builder) {
167203
if (onNullType != null) {
168204
switch (onNullType) {
169205
case NULL:
170206
builder.append(" NULL ON NULL");
171207
break;
172208
case ABSENT:
173-
builder.append(" ABSENT On NULL");
209+
builder.append(" ABSENT ON NULL");
174210
break;
175211
default:
176212
// this should never happen
177213
}
178214
}
215+
}
179216

217+
private void appendUniqueKeys(StringBuilder builder) {
180218
if (uniqueKeysType != null) {
181219
switch (uniqueKeysType) {
182220
case WITH:
@@ -189,41 +227,6 @@ public StringBuilder appendObject(StringBuilder builder) {
189227
// this should never happen
190228
}
191229
}
192-
193-
builder.append(" ) ");
194-
195-
return builder;
196-
}
197-
198-
199-
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
200-
public StringBuilder appendPostgresObject(StringBuilder builder) {
201-
builder.append("JSON_OBJECT( ");
202-
for (JsonKeyValuePair keyValuePair : keyValuePairs) {
203-
builder.append(keyValuePair.getKey());
204-
if (keyValuePair.getValue() != null) {
205-
builder.append(", ").append(keyValuePair.getValue());
206-
}
207-
}
208-
builder.append(" ) ");
209-
210-
return builder;
211-
}
212-
213-
public StringBuilder appendMySqlObject(StringBuilder builder) {
214-
builder.append("JSON_OBJECT( ");
215-
int i = 0;
216-
for (JsonKeyValuePair keyValuePair : keyValuePairs) {
217-
if (i > 0) {
218-
builder.append(", ");
219-
}
220-
builder.append(keyValuePair.getKey());
221-
builder.append(", ").append(keyValuePair.getValue());
222-
i++;
223-
}
224-
builder.append(" ) ");
225-
226-
return builder;
227230
}
228231

229232
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
@@ -239,18 +242,7 @@ public StringBuilder appendArray(StringBuilder builder) {
239242
i++;
240243
}
241244

242-
if (onNullType != null) {
243-
switch (onNullType) {
244-
case NULL:
245-
builder.append(" NULL ON NULL ");
246-
break;
247-
case ABSENT:
248-
builder.append(" ABSENT ON NULL ");
249-
break;
250-
default:
251-
// "ON NULL" was omitted
252-
}
253-
}
245+
appendOnNullType(builder);
254246
builder.append(") ");
255247

256248
return builder;

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@
1414
* @author <a href="mailto:[email protected]">Andreas Reichel</a>
1515
*/
1616
public enum JsonFunctionType {
17-
OBJECT, ARRAY, POSTGRES_OBJECT, MYSQL_OBJECT;
17+
OBJECT, ARRAY,
18+
19+
/**
20+
* Not used anymore
21+
*/
22+
@Deprecated
23+
POSTGRES_OBJECT,
24+
25+
/**
26+
* Not used anymore
27+
*/
28+
@Deprecated
29+
MYSQL_OBJECT;
1830

1931
public static JsonFunctionType from(String type) {
2032
return Enum.valueOf(JsonFunctionType.class, type.toUpperCase());

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

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,27 @@
2020
public class JsonKeyValuePair implements Serializable {
2121
private final Object key;
2222
private final Object value;
23-
private boolean usingKeyKeyword = false;
24-
private boolean usingValueKeyword = false;
23+
private boolean usingKeyKeyword;
24+
private JsonKeyValuePairSeparator separator;
2525
private boolean usingFormatJson = false;
2626

27+
/**
28+
* Please use the Constructor with {@link JsonKeyValuePairSeparator} parameter.
29+
*/
30+
@Deprecated
2731
public JsonKeyValuePair(Object key, Object value, boolean usingKeyKeyword,
2832
boolean usingValueKeyword) {
33+
this(key, value, usingKeyKeyword, usingValueKeyword ? JsonKeyValuePairSeparator.VALUE
34+
: JsonKeyValuePairSeparator.COLON);
35+
}
36+
37+
public JsonKeyValuePair(Object key, Object value, boolean usingKeyKeyword,
38+
JsonKeyValuePairSeparator separator) {
2939
this.key = Objects.requireNonNull(key, "The KEY of the Pair must not be null");
3040
this.value = value;
3141
this.usingKeyKeyword = usingKeyKeyword;
32-
this.usingValueKeyword = usingValueKeyword;
42+
this.separator =
43+
Objects.requireNonNull(separator, "The KeyValuePairSeparator must not be NULL");
3344
}
3445

3546
public boolean isUsingKeyKeyword() {
@@ -45,19 +56,45 @@ public JsonKeyValuePair withUsingKeyKeyword(boolean usingKeyKeyword) {
4556
return this;
4657
}
4758

59+
/**
60+
* Use {@link #getSeparator()}
61+
*/
62+
@Deprecated
4863
public boolean isUsingValueKeyword() {
49-
return usingValueKeyword;
64+
return separator == JsonKeyValuePairSeparator.VALUE;
5065
}
5166

67+
/**
68+
* Use {@link #setSeparator(JsonKeyValuePairSeparator)}
69+
*/
70+
@Deprecated
5271
public void setUsingValueKeyword(boolean usingValueKeyword) {
53-
this.usingValueKeyword = usingValueKeyword;
72+
separator = usingValueKeyword ? JsonKeyValuePairSeparator.VALUE
73+
: JsonKeyValuePairSeparator.COLON;
5474
}
5575

76+
/**
77+
* Use {@link #withSeparator(JsonKeyValuePairSeparator)}
78+
*/
79+
@Deprecated
5680
public JsonKeyValuePair withUsingValueKeyword(boolean usingValueKeyword) {
5781
this.setUsingValueKeyword(usingValueKeyword);
5882
return this;
5983
}
6084

85+
public JsonKeyValuePairSeparator getSeparator() {
86+
return separator;
87+
}
88+
89+
public void setSeparator(JsonKeyValuePairSeparator separator) {
90+
this.separator = separator;
91+
}
92+
93+
public JsonKeyValuePair withSeparator(JsonKeyValuePairSeparator separator) {
94+
this.setSeparator(separator);
95+
return this;
96+
}
97+
6198
public boolean isUsingFormatJson() {
6299
return usingFormatJson;
63100
}
@@ -102,13 +139,14 @@ public Object getValue() {
102139
}
103140

104141
public StringBuilder append(StringBuilder builder) {
105-
if (isUsingValueKeyword()) {
106-
if (isUsingKeyKeyword()) {
107-
builder.append("KEY ");
108-
}
109-
builder.append(getKey()).append(" VALUE ").append(getValue());
110-
} else {
111-
builder.append(getKey()).append(":").append(getValue());
142+
if (isUsingKeyKeyword() && getSeparator() == JsonKeyValuePairSeparator.VALUE) {
143+
builder.append("KEY ");
144+
}
145+
builder.append(getKey());
146+
147+
if (getValue() != null) {
148+
builder.append(getSeparator().getSeparatorString());
149+
builder.append(getValue());
112150
}
113151

114152
if (isUsingFormatJson()) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.sf.jsqlparser.expression;
2+
3+
/**
4+
* Describes the string used to separate the key from the value.
5+
*/
6+
public enum JsonKeyValuePairSeparator {
7+
VALUE(" VALUE "), COLON(":"),
8+
9+
// Used in MySQL dialect
10+
COMMA(","),
11+
12+
// Is used in case they KeyValuePair has only a key and no value
13+
NOT_USED("");
14+
15+
private final String separator;
16+
17+
JsonKeyValuePairSeparator(String separator) {
18+
this.separator = separator;
19+
}
20+
21+
public String getSeparatorString() {
22+
return separator;
23+
}
24+
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,19 @@ public enum Feature {
809809
* "EXPORT"
810810
*/
811811
export,
812+
813+
/**
814+
* MySQL allows a ',' as a separator between key and value entries. We allow that by default,
815+
* but it can be disabled here
816+
*/
817+
allowCommaAsKeyValueSeparator(true),
818+
819+
/**
820+
* DB2 and Oracle allow Expressions as JSON_OBJECT key values. This clashes with Informix and
821+
* Snowflake Json-Extraction syntax
822+
*/
823+
allowExpressionAsJsonObjectKey(false)
824+
812825
;
813826

814827
private final Object value;

0 commit comments

Comments
 (0)