diff --git a/ruleset.xml b/ruleset.xml
index 912f4f0b2..5cf9d1d29 100644
--- a/ruleset.xml
+++ b/ruleset.xml
@@ -104,6 +104,9 @@ under the License.
+
+
+
diff --git a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java
index 2b9e7e472..7edd1e717 100644
--- a/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java
+++ b/src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java
@@ -179,7 +179,7 @@ public void setIgnoreNulls(boolean ignoreNulls) {
}
@Override
- @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.MissingBreakInSwitch"})
public String toString() {
StringBuilder b = new StringBuilder();
diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java
index 7f4ced067..8aa1995d0 100644
--- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java
@@ -168,4 +168,8 @@ public interface ExpressionVisitor {
void visit(XMLSerializeExpr aThis);
void visit(TimezoneExpression aThis);
+
+ void visit(JsonAggregateFunction aThis);
+
+ void visit(JsonFunction aThis);
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java
index 89b2dbe4f..443f14500 100644
--- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java
@@ -585,4 +585,24 @@ public void visit(XMLSerializeExpr expr) {
public void visit(TimezoneExpression expr) {
expr.getLeftExpression().accept(this);
}
+
+ @Override
+ public void visit(JsonAggregateFunction expression) {
+ Expression expr = expression.getExpression();
+ if (expr!=null) {
+ expr.accept(this);
+ }
+
+ expr = expression.getFilterExpression();
+ if (expr!=null) {
+ expr.accept(this);
+ }
+ }
+
+ @Override
+ public void visit(JsonFunction expression) {
+ for (JsonFunctionExpression expr: expression.getExpressions()) {
+ expr.getExpression().accept(this);
+ }
+ }
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/FilterOverImpl.java b/src/main/java/net/sf/jsqlparser/expression/FilterOverImpl.java
new file mode 100644
index 000000000..f3a28349d
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/FilterOverImpl.java
@@ -0,0 +1,140 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.util.List;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+import net.sf.jsqlparser.statement.select.OrderByElement;
+
+/**
+ *
+ * @author tw
+ */
+public class FilterOverImpl extends ASTNodeAccessImpl {
+ private final OrderByClause orderBy = new OrderByClause();
+ private final PartitionByClause partitionBy = new PartitionByClause();
+ private AnalyticType analyticType = AnalyticType.FILTER_ONLY;
+ private Expression filterExpression = null;
+ private WindowElement windowElement = null;
+
+ public AnalyticType getAnalyticType() {
+ return analyticType;
+ }
+
+ public void setAnalyticType(AnalyticType analyticType) {
+ this.analyticType = analyticType;
+ }
+
+ public FilterOverImpl withAnalyticType(AnalyticType analyticType) {
+ this.setAnalyticType(analyticType);
+ return this;
+ }
+
+ public List getOrderByElements() {
+ return orderBy.getOrderByElements();
+ }
+
+ public void setOrderByElements(List orderByElements) {
+ orderBy.setOrderByElements(orderByElements);
+ }
+
+ public FilterOverImpl withOrderByElements(List orderByElements) {
+ this.setOrderByElements(orderByElements);
+ return this;
+ }
+
+ public ExpressionList getPartitionExpressionList() {
+ return partitionBy.getPartitionExpressionList();
+ }
+
+ public void setPartitionExpressionList(ExpressionList partitionExpressionList) {
+ setPartitionExpressionList(partitionExpressionList, false);
+ }
+
+ public void setPartitionExpressionList(ExpressionList partitionExpressionList, boolean brackets) {
+ partitionBy.setPartitionExpressionList(partitionExpressionList, brackets);
+ }
+
+ public boolean isPartitionByBrackets() {
+ return partitionBy.isBrackets();
+ }
+
+ public Expression getFilterExpression() {
+ return filterExpression;
+ }
+
+ public void setFilterExpression(Expression filterExpression) {
+ this.filterExpression = filterExpression;
+ }
+
+
+ public FilterOverImpl withFilterExpression(Expression filterExpression) {
+ this.setFilterExpression(filterExpression);
+ return this;
+ }
+
+ public WindowElement getWindowElement() {
+ return windowElement;
+ }
+
+ public void setWindowElement(WindowElement windowElement) {
+ this.windowElement = windowElement;
+ }
+
+ public FilterOverImpl withWindowElement(WindowElement windowElement) {
+ this.setWindowElement(windowElement);
+ return this;
+ }
+
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", "PMD.MissingBreakInSwitch"})
+ public StringBuilder append(StringBuilder builder) {
+ if (filterExpression != null) {
+ builder.append("FILTER (WHERE ");
+ builder.append(filterExpression.toString());
+ builder.append(")");
+ if (analyticType != AnalyticType.FILTER_ONLY) {
+ builder.append(" ");
+ }
+ }
+
+ switch (analyticType) {
+ case FILTER_ONLY:
+ return builder;
+ case WITHIN_GROUP:
+ builder.append("WITHIN GROUP");
+ break;
+ default:
+ builder.append("OVER");
+ }
+ builder.append(" (");
+
+ partitionBy.toStringPartitionBy(builder);
+ orderBy.toStringOrderByElements(builder);
+
+ if (windowElement != null) {
+ if (orderBy.getOrderByElements() != null) {
+ builder.append(' ');
+ }
+ builder.append(windowElement);
+ }
+
+ builder.append(")");
+
+ return builder;
+ }
+
+ @Override
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ return append(builder).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java
new file mode 100644
index 000000000..c49086106
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateFunction.java
@@ -0,0 +1,288 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.util.List;
+import java.util.Objects;
+import net.sf.jsqlparser.statement.select.OrderByElement;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+
+public class JsonAggregateFunction extends FilterOverImpl implements Expression {
+ private JsonFunctionType functionType;
+
+ private Expression expression = null;
+ private final OrderByClause expressionOrderBy = new OrderByClause();
+
+ private boolean usingKeyKeyword = false;
+ private String key;
+ private boolean usingValueKeyword = false;
+ private Object value;
+
+ private boolean usingFormatJson = false;
+
+ private JsonAggregateOnNullType onNullType;
+ private JsonAggregateUniqueKeysType uniqueKeysType;
+
+
+ public JsonAggregateOnNullType getOnNullType() {
+ return onNullType;
+ }
+
+ public void setOnNullType(JsonAggregateOnNullType onNullType) {
+ this.onNullType = onNullType;
+ }
+
+ public JsonAggregateFunction withOnNullType(JsonAggregateOnNullType onNullType) {
+ this.setOnNullType(onNullType);
+ return this;
+ }
+
+ public JsonAggregateUniqueKeysType getUniqueKeysType() {
+ return uniqueKeysType;
+ }
+
+ public void setUniqueKeysType(JsonAggregateUniqueKeysType uniqueKeysType) {
+ this.uniqueKeysType = uniqueKeysType;
+ }
+
+ public JsonAggregateFunction withUniqueKeysType(JsonAggregateUniqueKeysType uniqueKeysType) {
+ this.setUniqueKeysType(uniqueKeysType);
+ return this;
+ }
+
+ public JsonFunctionType getType() {
+ return functionType;
+ }
+
+ public void setType(JsonFunctionType type) {
+ this.functionType = Objects.requireNonNull(type, "The Type of the JSON Aggregate Function must not be null");
+ }
+
+ public JsonAggregateFunction withType(JsonFunctionType type) {
+ this.setType(type);
+ return this;
+ }
+
+ public void setType(String typeName) {
+ this.functionType = JsonFunctionType
+ .valueOf( Objects.requireNonNull(typeName, "The Type of the JSON Aggregate Function must not be null").toUpperCase());
+ }
+
+ public JsonAggregateFunction withType(String typeName) {
+ this.setType(typeName);
+ return this;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public void setExpression(Expression expression) {
+ this.expression = expression;
+ }
+
+ public JsonAggregateFunction withExpression(Expression expression) {
+ this.setExpression(expression);
+ return this;
+ }
+
+ public boolean isUsingKeyKeyword() {
+ return usingKeyKeyword;
+ }
+
+ public void setUsingKeyKeyword(boolean usingKeyKeyword) {
+ this.usingKeyKeyword = usingKeyKeyword;
+ }
+
+ public JsonAggregateFunction withUsingKeyKeyword(boolean usingKeyKeyword) {
+ this.setUsingKeyKeyword(usingKeyKeyword);
+ return this;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public JsonAggregateFunction withKey(String key) {
+ this.setKey(key);
+ return this;
+ }
+
+ public boolean isUsingValueKeyword() {
+ return usingValueKeyword;
+ }
+
+ public void setUsingValueKeyword(boolean usingValueKeyword) {
+ this.usingValueKeyword = usingValueKeyword;
+ }
+
+ public JsonAggregateFunction withUsingValueKeyword(boolean usingValueKeyword) {
+ this.setUsingValueKeyword(usingValueKeyword);
+ return this;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ public JsonAggregateFunction withValue(Object value) {
+ this.setValue(value);
+ return this;
+ }
+
+ public boolean isUsingFormatJson() {
+ return usingFormatJson;
+ }
+
+ public void setUsingFormatJson(boolean usingFormatJson) {
+ this.usingFormatJson = usingFormatJson;
+ }
+
+ public JsonAggregateFunction withUsingFormatJson(boolean usingFormatJson) {
+ this.setUsingFormatJson(usingFormatJson);
+ return this;
+ }
+
+ public List getExpressionOrderByElements() {
+ return expressionOrderBy.getOrderByElements();
+ }
+
+ public void setExpressionOrderByElements(List orderByElements) {
+ expressionOrderBy.setOrderByElements(orderByElements);
+ }
+
+ public JsonAggregateFunction withExpressionOrderByElements(List orderByElements) {
+ this.setExpressionOrderByElements(orderByElements);
+ return this;
+ }
+
+ @Override
+ public void accept(ExpressionVisitor expressionVisitor) {
+ expressionVisitor.visit(this);
+ }
+
+ // avoid countless Builder --> String conversion
+ @Override
+ public StringBuilder append(StringBuilder builder) {
+ switch (functionType) {
+ case OBJECT:
+ appendObject(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;
+ }
+
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public StringBuilder appendObject(StringBuilder builder) {
+ builder.append("JSON_OBJECTAGG( ");
+ if (usingValueKeyword) {
+ if (usingKeyKeyword) {
+ builder.append("KEY ");
+ }
+ builder.append(key).append(" VALUE ").append(value);
+ } else {
+ builder.append(key).append(":").append(value);
+ }
+
+ if (usingFormatJson) {
+ builder.append(" FORMAT JSON");
+ }
+
+ if (onNullType!=null) {
+ switch(onNullType) {
+ case NULL:
+ builder.append(" NULL ON NULL");
+ break;
+ case ABSENT:
+ builder.append(" ABSENT On NULL");
+ break;
+ default:
+ // this should never happen
+ }
+ }
+
+ if (uniqueKeysType!=null) {
+ switch(uniqueKeysType) {
+ case WITH:
+ builder.append(" WITH UNIQUE KEYS");
+ break;
+ case WITHOUT:
+ builder.append(" WITHOUT UNIQUE KEYS");
+ break;
+ default:
+ // this should never happen
+ }
+ }
+
+ builder.append(" ) ");
+
+
+ // FILTER( WHERE expression ) OVER windowNameOrSpecification
+ super.append(builder);
+
+ return builder;
+ }
+
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public StringBuilder appendArray(StringBuilder builder) {
+ builder.append("JSON_ARRAYAGG( ");
+ builder.append(expression).append(" ");
+
+ if (usingFormatJson) {
+ builder.append("FORMAT JSON ");
+ }
+
+ expressionOrderBy.toStringOrderByElements(builder);
+
+ if (onNullType!=null) {
+ switch(onNullType) {
+ case NULL:
+ builder.append(" NULL ON NULL ");
+ break;
+ case ABSENT:
+ builder.append(" ABSENT On NULL ");
+ break;
+ default:
+ // "ON NULL" was ommitted
+ }
+ }
+ builder.append(") ");
+
+
+ // FILTER( WHERE expression ) OVER windowNameOrSpecification
+ super.append(builder);
+
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ return append(builder).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java
new file mode 100644
index 000000000..bc3ec2a00
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateOnNullType.java
@@ -0,0 +1,35 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+/*
+ * Copyright (C) 2021 JSQLParser.
+ *
+ * This library is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with this library;
+ * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+package net.sf.jsqlparser.expression;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+public enum JsonAggregateOnNullType {
+ NULL
+ , ABSENT
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java
new file mode 100644
index 000000000..2f0b18d21
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonAggregateUniqueKeysType.java
@@ -0,0 +1,35 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+/*
+ * Copyright (C) 2021 JSQLParser.
+ *
+ * This library is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with this library;
+ * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+package net.sf.jsqlparser.expression;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+public enum JsonAggregateUniqueKeysType {
+ WITH
+ , WITHOUT
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
new file mode 100644
index 000000000..41c480d93
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
@@ -0,0 +1,222 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.expression;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+
+public class JsonFunction extends ASTNodeAccessImpl implements Expression {
+ private JsonFunctionType functionType;
+ private final ArrayList keyValuePairs = new ArrayList<>();
+ private final ArrayList expressions = new ArrayList<>();
+ private JsonAggregateOnNullType onNullType;
+ private JsonAggregateUniqueKeysType uniqueKeysType;
+
+ public ArrayList getKeyValuePairs() {
+ return keyValuePairs;
+ }
+
+ public ArrayList getExpressions() {
+ return expressions;
+ }
+
+ public JsonKeyValuePair getKeyValuePair(int i) {
+ return keyValuePairs.get(i);
+ }
+
+ public JsonFunctionExpression getExpression(int i) {
+ return expressions.get(i);
+ }
+
+ public boolean add(JsonKeyValuePair keyValuePair) {
+ return keyValuePairs.add(keyValuePair);
+ }
+
+ public void add(int i, JsonKeyValuePair keyValuePair) {
+ keyValuePairs.add(i, keyValuePair);
+ }
+
+ public boolean add(JsonFunctionExpression expression) {
+ return expressions.add(expression);
+ }
+
+ public void add(int i, JsonFunctionExpression expression) {
+ expressions.add(i, expression);
+ }
+
+ public JsonAggregateOnNullType getOnNullType() {
+ return onNullType;
+ }
+
+ public void setOnNullType(JsonAggregateOnNullType onNullType) {
+ this.onNullType = onNullType;
+ }
+
+ public JsonFunction withOnNullType(JsonAggregateOnNullType onNullType) {
+ this.setOnNullType(onNullType);
+ return this;
+ }
+
+ public JsonAggregateUniqueKeysType getUniqueKeysType() {
+ return uniqueKeysType;
+ }
+
+ public void setUniqueKeysType(JsonAggregateUniqueKeysType uniqueKeysType) {
+ this.uniqueKeysType = uniqueKeysType;
+ }
+
+ public JsonFunction withUniqueKeysType(JsonAggregateUniqueKeysType uniqueKeysType) {
+ this.setUniqueKeysType(uniqueKeysType);
+ return this;
+ }
+
+ public JsonFunctionType getType() {
+ return functionType;
+ }
+
+ public void setType(JsonFunctionType type) {
+ this.functionType =
+ Objects.requireNonNull(type, "The Type of the JSON Aggregate Function must not be null");
+ }
+
+ public JsonFunction withType(JsonFunctionType type) {
+ this.setType(type);
+ return this;
+ }
+
+ public void setType(String typeName) {
+ this.functionType = JsonFunctionType.valueOf(
+ Objects.requireNonNull(typeName, "The Type of the JSON Aggregate Function must not be null")
+ .toUpperCase());
+ }
+
+ public JsonFunction withType(String typeName) {
+ this.setType(typeName);
+ return this;
+ }
+
+ @Override
+ public void accept(ExpressionVisitor expressionVisitor) {
+ expressionVisitor.visit(this);
+ }
+
+ // avoid countless Builder --> String conversion
+ public StringBuilder append(StringBuilder builder) {
+ switch (functionType) {
+ case OBJECT:
+ appendObject(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;
+ }
+
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public StringBuilder appendObject(StringBuilder builder) {
+ builder.append("JSON_OBJECT( ");
+ int i = 0;
+ for (JsonKeyValuePair keyValuePair : keyValuePairs) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ if (keyValuePair.isUsingValueKeyword()) {
+ if (keyValuePair.isUsingKeyKeyword()) {
+ builder.append("KEY ");
+ }
+ builder.append(keyValuePair.getKey()).append(" VALUE ").append(keyValuePair.getValue());
+ } else {
+ builder.append(keyValuePair.getKey()).append(":").append(keyValuePair.getValue());
+ }
+
+ if (keyValuePair.isUsingFormatJson()) {
+ builder.append(" FORMAT JSON");
+ }
+ i++;
+ }
+
+ if (onNullType != null) {
+ switch (onNullType) {
+ case NULL:
+ builder.append(" NULL ON NULL");
+ break;
+ case ABSENT:
+ builder.append(" ABSENT On NULL");
+ break;
+ default:
+ // this should never happen
+ }
+ }
+
+ if (uniqueKeysType != null) {
+ switch (uniqueKeysType) {
+ case WITH:
+ builder.append(" WITH UNIQUE KEYS");
+ break;
+ case WITHOUT:
+ builder.append(" WITHOUT UNIQUE KEYS");
+ break;
+ default:
+ // this should never happen
+ }
+ }
+
+ builder.append(" ) ");
+
+ return builder;
+ }
+
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
+ public StringBuilder appendArray(StringBuilder builder) {
+ builder.append("JSON_ARRAY( ");
+ int i = 0;
+
+ for (JsonFunctionExpression expr : expressions) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ expr.append(builder);
+ i++;
+ }
+
+ if (onNullType != null) {
+ switch (onNullType) {
+ case NULL:
+ builder.append(" NULL ON NULL ");
+ break;
+ case ABSENT:
+ builder.append(" ABSENT On NULL ");
+ break;
+ default:
+ // "ON NULL" was ommitted
+ }
+ }
+ builder.append(") ");
+
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ return append(builder).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java
new file mode 100644
index 000000000..c0558bb9e
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionExpression.java
@@ -0,0 +1,53 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+
+package net.sf.jsqlparser.expression;
+
+import java.util.Objects;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+
+public class JsonFunctionExpression {
+ private final Expression expression;
+
+ private boolean usingFormatJson = false;
+
+ public JsonFunctionExpression(Expression expression) {
+ this.expression = Objects.requireNonNull(expression, "The EXPRESSION must not be null");
+ }
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public boolean isUsingFormatJson() {
+ return usingFormatJson;
+ }
+
+ public void setUsingFormatJson(boolean usingFormatJson) {
+ this.usingFormatJson = usingFormatJson;
+ }
+
+ public JsonFunctionExpression withUsingFormatJson(boolean usingFormatJson) {
+ this.setUsingFormatJson(usingFormatJson);
+ return this;
+ }
+
+ public StringBuilder append(StringBuilder builder) {
+ return builder.append(getExpression()).append(isUsingFormatJson() ? " FORMAT JSON" : "");
+ }
+
+ @Override
+ public String toString() {
+ return append(new StringBuilder()).toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java
new file mode 100644
index 000000000..750a32109
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunctionType.java
@@ -0,0 +1,20 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+
+package net.sf.jsqlparser.expression;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+public enum JsonFunctionType {
+ OBJECT
+ , ARRAY
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java
new file mode 100644
index 000000000..7e52ffeb9
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonKeyValuePair.java
@@ -0,0 +1,126 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+
+package net.sf.jsqlparser.expression;
+
+import java.util.Objects;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+
+public class JsonKeyValuePair {
+ private final String key;
+ private boolean usingKeyKeyword = false;
+ private final Object value;
+ private boolean usingValueKeyword = false;
+ private boolean usingFormatJson = false;
+
+ 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.usingKeyKeyword = usingKeyKeyword;
+ this.usingValueKeyword = usingValueKeyword;
+ }
+
+ public boolean isUsingKeyKeyword() {
+ return usingKeyKeyword;
+ }
+
+ public void setUsingKeyKeyword(boolean usingKeyKeyword) {
+ this.usingKeyKeyword = usingKeyKeyword;
+ }
+
+ public JsonKeyValuePair withUsingKeyKeyword(boolean usingKeyKeyword) {
+ this.setUsingKeyKeyword(usingKeyKeyword);
+ return this;
+ }
+
+ public boolean isUsingValueKeyword() {
+ return usingValueKeyword;
+ }
+
+ public void setUsingValueKeyword(boolean usingValueKeyword) {
+ this.usingValueKeyword = usingValueKeyword;
+ }
+
+ public JsonKeyValuePair withUsingValueKeyword(boolean usingValueKeyword) {
+ this.setUsingValueKeyword(usingValueKeyword);
+ return this;
+ }
+
+ public boolean isUsingFormatJson() {
+ return usingFormatJson;
+ }
+
+ public void setUsingFormatJson(boolean usingFormatJson) {
+ this.usingFormatJson = usingFormatJson;
+ }
+
+ public JsonKeyValuePair withUsingFormatJson(boolean usingFormatJson) {
+ this.setUsingFormatJson(usingFormatJson);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 83 * hash + Objects.hashCode(this.key);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final JsonKeyValuePair other = (JsonKeyValuePair) obj;
+ return Objects.equals(this.key, other.key);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public StringBuilder append(StringBuilder builder) {
+ if (isUsingValueKeyword()) {
+ if (isUsingKeyKeyword()) {
+ builder.append("KEY ");
+ }
+ builder.append(getKey()).append(" VALUE ").append(getValue());
+ } else {
+ builder.append(getKey()).append(":").append(getValue());
+ }
+
+ if (isUsingFormatJson()) {
+ builder.append(" FORMAT JSON");
+ }
+
+ return builder;
+ }
+
+ @Override
+ public String toString() {
+ return append(new StringBuilder()).toString();
+ }
+
+}
diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
index 52402e9ff..08a3fabea 100644
--- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
+++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
@@ -31,7 +31,10 @@
import net.sf.jsqlparser.expression.IntervalExpression;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
import net.sf.jsqlparser.expression.JdbcParameter;
+import net.sf.jsqlparser.expression.JsonAggregateFunction;
import net.sf.jsqlparser.expression.JsonExpression;
+import net.sf.jsqlparser.expression.JsonFunction;
+import net.sf.jsqlparser.expression.JsonFunctionExpression;
import net.sf.jsqlparser.expression.KeepExpression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.MySQLGroupConcat;
@@ -1001,4 +1004,24 @@ public void visit(RollbackStatement rollbackStatement) {
public void visit(AlterSession alterSession) {
}
+
+ @Override
+ public void visit(JsonAggregateFunction expression) {
+ Expression expr = expression.getExpression();
+ if (expr!=null) {
+ expr.accept(this);
+ }
+
+ expr = expression.getFilterExpression();
+ if (expr!=null) {
+ expr.accept(this);
+ }
+ }
+
+ @Override
+ public void visit(JsonFunction expression) {
+ for (JsonFunctionExpression expr: expression.getExpressions()) {
+ expr.getExpression().accept(this);
+ }
+ }
}
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java
index f5b030fd8..3b7eb9f27 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java
@@ -33,7 +33,9 @@
import net.sf.jsqlparser.expression.IntervalExpression;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
import net.sf.jsqlparser.expression.JdbcParameter;
+import net.sf.jsqlparser.expression.JsonAggregateFunction;
import net.sf.jsqlparser.expression.JsonExpression;
+import net.sf.jsqlparser.expression.JsonFunction;
import net.sf.jsqlparser.expression.KeepExpression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.MySQLGroupConcat;
@@ -681,7 +683,7 @@ public void visit(Modulo modulo) {
}
@Override
- @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength"})
+ @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveMethodLength", "PMD.MissingBreakInSwitch"})
public void visit(AnalyticExpression aexpr) {
String name = aexpr.getName();
Expression expression = aexpr.getExpression();
@@ -993,4 +995,14 @@ public void visit(TimezoneExpression var) {
buffer.append(" AT TIME ZONE " + expr);
}
}
+
+ @Override
+ public void visit(JsonAggregateFunction expression) {
+ expression.append(buffer);
+ }
+
+ @Override
+ public void visit(JsonFunction expression) {
+ expression.append(buffer);
+ }
}
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java
index d42a8be81..5b2ea3606 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java
@@ -28,7 +28,9 @@
import net.sf.jsqlparser.expression.IntervalExpression;
import net.sf.jsqlparser.expression.JdbcNamedParameter;
import net.sf.jsqlparser.expression.JdbcParameter;
+import net.sf.jsqlparser.expression.JsonAggregateFunction;
import net.sf.jsqlparser.expression.JsonExpression;
+import net.sf.jsqlparser.expression.JsonFunction;
import net.sf.jsqlparser.expression.KeepExpression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.MySQLGroupConcat;
@@ -102,8 +104,6 @@
*/
@SuppressWarnings({"PMD.CyclomaticComplexity"})
public class ExpressionValidator extends AbstractValidator implements ExpressionVisitor {
-
-
@Override
public void visit(Addition addition) {
visitBinaryExpression(addition, " + ");
@@ -581,4 +581,14 @@ public void visit(XMLSerializeExpr xml) {
// TODO this feature seams very close to a jsqlparser-user usecase
}
+ @Override
+ public void visit(JsonAggregateFunction expression) {
+ // no idea what this is good for
+ }
+
+ @Override
+ public void visit(JsonFunction expression) {
+ // no idea what this is good for
+ }
+
}
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index e798a030d..77f490c83 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -119,7 +119,8 @@ SKIP:
TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
{
-
+
+|
|
|
|
@@ -240,8 +241,13 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
|
|
|
+|
+|
+|
+|
|
|
+|
|
|
|
@@ -3443,11 +3449,15 @@ Expression PrimaryExpression() #PrimaryExpression:
| LOOKAHEAD(JsonExpression()) retval=JsonExpression()
+ | LOOKAHEAD(JsonFunction()) retval = JsonFunction()
+
+ | LOOKAHEAD(JsonAggregateFunction()) retval = JsonAggregateFunction()
+
/* | LOOKAHEAD(FunctionWithCondParams()) retval = FunctionWithCondParams() */
| LOOKAHEAD(FullTextSearch()) retval = FullTextSearch()
- | LOOKAHEAD(Function()) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression((Function)retval) ]
+ | LOOKAHEAD(Function()) retval=Function() [ LOOKAHEAD(2) retval = AnalyticExpression( (Function) retval ) ]
| LOOKAHEAD(2) retval = IntervalExpression() { dateExpressionAllowed = false; }
@@ -3649,6 +3659,194 @@ JsonExpression JsonExpression() : {
}
}
+JsonFunction JsonFunction() : {
+ JsonFunction result = new JsonFunction();
+ boolean usingKeyKeyword = false;
+ boolean usingValueKeyword = false;
+ Token keyToken;
+ Token valueToken;
+ JsonKeyValuePair keyValuePair;
+
+ Expression expression;
+ JsonFunctionExpression functionExpression;
+
+}
+{
+ (
+ (
+ (
+ "(" { 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); }
+
+ [ { 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 ); }
+ )
+ ]
+
+ [
+ (
+ { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); }
+ )
+ |
+ (
+ { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); }
+ )
+ ]
+
+ ")"
+ )
+ |
+ (
+ { result.setType( JsonFunctionType.ARRAY ); }
+ "("
+ (
+ 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 ); }
+ ]
+
+ ")"
+ )
+ )
+ )
+
+ {
+ return result;
+ }
+}
+
+JsonAggregateFunction JsonAggregateFunction() : {
+ JsonAggregateFunction result = new JsonAggregateFunction();
+ Token token;
+ Expression expression;
+ List expressionOrderByList = null;
+
+ Expression filter;
+ ExpressionList expressionList = null;
+ List olist = null;
+ WindowElement windowElement = null;
+ boolean partitionByBrackets = false;
+}
+{
+ (
+ (
+ (
+ "(" { 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 ); }
+
+ [ { result.setUsingFormatJson( true ); } ]
+
+ [
+ LOOKAHEAD(2) (
+ { result.setOnNullType( JsonAggregateOnNullType.NULL ); }
+ )
+ |
+ (
+ { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); }
+ )
+ ]
+
+ [
+ (
+ { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITH ); }
+ )
+ |
+ (
+ { result.setUniqueKeysType( JsonAggregateUniqueKeysType.WITHOUT ); }
+ )
+ ]
+ ")"
+ )
+ |
+ (
+
+ "(" { result.setType( JsonFunctionType.ARRAY ); }
+ expression=Expression() { result.setExpression( expression ); }
+ [ { result.setUsingFormatJson( true ); } ]
+ [ expressionOrderByList = OrderByElements() { result.setExpressionOrderByElements( expressionOrderByList ); } ]
+
+ [
+ LOOKAHEAD(2) (
+ { result.setOnNullType( JsonAggregateOnNullType.NULL ); }
+ )
+ |
+ (
+ { result.setOnNullType( JsonAggregateOnNullType.ABSENT ); }
+ )
+ ]
+
+ ")"
+ )
+ )
+
+ // -- Filter
+ [ "(" { result.setAnalyticType(AnalyticType.FILTER_ONLY); } filter = Expression() { result.setFilterExpression( filter ); } ")" ]
+
+ // -- OVER
+ [ LOOKAHEAD(2)
+ {result.setAnalyticType(AnalyticType.OVER);}
+ "("
+ [
+ (LOOKAHEAD(ComplexExpressionList()) expressionList=ComplexExpressionList()
+ | "(" {partitionByBrackets = true;} expressionList=ComplexExpressionList() ")" )
+ ]
+ [olist=OrderByElements() ]
+ [windowElement = WindowElement() ]
+ {
+ result.setPartitionExpressionList(expressionList, partitionByBrackets);
+ result.setOrderByElements(olist);
+ result.setWindowElement(windowElement);
+ }
+ ")"
+ ]
+ )
+
+ {
+ return result;
+ }
+}
+
IntervalExpression IntervalExpression() : {
IntervalExpression interval;
Token token = null;
diff --git a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java
index 38924d6d9..32cbb3016 100644
--- a/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java
+++ b/src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java
@@ -234,4 +234,26 @@ public void testAtTimeZoneExpression() throws JSQLParserException {
ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter();
expr.accept(adapter);
}
+
+ @Test
+ public void testJsonFunction() throws JSQLParserException {
+ ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter();
+ CCJSqlParserUtil
+ .parseExpression("JSON_OBJECT( KEY foo VALUE bar, KEY foo VALUE bar)")
+ .accept(adapter);
+ CCJSqlParserUtil
+ .parseExpression("JSON_ARRAY( (SELECT * from dual) )")
+ .accept(adapter);
+ }
+
+ @Test
+ public void testJsonAggregateFunction() throws JSQLParserException {
+ ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter();
+ CCJSqlParserUtil
+ .parseExpression("JSON_OBJECTAGG( KEY foo VALUE bar NULL ON NULL WITH UNIQUE KEYS ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name )")
+ .accept(adapter);
+ CCJSqlParserUtil
+ .parseExpression("JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name )")
+ .accept(adapter);
+ }
}
diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java
new file mode 100644
index 000000000..e15dd9197
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/expression/JsonFunctionTest.java
@@ -0,0 +1,193 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2021 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+
+package net.sf.jsqlparser.expression;
+
+import java.util.Objects;
+import junit.framework.Assert;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.test.TestUtils;
+import org.junit.Test;
+
+/**
+ *
+ * @author Andreas Reichel
+ */
+public class JsonFunctionTest {
+ @Test
+ public void testObjectAgg() throws JSQLParserException {
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_OBJECTAGG( foo:bar) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( foo:bar FORMAT JSON) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar NULL ON NULL) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar ABSENT ON NULL) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar WITH UNIQUE KEYS) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar WITHOUT UNIQUE KEYS) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar NULL ON NULL WITH UNIQUE KEYS ) FROM dual ",
+ true);
+
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar NULL ON NULL WITH UNIQUE KEYS ) FILTER( WHERE name = 'Raj' ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar NULL ON NULL WITH UNIQUE KEYS ) OVER( PARTITION BY name ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECTAGG( KEY foo VALUE bar NULL ON NULL WITH UNIQUE KEYS ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name ) FROM dual ",
+ true);
+ }
+
+ @Test
+ public void testObjectBuilder() throws JSQLParserException {
+ JsonFunction f = new JsonFunction();
+ f.setType(JsonFunctionType.OBJECT);
+
+ JsonKeyValuePair keyValuePair1 = new JsonKeyValuePair("foo", "bar", false, false);
+ keyValuePair1.setUsingKeyKeyword(true);
+ keyValuePair1.setUsingValueKeyword(true);
+ f.add(keyValuePair1.withUsingFormatJson(true));
+
+ JsonKeyValuePair keyValuePair2 = new JsonKeyValuePair("foo", "bar", false, false).withUsingKeyKeyword(true).withUsingValueKeyword(true).withUsingFormatJson(false);
+
+ // this should work because we compare based on KEY only
+ Assert.assertEquals(keyValuePair1, keyValuePair2);
+
+ // this must fail because all the properties are considered
+ Assert.assertFalse(Objects.equals(keyValuePair1.toString(), keyValuePair2.toString()));
+
+ JsonKeyValuePair keyValuePair3 = new JsonKeyValuePair("foo", "bar", false, false).withUsingKeyKeyword(false).withUsingValueKeyword(false).withUsingFormatJson(false);
+ Assert.assertNotNull(keyValuePair3 );
+ Assert.assertEquals(keyValuePair3, keyValuePair3);
+ Assert.assertFalse(Objects.equals( keyValuePair3, f));
+
+ Assert.assertTrue(keyValuePair3.hashCode()!=0);
+
+ f.add(keyValuePair2);
+ }
+
+ @Test
+ public void testArrayBuilder() throws JSQLParserException {
+ JsonFunction f = new JsonFunction();
+ f.setType(JsonFunctionType.ARRAY);
+
+ JsonFunctionExpression expression1 = new JsonFunctionExpression(new NullValue());
+ expression1.setUsingFormatJson(true);
+
+ JsonFunctionExpression expression2 = new JsonFunctionExpression(new NullValue()).withUsingFormatJson(
+ true);
+
+ Assert.assertTrue(Objects.equals(expression1.toString(), expression2.toString()));
+
+ f.add(expression1);
+ f.add(expression2);
+ }
+
+ @Test
+ public void testArrayAgg() throws JSQLParserException {
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_ARRAYAGG( a ) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_ARRAYAGG( a ORDER BY a ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_ARRAYAGG( a NULL ON NULL ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_ARRAYAGG( a FORMAT JSON ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_ARRAYAGG( a FORMAT JSON NULL ON NULL ) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FILTER( WHERE name = 'Raj' ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) OVER( PARTITION BY name ) FROM dual ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_ARRAYAGG( a FORMAT JSON ABSENT ON NULL ) FILTER( WHERE name = 'Raj' ) OVER( PARTITION BY name ) FROM dual ",
+ true);
+
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT json_arrayagg(json_array(\"v0\") order by \"t\".\"v0\") FROM dual ", true);
+ }
+
+ @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 ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "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 ",
+ true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_OBJECT( KEY foo VALUE bar FORMAT JSON, foo:bar, foo:bar ABSENT ON NULL) FROM dual ",
+ true);
+
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object(null on null)", true);
+
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object(absent on null)", true);
+
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_object()", true);
+ }
+
+ @Test
+ public void testArray() throws JSQLParserException {
+ TestUtils.assertSqlCanBeParsedAndDeparsed(
+ "SELECT JSON_ARRAY( (SELECT * from dual) ) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_ARRAY( 1, 2, 3 ) FROM dual ", true);
+ TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT JSON_ARRAY( \"v0\" ) FROM dual ", true);
+ }
+
+ @Test
+ public void testArrayWithNullExpressions() throws JSQLParserException {
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("JSON_ARRAY( 1, 2, 3 )", true);
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_array(null on null)", true);
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_array(null null on null)", true);
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_array(null, null null on null)", true);
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("json_array()", true);
+ }
+
+ @Test
+ public void testIssue1260() throws JSQLParserException {
+ TestUtils.assertSqlCanBeParsedAndDeparsed("select \n" + " cast((\n" + " select coalesce(\n"
+ + " json_arrayagg(json_array(\"v0\") order by \"t\".\"v0\"),\n"
+ + " json_array(null on null)\n" + " )\n" + " from (\n"
+ + " select 2 \"v0\"\n" + " union\n" + " select 4 \"ID\"\n" + " ) \"t\"\n"
+ + " ) as text)", true);
+
+ TestUtils.assertExpressionCanBeParsedAndDeparsed("listagg( json_object(key 'v0' value \"v0\"), ',' )", true);
+
+ TestUtils.assertSqlCanBeParsedAndDeparsed("select (\n" +
+ " select coalesce(\n" +
+ " cast(('[' || listagg(\n" +
+ " json_object(key 'v0' value \"v0\"),\n" +
+ " ','\n" +
+ " ) || ']') as varchar(32672)),\n" +
+ " json_array()\n" +
+ " )\n" +
+ " from (\n" +
+ " select cast(null as timestamp) \"v0\"\n" +
+ " from SYSIBM.DUAL\n" +
+ " union all\n" +
+ " select timestamp '2000-03-15 10:15:00.0' \"a\"\n" +
+ " from SYSIBM.DUAL\n" +
+ " ) \"t\"\n" +
+ ")\n" +
+ "from SYSIBM.DUAL", true);
+ }
+}
diff --git a/src/test/java/net/sf/jsqlparser/test/TestUtils.java b/src/test/java/net/sf/jsqlparser/test/TestUtils.java
index a3822d1f1..e3617f357 100644
--- a/src/test/java/net/sf/jsqlparser/test/TestUtils.java
+++ b/src/test/java/net/sf/jsqlparser/test/TestUtils.java
@@ -283,6 +283,12 @@ public static void assertExpressionCanBeDeparsedAs(final Expression parsed, Stri
assertEquals(expression, stringBuilder.toString());
}
+
+ public static void assertExpressionCanBeParsedAndDeparsed(String expressionStr, boolean laxDeparsingCheck) throws JSQLParserException {
+ Expression expression = CCJSqlParserUtil.parseExpression(expressionStr);
+ assertEquals(buildSqlString(expressionStr, laxDeparsingCheck),
+ buildSqlString(expression.toString(), laxDeparsingCheck));
+ }
public static void assertOracleHintExists(String sql, boolean assertDeparser, String... hints)
throws JSQLParserException {
diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java
index 12d694cb2..e17fd1c33 100644
--- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java
+++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java
@@ -677,5 +677,26 @@ public void testUsing() throws JSQLParserException {
assertTrue(tableList.contains("A"));
assertTrue(tableList.contains("B.C"));
}
+
+ @Test
+ public void testJsonFunction() throws JSQLParserException {
+ String sql = "SELECT JSON_ARRAY( 1, 2, 3 ) FROM mytbl";
+ Statement stmt = CCJSqlParserUtil.parse(sql);
+ TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
+ List tableList = tablesNamesFinder.getTableList(stmt);
+ assertEquals(1, tableList.size());
+ assertTrue(tableList.contains("mytbl"));
+ }
+
+ @Test
+ public void testJsonAggregateFunction() throws JSQLParserException {
+ String sql = "SELECT JSON_ARRAYAGG( (SELECT * from dual) FORMAT JSON) FROM mytbl";
+ Statement stmt = CCJSqlParserUtil.parse(sql);
+ TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
+ List tableList = tablesNamesFinder.getTableList(stmt);
+ assertEquals(2, tableList.size());
+ assertTrue(tableList.contains("dual"));
+ assertTrue(tableList.contains("mytbl"));
+ }
}
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 dae05ff7c..3f85effbf 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
@@ -204,4 +204,28 @@ public void testAtTimeZoneExpression() throws JSQLParserException {
validateNoErrors("SELECT DATE(date1 AT TIME ZONE 'UTC' AT TIME ZONE 'australia/sydney') AS another_date FROM mytbl", 1,
EXPRESSIONS);
}
+
+ @Test
+ public void testJsonFunctionExpression() throws JSQLParserException {
+ validateNoErrors("SELECT json_array(null on null) FROM mytbl", 1,
+ EXPRESSIONS);
+ validateNoErrors("SELECT json_array(null null on null) FROM mytbl", 1,
+ EXPRESSIONS);
+ validateNoErrors("SELECT json_array(null, null null on null) FROM mytbl", 1,
+ EXPRESSIONS);
+
+ validateNoErrors("SELECT json_object(null on null) FROM mytbl", 1,
+ EXPRESSIONS);
+
+ validateNoErrors("SELECT json_object() FROM mytbl", 1,
+ EXPRESSIONS);
+ }
+
+ @Test
+ 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,
+ EXPRESSIONS);
+ }
}