diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index ada4bfdf4..13b82a21f 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -58,4 +58,6 @@
exports net.sf.jsqlparser.util.validation.feature;
exports net.sf.jsqlparser.util.validation.metadata;
exports net.sf.jsqlparser.util.validation.validator;
+ exports net.sf.jsqlparser.expression.json;
+ exports net.sf.jsqlparser.statement.from;
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
index aee8e7bf3..d74818b24 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonFunction.java
@@ -27,7 +27,7 @@
*/
public class JsonFunction extends ASTNodeAccessImpl implements Expression {
public enum JsonOnResponseBehaviorType {
- ERROR, NULL, DEFAULT, EMPTY_ARRAY, EMPTY_OBJECT, TRUE, FALSE, UNKNOWN
+ ERROR, NULL, DEFAULT, EMPTY, EMPTY_ARRAY, EMPTY_OBJECT, TRUE, FALSE, UNKNOWN
}
public enum JsonWrapperType {
@@ -42,6 +42,10 @@ public enum JsonQuotesType {
KEEP, OMIT
}
+ public enum ScalarsType {
+ ALLOW, DISALLOW
+ }
+
public static class JsonOnResponseBehavior {
private JsonOnResponseBehaviorType type;
private Expression expression;
@@ -82,6 +86,9 @@ public StringBuilder append(StringBuilder builder) {
case DEFAULT:
builder.append("DEFAULT ").append(expression);
break;
+ case EMPTY:
+ builder.append("EMPTY ");
+ break;
case EMPTY_ARRAY:
builder.append("EMPTY ARRAY");
break;
@@ -98,6 +105,7 @@ public StringBuilder append(StringBuilder builder) {
builder.append("UNKNOWN");
break;
default:
+ throw new IllegalStateException("Unhandled JsonOnResponseBehavior: " + type );
// this should never happen
}
return builder;
@@ -130,6 +138,7 @@ public String toString() {
private boolean wrapperArray;
private JsonQuotesType quotesType;
private boolean quotesOnScalarString;
+ private ScalarsType scalarsType;
public JsonFunction() {}
@@ -294,6 +303,14 @@ public void setQuotesOnScalarString(boolean quotesOnScalarString) {
this.quotesOnScalarString = quotesOnScalarString;
}
+ public ScalarsType getScalarsType() {
+ return scalarsType;
+ }
+
+ public void setScalarsType(ScalarsType type) {
+ this.scalarsType = type;
+ }
+
public boolean isEmpty() {
return keyValuePairs.isEmpty();
}
diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java
index b7f5d0149..629dc54ad 100644
--- a/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java
+++ b/src/main/java/net/sf/jsqlparser/expression/JsonTableFunction.java
@@ -33,7 +33,15 @@ public String getDisplay() {
}
public enum JsonTableOnErrorType {
- ERROR, EMPTY
+ ERROR, NULL, EMPTY
+ }
+
+ public enum JsonTableOnEmptyType {
+ ERROR, NULL, EMPTY
+ }
+
+ public enum JsonTableParsingType {
+ STRICT, LAX
}
public static class JsonTablePassingClause extends ASTNodeAccessImpl implements Serializable {
@@ -78,10 +86,28 @@ public String toString() {
}
public static class JsonTableWrapperClause extends ASTNodeAccessImpl implements Serializable {
+ private boolean beforePathExpression;
private JsonFunction.JsonWrapperType wrapperType;
private JsonFunction.JsonWrapperMode wrapperMode;
private boolean array;
+ /**
+ * Creates a wrapper clause. Depending on the dialect, this clause can come before or after the PATH expression.
+ *
+ * - Trino: after PATH
+ * - Oracle: before PATH
+ *
+ *
+ * @param beforePathExpression A flag to determine wether the clause is rendered before or after the PATH expression
+ */
+ public JsonTableWrapperClause(boolean beforePathExpression) {
+ this.beforePathExpression = beforePathExpression;
+ }
+
+ public boolean isBeforePathExpression() {
+ return beforePathExpression;
+ }
+
public JsonFunction.JsonWrapperType getWrapperType() {
return wrapperType;
}
@@ -159,6 +185,15 @@ public String toString() {
public static class JsonTableOnErrorClause extends ASTNodeAccessImpl implements Serializable {
private JsonTableOnErrorType type;
+ private boolean beforeColumns = true;
+
+ public JsonTableOnErrorClause(boolean beforeColumns) {
+ this.beforeColumns = beforeColumns;
+ }
+
+ public boolean isBeforeColumns() {
+ return beforeColumns;
+ }
public JsonTableOnErrorType getType() {
return type;
@@ -175,6 +210,48 @@ public String toString() {
}
}
+ public static class JsonTableOnEmptyClause extends ASTNodeAccessImpl implements Serializable {
+ private JsonTableOnEmptyType type;
+
+ public JsonTableOnEmptyClause() {
+ }
+
+ public JsonTableOnEmptyType getType() {
+ return type;
+ }
+
+ public JsonTableOnEmptyClause setType(JsonTableOnEmptyType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return type + " ON EMPTY";
+ }
+ }
+
+ public static class JsonTableParsingTypeClause extends ASTNodeAccessImpl implements Serializable {
+ private JsonTableParsingType type;
+
+ public JsonTableParsingTypeClause() {
+ }
+
+ public JsonTableParsingType getType() {
+ return type;
+ }
+
+ public JsonTableParsingTypeClause setType(JsonTableParsingType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "TYPE(" + type + ")";
+ }
+ }
+
public static class JsonTablePlanTerm extends ASTNodeAccessImpl implements Serializable {
private JsonTablePlanExpression nestedPlanExpression;
private String name;
@@ -395,6 +472,7 @@ public static class JsonTableValueColumnDefinition extends JsonTableColumnDefini
private JsonTableQuotesClause quotesClause;
private JsonFunction.JsonOnResponseBehavior onEmptyBehavior;
private JsonFunction.JsonOnResponseBehavior onErrorBehavior;
+ private JsonFunction.ScalarsType scalarsType;
public String getColumnName() {
return columnName;
@@ -489,6 +567,14 @@ public JsonTableValueColumnDefinition setOnErrorBehavior(
return this;
}
+ public void setScalarsType(JsonFunction.ScalarsType scalarsType) {
+ this.scalarsType = scalarsType;
+ }
+
+ public JsonFunction.ScalarsType getScalarsType() {
+ return scalarsType;
+ }
+
@Override
public void collectExpressions(List expressions) {
if (pathExpression != null) {
@@ -509,18 +595,27 @@ public String toString() {
builder.append(" FOR ORDINALITY");
return builder.toString();
}
-
- builder.append(" ").append(dataType);
+ if (dataType != null) {
+ builder.append(" ").append(dataType);
+ }
if (formatJson) {
builder.append(" FORMAT JSON");
if (encoding != null) {
builder.append(" ENCODING ").append(encoding);
}
}
+ if (scalarsType != null) {
+ builder.append(" ");
+ builder.append(scalarsType);
+ builder.append(" SCALARS");
+ }
+ if (wrapperClause != null && wrapperClause.isBeforePathExpression()) {
+ builder.append(" ").append(wrapperClause);
+ }
if (pathExpression != null) {
builder.append(" PATH ").append(pathExpression);
}
- if (wrapperClause != null) {
+ if (wrapperClause != null && !wrapperClause.isBeforePathExpression()) {
builder.append(" ").append(wrapperClause);
}
if (quotesClause != null) {
@@ -580,11 +675,23 @@ public String toString() {
private JsonTableColumnsClause columnsClause;
private JsonTablePlanClause planClause;
private JsonTableOnErrorClause onErrorClause;
+ private JsonTableParsingTypeClause parsingTypeClause;
+ private JsonTableOnEmptyClause onEmptyClause;
+ private boolean formatJson;
public JsonTableFunction() {
setName("JSON_TABLE");
}
+ public boolean getFormatJson() {
+ return formatJson;
+ }
+
+ public JsonTableFunction setFormatJson(boolean formatJson) {
+ this.formatJson = formatJson;
+ return this;
+ }
+
public Expression getJsonInputExpression() {
return jsonInputExpression;
}
@@ -648,6 +755,24 @@ public JsonTableFunction setOnErrorClause(JsonTableOnErrorClause onErrorClause)
return this;
}
+ public JsonTableParsingTypeClause getParsingTypeClause() {
+ return parsingTypeClause;
+ }
+
+ public JsonTableFunction setParsingTypeClause(JsonTableParsingTypeClause parsingTypeClause) {
+ this.parsingTypeClause = parsingTypeClause;
+ return this;
+ }
+
+ public JsonTableOnEmptyClause getOnEmptyClause() {
+ return onEmptyClause;
+ }
+
+ public JsonTableFunction setOnEmptyClause(JsonTableOnEmptyClause onEmptyClause) {
+ this.onEmptyClause = onEmptyClause;
+ return this;
+ }
+
public List getAllExpressions() {
List expressions = new ArrayList<>();
if (jsonInputExpression != null) {
@@ -676,7 +801,13 @@ public T accept(ExpressionVisitor expressionVisitor, S context) {
@Override
public String toString() {
StringBuilder builder = new StringBuilder("JSON_TABLE(");
- builder.append(jsonInputExpression).append(", ").append(jsonPathExpression);
+ builder.append(jsonInputExpression);
+ if (formatJson) {
+ builder.append(" FORMAT JSON");
+ }
+ if (jsonPathExpression != null) {
+ builder.append(", ").append(jsonPathExpression);
+ }
if (pathName != null) {
builder.append(" AS ").append(pathName);
}
@@ -691,11 +822,20 @@ public String toString() {
first = false;
}
}
+ if (onErrorClause != null && onErrorClause.isBeforeColumns()) {
+ builder.append(" ").append(onErrorClause);
+ }
+ if (parsingTypeClause != null) {
+ builder.append(" ").append(parsingTypeClause);
+ }
+ if (onEmptyClause != null) {
+ builder.append(" ").append(onEmptyClause);
+ }
builder.append(" ").append(columnsClause);
if (planClause != null) {
builder.append(" ").append(planClause);
}
- if (onErrorClause != null) {
+ if (onErrorClause != null && !onErrorClause.isBeforeColumns()) {
builder.append(" ").append(onErrorClause);
}
builder.append(")");
diff --git a/src/main/java/net/sf/jsqlparser/expression/json/JsonOnEmptyType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnEmptyType.java
new file mode 100644
index 000000000..c78a852d1
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnEmptyType.java
@@ -0,0 +1,48 @@
+/*-
+ * #%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.json;
+
+/**
+ *
+ */
+public enum JsonOnEmptyType {
+ ERROR("ERROR"), NULL("NULL"), EMPTY("EMPTY"), EMPTY_ARRAY("EMPTY ARRAY"), EMPTY_OBJECT(
+ "EMPTY OBJECT"), TRUE("TRUE"), FALSE("FALSE"), DEFAULT("DEFAULT");
+
+ private final String value;
+
+ JsonOnEmptyType(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static JsonOnEmptyType from(String type) {
+ return Enum.valueOf(JsonOnEmptyType.class, type.toUpperCase());
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/json/JsonOnErrorType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnErrorType.java
new file mode 100644
index 000000000..3c5b50b88
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnErrorType.java
@@ -0,0 +1,48 @@
+/*-
+ * #%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.json;
+
+/**
+ *
+ */
+public enum JsonOnErrorType {
+ ERROR("ERROR"), NULL("NULL"), EMPTY("EMPTY"), EMPTY_ARRAY("EMPTY ARRAY"), EMPTY_OBJECT(
+ "EMPTY OBJECT"), TRUE("TRUE"), FALSE("FALSE"), DEFAULT("DEFAULT");
+
+ private final String value;
+
+ JsonOnErrorType(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static JsonOnErrorType from(String type) {
+ return Enum.valueOf(JsonOnErrorType.class, type.toUpperCase());
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/json/JsonQueryWrapperType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonQueryWrapperType.java
new file mode 100644
index 000000000..bd719511f
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonQueryWrapperType.java
@@ -0,0 +1,77 @@
+/*-
+ * #%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.json;
+
+/**
+ *
+ */
+public enum JsonQueryWrapperType {
+ //@formatter:off
+ WITHOUT_WRAPPER("WITHOUT WRAPPER"),
+ WITHOUT_ARRAY_WRAPPER("WITHOUT ARRAY WRAPPER"),
+ WITH_WRAPPER("WITH WRAPPER"),
+ WITH_ARRAY_WRAPPER("WITH ARRAY WRAPPER"),
+ WITH_UNCONDITIONAL_WRAPPER("WITH UNCONDITIONAL WRAPPER"),
+ WITH_UNCONDITIONAL_ARRAY_WRAPPER("WITH UNCONDITIONAL ARRAY WRAPPER"),
+ WITH_CONDITIONAL_WRAPPER("WITH CONDITIONAL WRAPPER"),
+ WITH_CONDITIONAL_ARRAY_WRAPPER("WITH CONDITIONAL ARRAY WRAPPER");
+ //@formatter:on
+
+ private final String value;
+
+ JsonQueryWrapperType(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static JsonQueryWrapperType from(String type) {
+ return Enum.valueOf(JsonQueryWrapperType.class, type.toUpperCase());
+ }
+
+ public static JsonQueryWrapperType fromWithParts(boolean isArray, boolean isConditional,
+ boolean isUnconditional) {
+ if (isArray) {
+ if (isConditional) {
+ return JsonQueryWrapperType.WITH_CONDITIONAL_ARRAY_WRAPPER;
+ } else if (isUnconditional) {
+ return JsonQueryWrapperType.WITH_UNCONDITIONAL_ARRAY_WRAPPER;
+ } else {
+ return JsonQueryWrapperType.WITH_ARRAY_WRAPPER;
+ }
+ } else {
+ if (isConditional) {
+ return JsonQueryWrapperType.WITH_CONDITIONAL_WRAPPER;
+ } else if (isUnconditional) {
+ return JsonQueryWrapperType.WITH_UNCONDITIONAL_WRAPPER;
+ } else {
+ return JsonQueryWrapperType.WITH_WRAPPER;
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnClause.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnClause.java
new file mode 100644
index 000000000..b58c29c9c
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnClause.java
@@ -0,0 +1,71 @@
+package net.sf.jsqlparser.expression.json;
+
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+public class JsonReturnClause extends ASTNodeAccessImpl {
+
+ private JsonReturnType type;
+
+ private Long varcharSize;
+
+ public JsonReturnClause() {}
+
+ public JsonReturnClause(JsonReturnType type) {
+ this.type = type;
+ }
+
+ public JsonReturnType getType() {
+ return type;
+ }
+
+ public void setType(JsonReturnType type) {
+ this.type = type;
+ }
+
+ public JsonReturnClause withType(JsonReturnType type) {
+ setType(type);
+ return this;
+ }
+
+ public Long getVarcharSize() {
+ return varcharSize;
+ }
+
+ public void setVarcharSize(Long varcharSize) {
+ this.varcharSize = varcharSize;
+ }
+
+ public JsonReturnClause withVarcharSize(Long varcharSize) {
+ setVarcharSize(varcharSize);
+ return this;
+ }
+
+ public StringBuilder append(StringBuilder builder) {
+ builder.append(" ");
+ builder.append(type.getValue());
+ switch (type) {
+ case VARCHAR2:
+ case VARCHAR2_BYTE:
+ case VARCHAR2_CHAR:
+ if (varcharSize != null) {
+ builder.append("(");
+ builder.append(varcharSize);
+ switch (type) {
+ case VARCHAR2_BYTE:
+ builder.append(" BYTE");
+ break;
+ case VARCHAR2_CHAR:
+ builder.append(" CHAR");
+ break;
+ }
+ builder.append(")");
+ }
+ break;
+ default:
+ // Nothing to do
+ break;
+ }
+ return builder;
+ }
+
+}
diff --git a/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnType.java
new file mode 100644
index 000000000..24b57739e
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnType.java
@@ -0,0 +1,73 @@
+/*-
+ * #%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.json;
+
+/**
+ *
+ */
+public enum JsonReturnType {
+ VARCHAR2("VARCHAR2"), CLOB("CLOB"), BLOB("BLOB"), NUMBER("NUMBER"), DATE("DATE"), TIMESTAMP(
+ "TIMESTAMP"), TIMESTAMP_WITH_TIMEZONE(
+ "TIMESTAMP WITH TIMEZONE"), BOOLEAN("BOOLEAN"), VECTOR("VECTOR"), JSON("JSON"),
+
+ // VARCHAR2( x BYTE)
+ VARCHAR2_BYTE("VARCHAR2"),
+
+ // VARCHAR2( x CHAR)
+ VARCHAR2_CHAR("VARCHAR2"),
+ ;
+
+ private final String value;
+
+ JsonReturnType(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public static JsonReturnType from(String type) {
+ return Enum.valueOf(JsonReturnType.class, type.toUpperCase());
+ }
+
+ /**
+ * @see "https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/JSON_QUERY.html#GUID-6D396EC4-D2AA-43D2-8F5D-08D646A4A2D9__CJADJIIJ"
+ */
+ public boolean isValidForJsonQueryReturnType() {
+ switch (this) {
+ case VARCHAR2:
+ case CLOB:
+ case BLOB:
+ case JSON:
+ case VECTOR:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/from/JsonTable.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTable.java
new file mode 100644
index 000000000..47f73c3d9
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTable.java
@@ -0,0 +1,202 @@
+package net.sf.jsqlparser.statement.from;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.json.JsonOnEmptyType;
+import net.sf.jsqlparser.expression.json.JsonOnErrorType;
+import net.sf.jsqlparser.statement.select.AbstractFromitem;
+import net.sf.jsqlparser.statement.select.FromItem;
+import net.sf.jsqlparser.statement.select.FromItemVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class JsonTable extends AbstractFromitem implements FromItem {
+
+ private Expression expression;
+ private boolean isFormatJson = false;
+
+ private String pathExpression;
+
+ private JsonOnErrorType onErrorType;
+ private JsonTableType type;
+ private JsonOnEmptyType onEmptyType;
+
+ private List jsonColumns = new ArrayList<>();
+
+ public StringBuilder append(StringBuilder builder) {
+ builder.append("JSON_TABLE(");
+ builder.append(expression.toString());
+
+ if (isFormatJson) {
+ builder.append(" FORMAT JSON");
+ }
+
+ if (pathExpression != null) {
+ builder.append(", '");
+ builder.append(pathExpression);
+ builder.append("'");
+ }
+
+ if (onErrorType != null) {
+ builder.append(" ");
+ builder.append(onErrorType);
+ builder.append(" ON ERROR");
+ }
+
+ if (type != null) {
+ builder.append(" TYPE(");
+ builder.append(type);
+ builder.append(")");
+ }
+
+ if (onEmptyType != null) {
+ builder.append(" ");
+ builder.append(onEmptyType);
+ builder.append(" ON EMPTY");
+ }
+
+ builder.append(" COLUMNS(");
+
+ for (JsonTableColumn column : jsonColumns) {
+ column.append(builder);
+ }
+
+ builder.append("))");
+
+ super.appendTo(builder, getAlias(), getSampleClause(), getPivot(), getUnPivot());
+
+ return builder;
+ }
+
+ public void setExpression(Expression expression) {
+ this.expression = expression;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public JsonTable withExpression(Expression expression) {
+ setExpression(expression);
+ return this;
+ }
+
+ public void setPathExpression(String pathExpression) {
+ this.pathExpression = pathExpression;
+ }
+
+ public String getPathExpression() {
+ return pathExpression;
+ }
+
+ public JsonTable withPathExpression(String pathExpression) {
+ setPathExpression(pathExpression);
+ return this;
+ }
+
+ public void setFormatJson(boolean usingJson) {
+ this.isFormatJson = true;
+ }
+
+ public boolean isFormatJson() {
+ return isFormatJson;
+ }
+
+ public JsonTable withFormatJson(boolean isFormatJson) {
+ setFormatJson(isFormatJson);
+ return this;
+ }
+
+ public void setOnErrorType(JsonOnErrorType onErrorType) {
+ if (onErrorType != null) {
+ switch (onErrorType) {
+ case NULL:
+ case ERROR:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "OnError type " + onErrorType + " is not allowed in JSON_TABLE");
+ }
+ }
+
+ this.onErrorType = onErrorType;
+ }
+
+ /**
+ * Returns the ON ERROR clause or NULL if none is set
+ *
+ * @return JsonOnErrorType or NULL
+ */
+ public JsonOnErrorType getOnErrorType() {
+ return onErrorType;
+ }
+
+ public JsonTable withOnErrorType(JsonOnErrorType onErrorType) {
+ setOnErrorType(onErrorType);
+ return this;
+ }
+
+ public void setType(JsonTableType type) {
+ this.type = type;
+ }
+
+ public JsonTableType getType() {
+ return type;
+ }
+
+ public JsonTable withType(JsonTableType type) {
+ setType(type);
+ return this;
+ }
+
+ public void setOnEmptyType(JsonOnEmptyType onEmptyType) {
+ if (onEmptyType != null) {
+ switch (onEmptyType) {
+ case NULL:
+ case ERROR:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "OnEmpty type " + onEmptyType + " is not allowed in JSON_TABLE");
+ }
+ }
+ this.onEmptyType = onEmptyType;
+ }
+
+ /**
+ * Returns the ON EMPTY clause or NULL if none is set
+ *
+ * @return JsonOnEmptyType or NULL
+ */
+ public JsonOnEmptyType getOnEmptyType() {
+ return onEmptyType;
+ }
+
+ public JsonTable withOnEmptyType(JsonOnEmptyType onEmptyType) {
+ setOnEmptyType(onEmptyType);
+ return this;
+ }
+
+ public void addColumn(JsonTableColumn column) {
+ this.jsonColumns.add(column);
+ }
+
+ public JsonTable withColumn(JsonTableColumn column) {
+ addColumn(column);
+ return this;
+ }
+
+ public List getColumns() {
+ return jsonColumns;
+ }
+
+ @Override
+ public String toString() {
+ return append(new StringBuilder()).toString();
+ }
+
+ @Override
+ public T accept(FromItemVisitor fromItemVisitor, S context) {
+ return fromItemVisitor.visit(this, context);
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumn.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumn.java
new file mode 100644
index 000000000..44a791c52
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumn.java
@@ -0,0 +1,287 @@
+package net.sf.jsqlparser.statement.from;
+
+import net.sf.jsqlparser.expression.json.JsonOnEmptyType;
+import net.sf.jsqlparser.expression.json.JsonOnErrorType;
+import net.sf.jsqlparser.expression.json.JsonQueryWrapperType;
+import net.sf.jsqlparser.expression.json.JsonReturnClause;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+public class JsonTableColumn extends ASTNodeAccessImpl {
+
+ private String name;
+ private JsonTableColumnType type;
+
+ private JsonReturnClause returnClause;
+
+ private boolean isFormatJson = false;
+ private String jsonPath;
+
+ // Can be true, false or NULL
+ private Boolean allowScalars;
+ private JsonQueryWrapperType queryWrapperType;
+ private JsonOnErrorType onErrorType;
+ private JsonOnEmptyType onEmptyType;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public JsonTableColumn withName(String columnName) {
+ setName(columnName);
+ return this;
+ }
+
+ public void setJsonPath(String jsonPath) {
+ this.jsonPath = jsonPath;
+ }
+
+ public String getJsonPath() {
+ return jsonPath;
+ }
+
+ public JsonTableColumn withJsonPath(String jsonPath) {
+ setJsonPath(jsonPath);
+ return this;
+ }
+
+ public void setType(JsonTableColumnType type) {
+ this.type = type;
+ }
+
+ public JsonTableColumn withType(JsonTableColumnType type) {
+ setType(type);
+ return this;
+ }
+
+ public JsonTableColumnType getType() {
+ return type;
+ }
+
+ public void setFormatJson(boolean usingJson) {
+ if (usingJson && type != JsonTableColumnType.JSON_QUERY) {
+ throw new IllegalArgumentException(
+ "FORMAT JSON can only be used on JSON_QUERY-Columns");
+ }
+ this.isFormatJson = true;
+ }
+
+ public boolean isFormatJson() {
+ return isFormatJson;
+ }
+
+ public JsonTableColumn withFormatJson(boolean isFormatJson) {
+ setFormatJson(isFormatJson);
+ return this;
+ }
+
+ public void setOnEmptyType(JsonOnEmptyType onEmptyType) {
+ if (onEmptyType != null) {
+ switch (type) {
+ case JSON_EXISTS:
+ switch (onEmptyType) {
+ case TRUE:
+ case FALSE:
+ case ERROR:
+ break;
+ default:
+ throw new IllegalArgumentException("OnEmpty type " + onEmptyType
+ + " is not allowed in JsonTableColumn of type " + type);
+ }
+ break;
+ case JSON_QUERY:
+ case ORDINALITY:
+ throw new IllegalArgumentException("OnEmpty type " + onEmptyType
+ + " is not allowed in JsonTableColumn of type " + type);
+ }
+ }
+ this.onEmptyType = onEmptyType;
+ }
+
+ /**
+ * Returns the ON EMPTY clause or NULL if none is set
+ *
+ * @return JsonOnEmptyType or NULL
+ */
+ public JsonOnEmptyType getOnEmptyType() {
+ return onEmptyType;
+ }
+
+ public JsonTableColumn withOnEmptyType(JsonOnEmptyType onEmptyType) {
+ setOnEmptyType(onEmptyType);
+ return this;
+ }
+
+ public void setOnErrorType(JsonOnErrorType onErrorType) {
+ if (onErrorType != null) {
+ switch (type) {
+ case JSON_EXISTS:
+ switch (onErrorType) {
+ case TRUE:
+ case FALSE:
+ case ERROR:
+ break;
+ default:
+ throw new IllegalArgumentException("OnError type " + onErrorType
+ + " is not allowed in JsonTableColumn of type " + type);
+ }
+ break;
+ case JSON_QUERY:
+ switch (onErrorType) {
+ case ERROR:
+ case NULL:
+ case EMPTY:
+ case EMPTY_ARRAY:
+ case EMPTY_OBJECT:
+ break;
+ default:
+ throw new IllegalArgumentException("OnError type " + onErrorType
+ + " is not allowed in JsonTableColumn of type " + type);
+ }
+ break;
+ case ORDINALITY:
+ throw new IllegalArgumentException("OnError type " + onErrorType
+ + " is not allowed in JsonTableColumn of type " + type);
+ }
+ }
+
+ this.onErrorType = onErrorType;
+ }
+
+ /**
+ * Returns the ON ERROR clause or NULL if none is set
+ *
+ * @return JsonOnErrorType or NULL
+ */
+ public JsonOnErrorType getOnErrorType() {
+ return onErrorType;
+ }
+
+ public JsonTableColumn withOnErrorType(JsonOnErrorType onErrorType) {
+ setOnErrorType(onErrorType);
+ return this;
+ }
+
+ public void setQueryWrapperType(JsonQueryWrapperType queryWrapperType) {
+ if (type != JsonTableColumnType.JSON_QUERY) {
+ throw new IllegalArgumentException(
+ "QueryWrapperType is only allowed on columns with type JSON_QUERY");
+ }
+ this.queryWrapperType = queryWrapperType;
+ }
+
+ public JsonQueryWrapperType getQueryWrapperType() {
+ return queryWrapperType;
+ }
+
+ public JsonTableColumn withQueryWrapperType(JsonQueryWrapperType queryWrapperType) {
+ setQueryWrapperType(queryWrapperType);
+ return this;
+ }
+
+ public Boolean getAllowScalars() {
+ return allowScalars;
+ }
+
+ public void setAllowScalars(Boolean allowScalars) {
+ if (allowScalars != null && type != JsonTableColumnType.JSON_QUERY) {
+ throw new IllegalArgumentException(
+ "AllowScalars is only allowed on columns with type JSON_QUERY");
+ }
+ this.allowScalars = allowScalars;
+ }
+
+ public JsonTableColumn withAllowScalars(Boolean allowScalars) {
+ setAllowScalars(allowScalars);
+ return this;
+ }
+
+ public JsonReturnClause getReturnClause() {
+ return returnClause;
+ }
+
+ public void setReturnClause(JsonReturnClause returnClause) {
+ this.returnClause = returnClause;
+ }
+
+ public JsonTableColumn withReturnClause(JsonReturnClause returnClause) {
+ setReturnClause(returnClause);
+ return this;
+ }
+
+ public StringBuilder append(StringBuilder builder) {
+
+ builder.append(name);
+
+ switch (type) {
+ case ORDINALITY:
+ builder.append(" FOR ORDINALITY");
+ break;
+ case JSON_EXISTS:
+ appendJsonExists(builder);
+ break;
+ case JSON_QUERY:
+ appendJsonQuery(builder);
+ break;
+ default:
+ throw new IllegalStateException("Type " + type + " is unknown");
+ }
+
+ return builder;
+ }
+
+ private void appendJsonQuery(StringBuilder builder) {
+ if (returnClause != null) {
+ returnClause.append(builder);
+ }
+ if (isFormatJson) {
+ builder.append(" FORMAT JSON");
+ }
+ if (allowScalars != null) {
+ if (allowScalars) {
+ builder.append(" ALLOW");
+ } else {
+ builder.append(" DISALLOW");
+ }
+ builder.append(" SCALARS");
+ }
+ if (queryWrapperType != null) {
+ builder.append(" ");
+ builder.append(queryWrapperType.getValue());
+ }
+ if (jsonPath != null) {
+ builder.append(" PATH '");
+ builder.append(jsonPath);
+ builder.append("'");
+ }
+ if (onErrorType != null) {
+ builder.append(" ");
+ builder.append(onErrorType.getValue());
+ builder.append(" ON ERROR");
+ }
+ }
+
+ private void appendJsonExists(StringBuilder builder) {
+ // TODO: Append return type
+ builder.append(" EXISTS");
+ if (jsonPath != null) {
+ builder.append(" PATH '");
+ builder.append(jsonPath);
+ builder.append("'");
+ }
+ if (onErrorType != null) {
+ builder.append(" ");
+ builder.append(onErrorType);
+ builder.append(" ON ERROR");
+ }
+ if (onEmptyType != null) {
+ builder.append(" ");
+ builder.append(onEmptyType);
+ builder.append(" ON EMPTY");
+ }
+ }
+
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumnType.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumnType.java
new file mode 100644
index 000000000..82c978bbc
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumnType.java
@@ -0,0 +1,5 @@
+package net.sf.jsqlparser.statement.from;
+
+public enum JsonTableColumnType {
+ JSON_EXISTS, JSON_QUERY, JSON_VALUE, JSON_NESTED_PATH, ORDINALITY
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/from/JsonTableType.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableType.java
new file mode 100644
index 000000000..5b2ae37cd
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableType.java
@@ -0,0 +1,37 @@
+/*-
+ * #%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.statement.from;
+
+/**
+ *
+ */
+public enum JsonTableType {
+ STRICT, LAX;
+
+ public static JsonTableType from(String type) {
+ return Enum.valueOf(JsonTableType.class, type.toUpperCase());
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/AbstractFromitem.java b/src/main/java/net/sf/jsqlparser/statement/select/AbstractFromitem.java
new file mode 100644
index 000000000..726a8ead5
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/select/AbstractFromitem.java
@@ -0,0 +1,53 @@
+package net.sf.jsqlparser.statement.select;
+
+import net.sf.jsqlparser.expression.Alias;
+import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
+
+public abstract class AbstractFromitem extends ASTNodeAccessImpl implements FromItem {
+ private Alias alias;
+ private Pivot pivot;
+ private UnPivot unPivot;
+ private SampleClause sampleClause = null;
+
+ @Override
+ public Alias getAlias() {
+ return alias;
+ }
+
+ @Override
+ public void setAlias(Alias alias) {
+ this.alias = alias;
+ }
+
+ @Override
+ public Pivot getPivot() {
+ return pivot;
+ }
+
+ @Override
+ public void setPivot(Pivot pivot) {
+ this.pivot = pivot;
+ }
+
+ @Override
+ public UnPivot getUnPivot() {
+ return unPivot;
+ }
+
+ @Override
+ public void setUnPivot(UnPivot unpivot) {
+ this.unPivot = unpivot;
+ }
+
+ @Override
+ public SampleClause getSampleClause() {
+ return sampleClause;
+ }
+
+ @Override
+ public FromItem setSampleClause(SampleClause sampleClause) {
+ this.sampleClause = sampleClause;
+ return this;
+ }
+
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java
index ed4432003..4a3c43d67 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java
@@ -10,6 +10,7 @@
package net.sf.jsqlparser.statement.select;
import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.from.JsonTable;
import net.sf.jsqlparser.statement.imprt.Import;
import net.sf.jsqlparser.statement.piped.FromQuery;
@@ -104,4 +105,10 @@ default void visit(Import imprt) {
}
T visit(FromQuery fromQuery, S context);
+
+ default void visit(JsonTable jsonTable) {
+ this.visit(jsonTable, null);
+ }
+
+ T visit(JsonTable jsonTable, S context);
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java
index 783b614f2..b634adf25 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java
@@ -14,6 +14,7 @@
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.from.JsonTable;
import net.sf.jsqlparser.statement.imprt.Import;
import net.sf.jsqlparser.statement.piped.FromQuery;
@@ -148,4 +149,9 @@ public T visit(Import imprt, S context) {
public T visit(FromQuery fromQuery, S context) {
return fromQuery.accept(selectVisitor, context);
}
+
+ @Override
+ public T visit(JsonTable jsonTable, S context) {
+ return jsonTable.getExpression().accept(expressionVisitor, context);
+ }
}
diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
index 09ca9faba..dd6a820e7 100644
--- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
+++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
@@ -123,6 +123,7 @@
import net.sf.jsqlparser.statement.select.FromItemVisitor;
import net.sf.jsqlparser.statement.select.FunctionAllColumns;
import net.sf.jsqlparser.statement.select.Join;
+import net.sf.jsqlparser.statement.from.JsonTable;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.ParenthesedFromItem;
@@ -1783,6 +1784,11 @@ public Void visit(JsonTableFunction expression, S context) {
return null;
}
+ @Override
+ public Void visit(JsonTable jsonTable, S context) {
+ return jsonTable.getExpression().accept(this, context);
+ }
+
@Override
public Void visit(ConnectByRootOperator connectByRootOperator, S context) {
connectByRootOperator.getColumn().accept(this, context);
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
index ba7d8ef3d..5b33a4d24 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
@@ -19,6 +19,7 @@
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
+import net.sf.jsqlparser.statement.from.JsonTable;
import net.sf.jsqlparser.expression.MySQLIndexHint;
import net.sf.jsqlparser.expression.OracleHint;
import net.sf.jsqlparser.expression.SQLServerHints;
@@ -925,6 +926,12 @@ public StringBuilder visit(FromQuery fromQuery, S context) {
return builder;
}
+ @Override
+ public StringBuilder visit(JsonTable jsonTable, S context) {
+ jsonTable.append(builder);
+ return builder;
+ }
+
public void visit(TableFunction tableFunction) {
visit(tableFunction, null);
}
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
index 36741ecd6..25da61708 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
@@ -12,6 +12,7 @@
import java.util.List;
import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.statement.from.JsonTable;
import net.sf.jsqlparser.expression.MySQLIndexHint;
import net.sf.jsqlparser.expression.SQLServerHints;
import net.sf.jsqlparser.parser.feature.Feature;
@@ -422,6 +423,11 @@ public Void visit(FromQuery fromQuery, S context) {
return null;
}
+ @Override
+ public Void visit(JsonTable jsonTable, S context) {
+ return null;
+ }
+
public void visit(TableFunction tableFunction) {
visit(tableFunction, null);
}
@@ -438,4 +444,6 @@ public void visit(Import imprt) {
visit(imprt, null);
}
+
+
}
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 8344b3309..782541d47 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -38,6 +38,7 @@ import java.lang.Integer;
import net.sf.jsqlparser.parser.feature.*;
import net.sf.jsqlparser.expression.*;
+import net.sf.jsqlparser.expression.json.*;
import net.sf.jsqlparser.expression.operators.arithmetic.*;
import net.sf.jsqlparser.expression.operators.conditional.*;
import net.sf.jsqlparser.expression.operators.relational.*;
@@ -58,6 +59,7 @@ import net.sf.jsqlparser.statement.create.table.*;
import net.sf.jsqlparser.statement.create.view.*;
import net.sf.jsqlparser.statement.delete.*;
import net.sf.jsqlparser.statement.drop.*;
+import net.sf.jsqlparser.statement.from.*;
import net.sf.jsqlparser.statement.insert.*;
import net.sf.jsqlparser.statement.execute.*;
import net.sf.jsqlparser.statement.piped.*;
@@ -772,6 +774,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -844,6 +847,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -857,6 +861,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -921,6 +926,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -1288,6 +1294,7 @@ TOKEN : /* Data Types */
| <#TYPE_BIT: "BISTRING">
| <#TYPE_BLOB: "BLOB" | "BYTEA" | | "VARBINARY" | >
| <#TYPE_BOOLEAN: | "BOOL" >
+ | <#TYPE_CLOB: "CLOB">
| <#TYPE_ENUM: "ENUM" >
| <#TYPE_MAP: "MAP" >
| <#TYPE_DECIMAL: "DECIMAL" | "NUMBER" | "NUMERIC" >
@@ -1303,7 +1310,7 @@ TOKEN : /* Data Types */
| <#TYPE_UHUGEINT: "UHUGEINT" >
| <#TYPE_REAL: "REAL" | "FLOAT4" | "FLOAT">
| <#TYPE_DOUBLE: "DOUBLE" | "PRECISION" | "FLOAT8" | "FLOAT64">
- | <#TYPE_VARCHAR: "NVARCHAR" | "VARCHAR" | "NCHAR" | | "BPCHAR" | "TEXT" | "STRING" | | "VARYING">
+ | <#TYPE_VARCHAR: "NVARCHAR" | "VARCHAR" | "NCHAR" | | "BPCHAR" | "TEXT" | "STRING" | | "VARYING" | "VARCHAR2">
| <#TYPE_TIME: "TIMETZ" >
| <#TYPE_TIMESTAMP: "TIMESTAMP_NS" | "TIMESTAMP_MS" | "TIMESTAMP_S" >
@@ -5681,6 +5688,8 @@ FromItem FromItem() #FromItem:
|| (getToken(1).kind == K_LATERAL && getToken(2).kind != OPENING_BRACKET) })
fromItem=TableFunction()
|
+// fromItem=JsonTable()
+// |
LOOKAHEAD(3) fromItem=Table()
|
LOOKAHEAD(ParenthesedFromItem()) fromItem = ParenthesedFromItem()
@@ -8159,6 +8168,12 @@ JsonFunction.JsonOnResponseBehavior JsonValueOnResponseBehavior() : {
JsonFunction.JsonOnResponseBehaviorType.NULL);
}
|
+
+ {
+ behavior = new JsonFunction.JsonOnResponseBehavior(
+ JsonFunction.JsonOnResponseBehaviorType.EMPTY);
+ }
+ |
expression = Expression()
{
behavior = new JsonFunction.JsonOnResponseBehavior(
@@ -8188,26 +8203,26 @@ JsonFunction.JsonOnResponseBehavior JsonQueryOnResponseBehavior() : {
JsonFunction.JsonOnResponseBehaviorType.NULL);
}
|
- token =
+
{
- if (!token.image.equalsIgnoreCase("EMPTY")) {
- throw new ParseException(
- "Expected EMPTY, ERROR or NULL but found " + token.image);
- }
+ behavior = new JsonFunction.JsonOnResponseBehavior(
+ JsonFunction.JsonOnResponseBehaviorType.EMPTY);
}
- (
-
- {
- behavior = new JsonFunction.JsonOnResponseBehavior(
- JsonFunction.JsonOnResponseBehaviorType.EMPTY_ARRAY);
- }
- |
- JsonKeyword("OBJECT")
- {
- behavior = new JsonFunction.JsonOnResponseBehavior(
- JsonFunction.JsonOnResponseBehaviorType.EMPTY_OBJECT);
- }
- )
+ [
+ (
+
+ {
+ behavior = new JsonFunction.JsonOnResponseBehavior(
+ JsonFunction.JsonOnResponseBehaviorType.EMPTY_ARRAY);
+ }
+ |
+ JsonKeyword("OBJECT")
+ {
+ behavior = new JsonFunction.JsonOnResponseBehavior(
+ JsonFunction.JsonOnResponseBehaviorType.EMPTY_OBJECT);
+ }
+ )
+ ]
)
{
if (behavior != null) {
@@ -8304,9 +8319,9 @@ JsonFunction JsonValueBody() : {
[ dataType = ColDataType() { result.setReturningType(dataType); } ]
[
- LOOKAHEAD( JsonValueOnResponseBehavior() )
+ LOOKAHEAD( JsonValueOnResponseBehavior() )
behavior = JsonValueOnResponseBehavior()
- JsonKeyword("EMPTY")
+
{ result.setOnEmptyBehavior(behavior); }
]
@@ -8410,9 +8425,9 @@ JsonFunction JsonQueryBody() : {
]
[
- LOOKAHEAD( JsonQueryOnResponseBehavior() )
+ LOOKAHEAD( JsonQueryOnResponseBehavior() )
behavior = JsonQueryOnResponseBehavior()
- JsonKeyword("EMPTY")
+
{ result.setOnEmptyBehavior(behavior); }
]
@@ -8491,9 +8506,9 @@ JsonFunction JsonQueryBody() : {
]
]
[
- LOOKAHEAD( JsonQueryOnResponseBehavior() )
+ LOOKAHEAD( JsonQueryOnResponseBehavior() )
additionalOnEmptyBehavior = JsonQueryOnResponseBehavior()
- JsonKeyword("EMPTY")
+
]
[
LOOKAHEAD( JsonQueryOnResponseBehavior() )
@@ -9405,9 +9420,9 @@ JsonFunction.JsonOnResponseBehavior JsonTableOnEmptyBehavior() : {
}
}
-JsonTableFunction.JsonTableWrapperClause JsonTableWrapperClause() : {
+JsonTableFunction.JsonTableWrapperClause JsonTableWrapperClause(boolean beforePathExpr) : {
JsonTableFunction.JsonTableWrapperClause wrapperClause =
- new JsonTableFunction.JsonTableWrapperClause();
+ new JsonTableFunction.JsonTableWrapperClause(beforePathExpr);
Token token;
}
{
@@ -9494,16 +9509,26 @@ JsonTableFunction.JsonTableColumnDefinition JsonTableColumnDefinition() : {
columnDefinition = valueColumnDefinition;
}
(
- JsonKeyword("ORDINALITY")
- { valueColumnDefinition.setForOrdinality(true); }
+ { valueColumnDefinition.setForOrdinality(true); }
|
- dataType = ColDataType() { valueColumnDefinition.setDataType(dataType); }
+ [ dataType = ColDataType() { valueColumnDefinition.setDataType(dataType); } ]
[
{ valueColumnDefinition.setFormatJson(true); }
[ encoding = JsonEncoding() { valueColumnDefinition.setEncoding(encoding); } ]
]
+ [
+ (
+ { valueColumnDefinition.setScalarsType(JsonFunction.ScalarsType.ALLOW); }
+ |
+ { valueColumnDefinition.setScalarsType(JsonFunction.ScalarsType.DISALLOW); }
+ )
+ JsonKeyword("SCALARS")
+ ]
+ // In Oracle, the wrapper clause comes before the PATH expression
+ [ wrapperClause = JsonTableWrapperClause(true) { valueColumnDefinition.setWrapperClause(wrapperClause); } ]
[ expression = Expression() { valueColumnDefinition.setPathExpression(expression); } ]
- [ wrapperClause = JsonTableWrapperClause() { valueColumnDefinition.setWrapperClause(wrapperClause); } ]
+ // In Truno the wrapper clause comes after the PATH expression
+ [ wrapperClause = JsonTableWrapperClause(false) { valueColumnDefinition.setWrapperClause(wrapperClause); } ]
[
LOOKAHEAD({
getToken(1).kind == K_KEEP
@@ -9513,14 +9538,14 @@ JsonTableFunction.JsonTableColumnDefinition JsonTableColumnDefinition() : {
quotesClause = JsonTableQuotesClause() { valueColumnDefinition.setQuotesClause(quotesClause); }
]
[
- LOOKAHEAD( JsonTableOnEmptyBehavior() )
+ LOOKAHEAD( JsonTableOnEmptyBehavior() )
behavior = JsonTableOnEmptyBehavior()
- JsonKeyword("EMPTY")
+
{ valueColumnDefinition.setOnEmptyBehavior(behavior); }
]
[
- LOOKAHEAD( JsonValueOnResponseBehavior() )
- behavior = JsonValueOnResponseBehavior()
+ LOOKAHEAD( JsonQueryOnResponseBehavior() )
+ behavior = JsonQueryOnResponseBehavior()
{ valueColumnDefinition.setOnErrorBehavior(behavior); }
]
@@ -9636,23 +9661,18 @@ JsonTableFunction.JsonTablePlanClause JsonTablePlanClause() : {
}
}
-JsonTableFunction.JsonTableOnErrorClause JsonTableOnErrorClause() : {
+JsonTableFunction.JsonTableOnErrorClause JsonTableOnErrorClause(boolean beforeColumns) : {
JsonTableFunction.JsonTableOnErrorClause onErrorClause =
- new JsonTableFunction.JsonTableOnErrorClause();
+ new JsonTableFunction.JsonTableOnErrorClause(beforeColumns);
Token token;
}
{
(
{ onErrorClause.setType(JsonTableFunction.JsonTableOnErrorType.ERROR); }
|
- token =
- {
- if (!token.image.equalsIgnoreCase("EMPTY")) {
- throw new ParseException(
- "Expected EMPTY or ERROR but found " + token.image);
- }
- onErrorClause.setType(JsonTableFunction.JsonTableOnErrorType.EMPTY);
- }
+ { onErrorClause.setType(JsonTableFunction.JsonTableOnErrorType.EMPTY); }
+ |
+ { onErrorClause.setType(JsonTableFunction.JsonTableOnErrorType.NULL); }
)
{
@@ -9662,6 +9682,44 @@ JsonTableFunction.JsonTableOnErrorClause JsonTableOnErrorClause() : {
}
}
+JsonTableFunction.JsonTableOnEmptyClause JsonTableOnEmptyClause() : {
+ JsonTableFunction.JsonTableOnEmptyClause onEmptyClause =
+ new JsonTableFunction.JsonTableOnEmptyClause();
+ Token token;
+}
+{
+ (
+ { onEmptyClause.setType(JsonTableFunction.JsonTableOnEmptyType.ERROR); }
+ |
+ { onEmptyClause.setType(JsonTableFunction.JsonTableOnEmptyType.EMPTY); }
+ |
+ { onEmptyClause.setType(JsonTableFunction.JsonTableOnEmptyType.NULL); }
+ )
+
+ {
+ if (onEmptyClause.getType() != null) {
+ return onEmptyClause;
+ }
+ }
+}
+
+JsonTableFunction.JsonTableParsingTypeClause JsonTableParsingTypeClause() : {
+ JsonTableFunction.JsonTableParsingTypeClause parsingType = new JsonTableFunction.JsonTableParsingTypeClause();
+}
+{
+
+
+ (
+ { parsingType.setType(JsonTableFunction.JsonTableParsingType.STRICT); }
+ |
+ { parsingType.setType(JsonTableFunction.JsonTableParsingType.LAX); }
+ )
+
+ {
+ return parsingType;
+ }
+}
+
JsonTableFunction JsonTableBody() : {
JsonTableFunction function = new JsonTableFunction();
Expression jsonInput;
@@ -9671,18 +9729,23 @@ JsonTableFunction JsonTableBody() : {
JsonTableFunction.JsonTableColumnsClause columnsClause;
JsonTableFunction.JsonTablePlanClause planClause = null;
JsonTableFunction.JsonTableOnErrorClause onErrorClause = null;
+ JsonTableFunction.JsonTableParsingTypeClause parsingTypeClause = null;
+ JsonTableFunction.JsonTableOnEmptyClause onEmptyClause = null;
}
{
"("
jsonInput = Expression() {
function.setJsonInputExpression(jsonInput);
}
- ","
- jsonPath = Expression() {
- function.setJsonPathExpression(jsonPath);
- function.setParameters(new ExpressionList(jsonInput, jsonPath));
- }
- [ pathName = RelObjectName() { function.setPathName(pathName); } ]
+ [ { function.setFormatJson(true); } ]
+ [
+ ","
+ jsonPath = Expression() {
+ function.setJsonPathExpression(jsonPath);
+ function.setParameters(new ExpressionList(jsonInput, jsonPath));
+ }
+ [ pathName = RelObjectName() { function.setPathName(pathName); } ]
+ ]
[
LOOKAHEAD({ getToken(1).kind == S_IDENTIFIER && getToken(1).image.equalsIgnoreCase("PASSING") })
JsonKeyword("PASSING")
@@ -9692,9 +9755,12 @@ JsonTableFunction JsonTableBody() : {
passingClause = JsonTablePassingClause() { function.addPassingClause(passingClause); }
)*
]
+ [ LOOKAHEAD(3) onErrorClause = JsonTableOnErrorClause(true) { function.setOnErrorClause(onErrorClause); } ]
+ [ parsingTypeClause = JsonTableParsingTypeClause() { function.setParsingTypeClause(parsingTypeClause); } ]
+ [ onEmptyClause = JsonTableOnEmptyClause() { function.setOnEmptyClause(onEmptyClause); } ]
columnsClause = JsonTableColumnsClause() { function.setColumnsClause(columnsClause); }
[ planClause = JsonTablePlanClause() { function.setPlanClause(planClause); } ]
- [ onErrorClause = JsonTableOnErrorClause() { function.setOnErrorClause(onErrorClause); } ]
+ [ onErrorClause = JsonTableOnErrorClause(false) { function.setOnErrorClause(onErrorClause); } ]
")"
{
return function;
@@ -10195,6 +10261,7 @@ ColDataType DataType():
]
[
LOOKAHEAD(2) "(" ( tk= { precision = Integer.valueOf(tk.image); } | tk= { precision = Integer.MAX_VALUE; } )
+ [ | ]
[ "," tk = { scale = Integer.valueOf(tk.image); } ]
")"
]
diff --git a/src/test/java/net/sf/jsqlparser/statement/from/JsonTableTest.java b/src/test/java/net/sf/jsqlparser/statement/from/JsonTableTest.java
new file mode 100644
index 000000000..f5f971557
--- /dev/null
+++ b/src/test/java/net/sf/jsqlparser/statement/from/JsonTableTest.java
@@ -0,0 +1,288 @@
+package net.sf.jsqlparser.statement.from;
+
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Function;
+import net.sf.jsqlparser.expression.JsonTableFunction;
+import net.sf.jsqlparser.expression.json.JsonOnEmptyType;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.Statement;
+import net.sf.jsqlparser.statement.select.FromItem;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.TableFunction;
+import net.sf.jsqlparser.test.TestUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.*;
+
+public class JsonTableTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "SELECT jt.phones FROM j_purchaseorder,\n" +
+ "JSON_TABLE(po_document, '$.ShippingInstructions'\n" +
+ "COLUMNS(phones VARCHAR2(100) FORMAT JSON PATH '$.Phone')) AS jt",
+ "SELECT jt.phones FROM j_purchaseorder,\n" +
+ "JSON_TABLE(po_document, '$.ShippingInstructions'\n" +
+ "COLUMNS(phones FORMAT JSON PATH '$.Phone')) AS jt"
+ })
+ void testObjectOracle(String sqlStr) throws JSQLParserException {
+ TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "JSON_TABLE(document COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document FORMAT JSON COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document, '$.SubPath' COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document NULL ON ERROR COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document ERROR ON ERROR COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document TYPE(LAX) COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document TYPE(STRICT) COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document NULL ON EMPTY COLUMNS( id FOR ORDINALITY))",
+ "JSON_TABLE(document ERROR ON EMPTY COLUMNS( id FOR ORDINALITY))",
+ })
+ void testExpression(String jsonTableStr) throws JSQLParserException {
+ JsonTableFunction table = parseTable(jsonTableStr);
+
+ assertThat(table.getColumnsClause().getColumnDefinitions()).hasSize(1);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest' TRUE ON ERROR TRUE ON EMPTY))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest' FALSE ON ERROR FALSE ON EMPTY))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest' ERROR ON ERROR ERROR ON EMPTY))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest' ERROR ON ERROR))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest' ERROR ON EMPTY))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS ERROR ON EMPTY))",
+ "JSON_TABLE(document COLUMNS( hasValue EXISTS))",
+ })
+ void testExistsColumns(String jsonTableStr) throws JSQLParserException {
+ JsonTableFunction table = parseTable(jsonTableStr);
+
+ assertThat(table.getColumnsClause().getColumnDefinitions()).hasSize(1);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "JSON_TABLE(document COLUMNS( val PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val FORMAT JSON PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val ALLOW SCALARS PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val DISALLOW SCALARS PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val FORMAT JSON DISALLOW SCALARS PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITHOUT WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH ARRAY WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITHOUT ARRAY WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH CONDITIONAL WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH CONDITIONAL ARRAY WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH UNCONDITIONAL WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH UNCONDITIONAL ARRAY WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val WITH UNCONDITIONAL ARRAY WRAPPER PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val PATH '$.pathTest' ERROR ON ERROR))",
+ "JSON_TABLE(document COLUMNS( val PATH '$.pathTest' NULL ON ERROR))",
+ "JSON_TABLE(document COLUMNS( val PATH '$.pathTest' EMPTY ON ERROR))",
+ "JSON_TABLE(document COLUMNS( val PATH '$.pathTest' EMPTY ARRAY ON ERROR))",
+ "JSON_TABLE(document COLUMNS( val PATH '$.pathTest' EMPTY OBJECT ON ERROR))",
+ "JSON_TABLE(document COLUMNS( val CLOB PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val BLOB PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val JSON PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val VECTOR PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val VARCHAR2 PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val VARCHAR2(240) PATH '$.pathTest'))",
+ "JSON_TABLE(document COLUMNS( val VARCHAR2(240) FORMAT JSON PATH '$.pathTest'))",
+
+ // These would require adapting ColDataType in Line 10176
+// "JSON_TABLE(document COLUMNS( val VARCHAR2(500 BYTE) PATH '$.pathTest'))",
+// "JSON_TABLE(document COLUMNS( val VARCHAR2(100 CHAR) PATH '$.pathTest'))",
+// "JSON_TABLE(document COLUMNS( val VARCHAR2(500 BYTE) FORMAT JSON DISALLOW SCALARS WITH UNCONDITIONAL ARRAY WRAPPER PATH '$.pathTest' EMPTY OBJECT ON ERROR))",
+ })
+ void testQueryColumns(String jsonTableStr) throws JSQLParserException {
+ JsonTableFunction table = parseTable(jsonTableStr);
+
+ assertThat(table.getColumnsClause().getColumnDefinitions()).hasSize(1);
+ }
+
+ @Test
+ void testFormatJson() throws JSQLParserException {
+ String expression = "JSON_TABLE(document FORMAT JSON COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getFormatJson()).isTrue();
+ }
+
+ @Test
+ void testPathExpression() throws JSQLParserException {
+ String expression = "JSON_TABLE(document, '$.SubPath' COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getJsonPathExpression().toString()).isEqualTo("'$.SubPath'");
+ }
+
+ @Test
+ void testNullOnError() throws JSQLParserException {
+ String expression = "JSON_TABLE(document NULL ON ERROR COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getOnErrorClause().getType()).isEqualTo(JsonTableFunction.JsonTableOnErrorType.NULL);
+ }
+
+ @Test
+ void testErrorOnError() throws JSQLParserException {
+ String expression = "JSON_TABLE(document ERROR ON ERROR COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getOnErrorClause().getType()).isEqualTo(JsonTableFunction.JsonTableOnErrorType.ERROR);
+ }
+
+ @Test
+ void testNullOnEmpty() throws JSQLParserException {
+ String expression = "JSON_TABLE(document NULL ON EMPTY COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getOnEmptyClause().getType()).isEqualTo(JsonTableFunction.JsonTableOnEmptyType.NULL);
+ }
+
+ @Test
+ void testErrorOnEmpty() throws JSQLParserException {
+ String expression = "JSON_TABLE(document ERROR ON EMPTY COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getOnEmptyClause().getType()).isEqualTo(JsonTableFunction.JsonTableOnEmptyType.ERROR);
+ }
+
+ @Test
+ void testParsingTypeLax() throws JSQLParserException {
+ String expression = "JSON_TABLE(document TYPE(LAX) COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getParsingTypeClause().getType()).isEqualTo(JsonTableFunction.JsonTableParsingType.LAX);
+ }
+
+ @Test
+ void testParsingTypeStrict() throws JSQLParserException {
+ String expression = "JSON_TABLE(document TYPE(STRICT) COLUMNS( id FOR ORDINALITY))";
+ JsonTableFunction table = parseTable(expression);
+
+ assertThat(table.getParsingTypeClause().getType()).isEqualTo(JsonTableFunction.JsonTableParsingType.STRICT);
+ }
+
+// @Test
+// void testColumnTypeExists() throws JSQLParserException {
+// String expression = "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest'))";
+// JsonTableFunction table = parseTable(expression);
+//
+// assertThat(table.getColumnsClause().getColumnDefinitions()).hasSize(1);
+//
+// JsonTableFunction.JsonTableColumnDefinition col = table.getColumnsClause().getColumnDefinitions().get(0);
+// assertThat(col.getType()).isEqualTo(JsonTableColumnType.JSON_EXISTS);
+// }
+
+// @Test
+// void testBuilder() {
+// Column c = new Column("document");
+//
+// JsonTable table = new JsonTable().withExpression(c)
+// .withPathExpression("$.subPath")
+// .withFormatJson(true)
+// .withType(JsonTableType.STRICT)
+// .withOnEmptyType(JsonOnEmptyType.NULL)
+// .withOnErrorType(JsonOnErrorType.ERROR)
+// .withColumn(new JsonTableColumn().withName("id")
+// .withType(JsonTableColumnType.ORDINALITY));
+//
+// assertThat(table.toString()).isEqualTo(
+// "JSON_TABLE(document FORMAT JSON, '$.subPath' ERROR ON ERROR TYPE(STRICT) NULL ON EMPTY COLUMNS(id FOR ORDINALITY))");
+// }
+
+// @Test
+// void testValidSetters() {
+// JsonTableFunction table = new JsonTableFunction();
+//
+// JsonTableFunction.JsonTableOnEmptyClause onEmptyClause = new JsonTableFunction.JsonTableOnEmptyClause();
+// JsonTableFunction.JsonTableOnErrorClause onErrorClause = new JsonTableFunction.JsonTableOnErrorClause(true);
+//
+// assertThatNoException().isThrownBy(() -> {
+// table.setOnEmptyClause(null);
+// onEmptyClause.setType(JsonTableFunction.JsonTableOnEmptyType.NULL);
+// onEmptyClause.setType(JsonTableFunction.JsonTableOnEmptyType.ERROR);
+//
+// onErrorClause.setOnErrorType(null);
+// onErrorClause.setOnErrorType(JsonTableFunction.JsonTableOnErrorType);
+// onErrorClause.setOnErrorType(JsonOnErrorType.ERROR);
+//
+// table.setType(null);
+// table.setType(JsonTableType.LAX);
+// table.setType(JsonTableType.STRICT);
+// });
+// }
+
+// @Test
+// void testInvalidSetters() {
+// JsonTableFunction table = new JsonTable();
+//
+// assertThatThrownBy(() -> table.setOnEmptyType(JsonOnEmptyType.EMPTY))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnEmptyType(JsonOnEmptyType.EMPTY_ARRAY))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnEmptyType(JsonOnEmptyType.EMPTY_OBJECT))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnEmptyType(JsonOnEmptyType.FALSE))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnEmptyType(JsonOnEmptyType.TRUE))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnEmptyType(JsonOnEmptyType.DEFAULT))
+// .isInstanceOf(IllegalArgumentException.class);
+//
+// assertThatThrownBy(() -> table.setOnErrorType(JsonOnErrorType.EMPTY))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnErrorType(JsonOnErrorType.EMPTY_ARRAY))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnErrorType(JsonOnErrorType.EMPTY_OBJECT))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnErrorType(JsonOnErrorType.FALSE))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnErrorType(JsonOnErrorType.TRUE))
+// .isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> table.setOnErrorType(JsonOnErrorType.DEFAULT))
+// .isInstanceOf(IllegalArgumentException.class);
+//
+// JsonTableColumn column = new JsonTableColumn();
+//
+// assertThatThrownBy(() -> {
+// column.setType(JsonTableColumnType.JSON_EXISTS);
+// column.setFormatJson(true);
+// }).isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> {
+// column.setType(JsonTableColumnType.JSON_VALUE);
+// column.setFormatJson(true);
+// }).isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> {
+// column.setType(JsonTableColumnType.ORDINALITY);
+// column.setFormatJson(true);
+// }).isInstanceOf(IllegalArgumentException.class);
+// assertThatThrownBy(() -> {
+// column.setType(JsonTableColumnType.JSON_NESTED_PATH);
+// column.setFormatJson(true);
+// }).isInstanceOf(IllegalArgumentException.class);
+// }
+
+ private JsonTableFunction parseTable(String jsonTableStr) throws JSQLParserException {
+ String sql = "SELECT * FROM " + jsonTableStr;
+ Statement stmt = CCJSqlParserUtil.parse(sql);
+
+ TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true);
+
+ FromItem fromItem = ((PlainSelect) stmt).getFromItem();
+ assertThat(fromItem).isInstanceOf(TableFunction.class);
+ Function function = ((TableFunction) fromItem).getFunction();
+ assertThat(function).isInstanceOf(JsonTableFunction.class);
+
+ return (JsonTableFunction) function;
+ }
+
+
+}
diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java
index 1180417fb..44a27624c 100644
--- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java
+++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java
@@ -759,4 +759,16 @@ void testNestedTablesInJsonObject() throws JSQLParserException {
assertThat(TablesNamesFinder.findTables(sqlStr)).containsExactlyInAnyOrder("table1",
"table2", "table3");
}
+
+ @Test
+ void testJsonTable() throws JSQLParserException {
+ String sqlStr = "SELECT * FROM JSON_TABLE(" +
+ "(SELECT json_column FROM table_with_json), '$.jsonPath' COLUMNS( id FOR ORDINALITY ))";
+
+ Set tables = TablesNamesFinder.findTables(sqlStr);
+
+ assertThat(tables).containsExactly("table_with_json");
+
+ }
+
}