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); + } }