From 0b8575350eafe7514d0ad203d6dabd8f6599a0d9 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Mon, 13 Oct 2025 18:40:16 +0200 Subject: [PATCH 1/3] [feat] Initial support for JSON_TABLE --- .../expression/ExpressionVisitor.java | 6 + .../expression/ExpressionVisitorAdapter.java | 6 + .../expression/JsonOnEmptyType.java | 53 ++++++ .../expression/JsonOnErrorType.java | 53 ++++++ .../sf/jsqlparser/expression/JsonTable.java | 154 ++++++++++++++++++ .../expression/JsonTableColumn.java | 48 ++++++ .../expression/JsonTableColumnType.java | 9 + .../jsqlparser/expression/JsonTableType.java | 37 +++++ .../sf/jsqlparser/util/TablesNamesFinder.java | 6 + .../util/deparser/ExpressionDeParser.java | 66 +------- .../validator/ExpressionValidator.java | 67 +------- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 56 ++++++- .../jsqlparser/expression/JsonTableTest.java | 37 +++++ 13 files changed, 478 insertions(+), 120 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonTable.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonTableType.java create mode 100644 src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java index 8b5ade13d..94ded2955 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java @@ -652,6 +652,12 @@ default void visit(JsonFunction jsonFunction) { this.visit(jsonFunction, null); } + T visit(JsonTable jsonTable, S context); + + default void visit(JsonTable jsonTable) { + this.visit(jsonTable, null); + } + T visit(ConnectByRootOperator connectByRootOperator, S context); default void visit(ConnectByRootOperator connectByRootOperator) { diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index 96d80d514..28fe1f8a8 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -728,6 +728,12 @@ public T visit(JsonFunction jsonFunction, S context) { return visitExpressions(jsonFunction, context, subExpressions); } + @Override + public T visit(JsonTable jsonTable, S context) { + // TODO: Implement + return null; + } + @Override public T visit(ConnectByRootOperator connectByRootOperator, S context) { return connectByRootOperator.getColumn().accept(this, context); diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java b/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java new file mode 100644 index 000000000..26296d44e --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.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% + */ +/* + * 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; + +/** + * + */ +public enum JsonOnEmptyType { + ERROR("ERROR"), + NULL("NULL"), + EMPTY("EMPTY"), + EMPTY_ARRAY("EMPTY ARRAY"), + EMPTY_OBJECT("EMPTY OBJECT"), + TRUE("TRUE"), + FALSE("FALSE"); + + 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/JsonOnErrorType.java b/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java new file mode 100644 index 000000000..0a990e142 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.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% + */ +/* + * 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; + +/** + * + */ +public enum JsonOnErrorType { + ERROR("ERROR"), + NULL("NULL"), + EMPTY("EMPTY"), + EMPTY_ARRAY("EMPTY ARRAY"), + EMPTY_OBJECT("EMPTY OBJECT"), + TRUE("TRUE"), + FALSE("FALSE"); + + 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/JsonTable.java b/src/main/java/net/sf/jsqlparser/expression/JsonTable.java new file mode 100644 index 000000000..35dcdbbaf --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonTable.java @@ -0,0 +1,154 @@ +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +import java.util.ArrayList; +import java.util.List; + +public class JsonTable extends ASTNodeAccessImpl implements Expression { + + 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); + } + + 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("))"); + return builder; + } + + @Override + public T accept(ExpressionVisitor expressionVisitor, S context) { + return expressionVisitor.visit(this, context); + } + + 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) { + this.onErrorType = onErrorType; + } + + 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) { + this.onEmptyType = onEmptyType; + } + + public JsonOnEmptyType getOnEmptyType() { + return onEmptyType; + } + + public JsonTable withOnEmptyType(JsonOnEmptyType onEmptyType) { + setOnEmptyType(onEmptyType); + return this; + } + + public void addColumn(JsonTableColumn column) { + this.jsonColumns.add(column); + } + + public List getColumns() { + return jsonColumns; + } + +} diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java new file mode 100644 index 000000000..052602f92 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java @@ -0,0 +1,48 @@ +package net.sf.jsqlparser.expression; + +public class JsonTableColumn { + + private String columnName; + private JsonTableColumnType type; + + private String jsonPath; + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public String getColumnName() { + return columnName; + } + + public void setJsonPath(String jsonPath) { + this.jsonPath = jsonPath; + } + + public String getJsonPath() { + return jsonPath; + } + + public void setType(JsonTableColumnType type) { + this.type = type; + } + + public JsonTableColumnType getType() { + return type; + } + + public StringBuilder append(StringBuilder builder) { + + builder.append(columnName); + + switch (type) { + case ORDINALITY: + builder.append(" FOR ORDINALITY"); + + break; + } + + return builder; + } + +} diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java new file mode 100644 index 000000000..d1c63ff23 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java @@ -0,0 +1,9 @@ +package net.sf.jsqlparser.expression; + +public enum JsonTableColumnType { + JSON_EXISTS, + JSON_QUERY, + JSON_VALUE, + JSON_NESTED_PATH, + ORDINALITY +} diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableType.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableType.java new file mode 100644 index 000000000..5275f309c --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/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.expression; + +/** + * + */ +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/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 9c33c4f27..b5d568eea 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -1721,6 +1721,12 @@ public Void visit(JsonFunction expression, S context) { return null; } + @Override + public Void visit(JsonTable jsonTable, S context) { + // TODO: Implement + return null; + } + @Override public Void visit(ConnectByRootOperator connectByRootOperator, S context) { connectByRootOperator.getColumn().accept(this, context); 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 27176d625..5176646b6 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -9,65 +9,7 @@ */ package net.sf.jsqlparser.util.deparser; -import net.sf.jsqlparser.expression.AllValue; -import net.sf.jsqlparser.expression.AnalyticExpression; -import net.sf.jsqlparser.expression.AnalyticType; -import net.sf.jsqlparser.expression.AnyComparisonExpression; -import net.sf.jsqlparser.expression.ArrayConstructor; -import net.sf.jsqlparser.expression.ArrayExpression; -import net.sf.jsqlparser.expression.BinaryExpression; -import net.sf.jsqlparser.expression.BooleanValue; -import net.sf.jsqlparser.expression.CaseExpression; -import net.sf.jsqlparser.expression.CastExpression; -import net.sf.jsqlparser.expression.CollateExpression; -import net.sf.jsqlparser.expression.ConnectByRootOperator; -import net.sf.jsqlparser.expression.ConnectByPriorOperator; -import net.sf.jsqlparser.expression.DateTimeLiteralExpression; -import net.sf.jsqlparser.expression.DateValue; -import net.sf.jsqlparser.expression.DoubleValue; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.ExpressionVisitor; -import net.sf.jsqlparser.expression.ExtractExpression; -import net.sf.jsqlparser.expression.Function; -import net.sf.jsqlparser.expression.HexValue; -import net.sf.jsqlparser.expression.HighExpression; -import net.sf.jsqlparser.expression.IntervalExpression; -import net.sf.jsqlparser.expression.Inverse; -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.LambdaExpression; -import net.sf.jsqlparser.expression.LongValue; -import net.sf.jsqlparser.expression.LowExpression; -import net.sf.jsqlparser.expression.MySQLGroupConcat; -import net.sf.jsqlparser.expression.NextValExpression; -import net.sf.jsqlparser.expression.NotExpression; -import net.sf.jsqlparser.expression.NullValue; -import net.sf.jsqlparser.expression.NumericBind; -import net.sf.jsqlparser.expression.OracleHierarchicalExpression; -import net.sf.jsqlparser.expression.OracleHint; -import net.sf.jsqlparser.expression.OracleNamedFunctionParameter; -import net.sf.jsqlparser.expression.OverlapsCondition; -import net.sf.jsqlparser.expression.RangeExpression; -import net.sf.jsqlparser.expression.RowConstructor; -import net.sf.jsqlparser.expression.RowGetExpression; -import net.sf.jsqlparser.expression.SignedExpression; -import net.sf.jsqlparser.expression.StringValue; -import net.sf.jsqlparser.expression.StructType; -import net.sf.jsqlparser.expression.TimeKeyExpression; -import net.sf.jsqlparser.expression.TimeValue; -import net.sf.jsqlparser.expression.TimestampValue; -import net.sf.jsqlparser.expression.TimezoneExpression; -import net.sf.jsqlparser.expression.TranscodingFunction; -import net.sf.jsqlparser.expression.TrimFunction; -import net.sf.jsqlparser.expression.UserVariable; -import net.sf.jsqlparser.expression.VariableAssignment; -import net.sf.jsqlparser.expression.WhenClause; -import net.sf.jsqlparser.expression.WindowElement; -import net.sf.jsqlparser.expression.XMLSerializeExpr; +import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -1628,6 +1570,12 @@ public StringBuilder visit(JsonFunction expression, S context) { return builder; } + @Override + public StringBuilder visit(JsonTable expression, S context) { + expression.append(builder); + return builder; + } + @Override public StringBuilder visit(ConnectByRootOperator connectByRootOperator, S context) { builder.append("CONNECT_BY_ROOT "); 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 a4b27d765..b6b9cb4f8 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 @@ -9,66 +9,7 @@ */ package net.sf.jsqlparser.util.validation.validator; -import net.sf.jsqlparser.expression.AllValue; -import net.sf.jsqlparser.expression.AnalyticExpression; -import net.sf.jsqlparser.expression.AnyComparisonExpression; -import net.sf.jsqlparser.expression.ArrayConstructor; -import net.sf.jsqlparser.expression.ArrayExpression; -import net.sf.jsqlparser.expression.BinaryExpression; -import net.sf.jsqlparser.expression.BooleanValue; -import net.sf.jsqlparser.expression.CaseExpression; -import net.sf.jsqlparser.expression.CastExpression; -import net.sf.jsqlparser.expression.CollateExpression; -import net.sf.jsqlparser.expression.ConnectByRootOperator; -import net.sf.jsqlparser.expression.ConnectByPriorOperator; -import net.sf.jsqlparser.expression.DateTimeLiteralExpression; -import net.sf.jsqlparser.expression.DateValue; -import net.sf.jsqlparser.expression.DoubleValue; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.ExpressionVisitor; -import net.sf.jsqlparser.expression.ExtractExpression; -import net.sf.jsqlparser.expression.Function; -import net.sf.jsqlparser.expression.HexValue; -import net.sf.jsqlparser.expression.HighExpression; -import net.sf.jsqlparser.expression.IntervalExpression; -import net.sf.jsqlparser.expression.Inverse; -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.LambdaExpression; -import net.sf.jsqlparser.expression.LongValue; -import net.sf.jsqlparser.expression.LowExpression; -import net.sf.jsqlparser.expression.MySQLGroupConcat; -import net.sf.jsqlparser.expression.NextValExpression; -import net.sf.jsqlparser.expression.NotExpression; -import net.sf.jsqlparser.expression.NullValue; -import net.sf.jsqlparser.expression.NumericBind; -import net.sf.jsqlparser.expression.OracleHierarchicalExpression; -import net.sf.jsqlparser.expression.OracleHint; -import net.sf.jsqlparser.expression.OracleNamedFunctionParameter; -import net.sf.jsqlparser.expression.OverlapsCondition; -import net.sf.jsqlparser.expression.RangeExpression; -import net.sf.jsqlparser.expression.RowConstructor; -import net.sf.jsqlparser.expression.RowGetExpression; -import net.sf.jsqlparser.expression.SignedExpression; -import net.sf.jsqlparser.expression.StringValue; -import net.sf.jsqlparser.expression.StructType; -import net.sf.jsqlparser.expression.TimeKeyExpression; -import net.sf.jsqlparser.expression.TimeValue; -import net.sf.jsqlparser.expression.TimestampValue; -import net.sf.jsqlparser.expression.TimezoneExpression; -import net.sf.jsqlparser.expression.TranscodingFunction; -import net.sf.jsqlparser.expression.TrimFunction; -import net.sf.jsqlparser.expression.UserVariable; -import net.sf.jsqlparser.expression.VariableAssignment; -import net.sf.jsqlparser.expression.WhenClause; -import net.sf.jsqlparser.expression.WindowElement; -import net.sf.jsqlparser.expression.WindowOffset; -import net.sf.jsqlparser.expression.WindowRange; -import net.sf.jsqlparser.expression.XMLSerializeExpr; +import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -1033,6 +974,12 @@ public Void visit(JsonFunction expression, S context) { return null; } + @Override + public Void visit(JsonTable jsonTable, S context) { + // TODO: Implement + return null; + } + @Override public Void visit(ConnectByRootOperator connectByRootOperator, S context) { connectByRootOperator.getColumn().accept(this, context); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 4305b09e1..e976dd127 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -351,6 +351,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -442,6 +443,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -450,6 +452,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -3268,7 +3271,7 @@ String RelObjectNameWithoutValue() : { Token tk = null; } { ( tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="AGGREGATE" | tk="ALGORITHM" | tk="ALIGN" | tk="ALTER" | tk="ALWAYS" | tk="ANALYZE" | tk="APPEND_ONLY" | tk="APPLY" | tk="APPROXIMATE" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="ASYMMETRIC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="AUTO_INCREMENT" | tk="AZURE" | tk="BASE64" | tk="BEFORE" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOBSTORAGE" | tk="BLOCK" | tk="BOOLEAN" | tk="BRANCH" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CERTIFICATE" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="CLOUD" | tk="COALESCE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMENTS" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="CORRESPONDING" | tk="COSTS" | tk="COUNT" | tk="CREATED" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DATETIME" | tk="DBA_RECYCLEBIN" | tk="DDL" | tk="DECLARE" | tk="DEFAULTS" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DELIMIT" | tk="DELIMITER" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCARD" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DRIVER" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="ENABLE" | tk="ENCODING" | tk="ENCRYPTION" | tk="END" | tk="ENFORCED" | tk="ENGINE" | tk="ERROR" | tk="ESCAPE" | tk="EXA" | tk="EXCHANGE" | tk="EXCLUDE" | tk="EXCLUDING" | tk="EXCLUSIVE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXPORT" | tk="EXTENDED" | tk="EXTRACT" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GROUP_CONCAT" | tk="GUARD" | tk="HASH" | tk="HIGH" | tk="HIGH_PRIORITY" | tk="HISTORY" | tk="HOPPING" | tk="IDENTIFIED" | tk="IDENTITY" | tk="INCLUDE" | tk="INCLUDE_NULL_VALUES" | tk="INCLUDING" | tk="INCREMENT" | tk="INDEX" | tk="INFORMATION" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="INVERSE" | tk="INVISIBLE" | tk="ISNULL" | tk="JDBC" | tk="JSON" | tk="JSON_ARRAY" | tk="JSON_ARRAYAGG" | tk="JSON_OBJECT" | tk="JSON_OBJECTAGG" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="KEY_BLOCK_SIZE" | tk="KILL" | tk="LAST" | tk="LEADING" | tk="LESS" | tk="LINK" | tk="LOCAL" | tk="LOCK" | tk="LOCKED" | tk="LOG" | tk="LONGTEXT" | tk="LOOP" | tk="LOW" | tk="LOW_PRIORITY" | tk="LTRIM" | tk="MATCH" | tk="MATCHED" | tk="MATCH_ALL" | tk="MATCH_ANY" | tk="MATCH_PHRASE" | tk="MATCH_PHRASE_PREFIX" | tk="MATCH_REGEXP" | tk="MATERIALIZED" | tk="MAX" | tk="MAXVALUE" | tk="MEDIUMTEXT" | tk="MEMBER" | tk="MERGE" | tk="MIN" | tk="MINVALUE" | tk="MODE" | tk="MODIFY" | tk="MOVEMENT" | tk="NAME" | tk="NAMES" | tk="NEVER" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NONE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="ORA" | tk="ORDINALITY" | tk="OVER" | tk="OVERFLOW" | tk="OVERLAPS" | tk="OVERRIDING" | tk="OVERWRITE" | tk="PADDING" | tk="PARALLEL" | tk="PARENT" | tk="PARSER" | tk="PARTITION" | tk="PARTITIONING" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PLAN" | tk="PLUS" | tk="PRECEDING" | tk="PRIMARY" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="REBUILD" | tk="RECURSIVE" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGEXP" | tk="REGEXP_LIKE" | tk="REGISTER" | tk="REJECT" | tk="REMOTE" | tk="REMOVE" | tk="RENAME" | tk="REORGANIZE" | tk="REPAIR" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESPECT" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="RTRIM" | tk="SAFE_CAST" | tk="SAFE_CONVERT" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="SPATIAL" | tk="STORED" | tk="STREAM" | tk="STRICT" | tk="STRING" | tk="STRUCT" | tk="SUMMARIZE" | tk="SUSPEND" | tk="SWITCH" | tk="SYMMETRIC" | tk="SYNONYM" | tk="SYSTEM" | tk="SYSTEM_TIME" | tk="SYSTEM_TIMESTAMP" | tk="SYSTEM_VERSION" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="TEXT" | tk="THAN" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TIMEZONE" | tk="TINYTEXT" | tk="TO" | tk="TRIGGER" | tk="TRUNCATE" | tk="TRY_CAST" | tk="TRY_CONVERT" | tk="TUMBLING" | tk="TYPE" | tk="UNLIMITED" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VALIDATION" | tk="VERBOSE" | tk="VERSION" | tk="VIEW" | tk="VISIBLE" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WITHOUT_ARRAY_WRAPPER" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) + | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="AGGREGATE" | tk="ALGORITHM" | tk="ALIGN" | tk="ALTER" | tk="ALWAYS" | tk="ANALYZE" | tk="APPEND_ONLY" | tk="APPLY" | tk="APPROXIMATE" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="ASYMMETRIC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="AUTO_INCREMENT" | tk="AZURE" | tk="BASE64" | tk="BEFORE" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOBSTORAGE" | tk="BLOCK" | tk="BOOLEAN" | tk="BRANCH" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CERTIFICATE" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="CLOUD" | tk="COALESCE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMENTS" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="CORRESPONDING" | tk="COSTS" | tk="COUNT" | tk="CREATED" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DATETIME" | tk="DBA_RECYCLEBIN" | tk="DDL" | tk="DECLARE" | tk="DEFAULTS" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DELIMIT" | tk="DELIMITER" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCARD" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DRIVER" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="EMPTY" | tk="ENABLE" | tk="ENCODING" | tk="ENCRYPTION" | tk="END" | tk="ENFORCED" | tk="ENGINE" | tk="ERROR" | tk="ESCAPE" | tk="EXA" | tk="EXCHANGE" | tk="EXCLUDE" | tk="EXCLUDING" | tk="EXCLUSIVE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXPORT" | tk="EXTENDED" | tk="EXTRACT" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GROUP_CONCAT" | tk="GUARD" | tk="HASH" | tk="HIGH" | tk="HIGH_PRIORITY" | tk="HISTORY" | tk="HOPPING" | tk="IDENTIFIED" | tk="IDENTITY" | tk="INCLUDE" | tk="INCLUDE_NULL_VALUES" | tk="INCLUDING" | tk="INCREMENT" | tk="INDEX" | tk="INFORMATION" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="INVERSE" | tk="INVISIBLE" | tk="ISNULL" | tk="JDBC" | tk="JSON" | tk="JSON_ARRAY" | tk="JSON_ARRAYAGG" | tk="JSON_OBJECT" | tk="JSON_OBJECTAGG" | tk="JSON_TABLE" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="KEY_BLOCK_SIZE" | tk="KILL" | tk="LAST" | tk="LAX" | tk="LEADING" | tk="LESS" | tk="LINK" | tk="LOCAL" | tk="LOCK" | tk="LOCKED" | tk="LOG" | tk="LONGTEXT" | tk="LOOP" | tk="LOW" | tk="LOW_PRIORITY" | tk="LTRIM" | tk="MATCH" | tk="MATCHED" | tk="MATCH_ALL" | tk="MATCH_ANY" | tk="MATCH_PHRASE" | tk="MATCH_PHRASE_PREFIX" | tk="MATCH_REGEXP" | tk="MATERIALIZED" | tk="MAX" | tk="MAXVALUE" | tk="MEDIUMTEXT" | tk="MEMBER" | tk="MERGE" | tk="MIN" | tk="MINVALUE" | tk="MODE" | tk="MODIFY" | tk="MOVEMENT" | tk="NAME" | tk="NAMES" | tk="NEVER" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NONE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="ORA" | tk="ORDINALITY" | tk="OVER" | tk="OVERFLOW" | tk="OVERLAPS" | tk="OVERRIDING" | tk="OVERWRITE" | tk="PADDING" | tk="PARALLEL" | tk="PARENT" | tk="PARSER" | tk="PARTITION" | tk="PARTITIONING" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PLAN" | tk="PLUS" | tk="PRECEDING" | tk="PRIMARY" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="REBUILD" | tk="RECURSIVE" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGEXP" | tk="REGEXP_LIKE" | tk="REGISTER" | tk="REJECT" | tk="REMOTE" | tk="REMOVE" | tk="RENAME" | tk="REORGANIZE" | tk="REPAIR" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESPECT" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="RTRIM" | tk="SAFE_CAST" | tk="SAFE_CONVERT" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="SPATIAL" | tk="STORED" | tk="STREAM" | tk="STRICT" | tk="STRING" | tk="STRUCT" | tk="SUMMARIZE" | tk="SUSPEND" | tk="SWITCH" | tk="SYMMETRIC" | tk="SYNONYM" | tk="SYSTEM" | tk="SYSTEM_TIME" | tk="SYSTEM_TIMESTAMP" | tk="SYSTEM_VERSION" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="TEXT" | tk="THAN" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TIMEZONE" | tk="TINYTEXT" | tk="TO" | tk="TRIGGER" | tk="TRUNCATE" | tk="TRY_CAST" | tk="TRY_CONVERT" | tk="TUMBLING" | tk="TYPE" | tk="UNLIMITED" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VALIDATION" | tk="VERBOSE" | tk="VERSION" | tk="VIEW" | tk="VISIBLE" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WITHOUT_ARRAY_WRAPPER" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) { return tk.image; } } @@ -6557,6 +6560,8 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(3, { !interrupted}) retval = JsonAggregateFunction() + | LOOKAHEAD(2, { !interrupted}) retval = JsonTable() + | LOOKAHEAD(3, { !interrupted}) retval = FullTextSearch() | LOOKAHEAD(2, {!interrupted}) retval=CastExpression() @@ -7238,6 +7243,55 @@ JsonAggregateFunction JsonAggregateFunction() : { } } +JsonTable JsonTable() : { + JsonTable result; + + Expression expression; + Token pathExpression; + JsonTableColumn column; +} +{ + { result = new JsonTable(); } + + expression = Expression() { result.setExpression(expression); } + [ { result.setFormatJson( true ); } ] + [ pathExpression = { result.setPathExpression(pathExpression.image); } ] + [ LOOKAHEAD(3) ( { result.setOnErrorType(JsonOnErrorType.ERROR); } | { result.setOnErrorType(JsonOnErrorType.NULL); } ) ] + [ { result.setType(JsonTableType.STRICT ); } | { result.setType(JsonTableType.LAX ); } ] + [ { result.setOnEmptyType(JsonOnEmptyType.ERROR); } | { result.setOnEmptyType(JsonOnEmptyType.NULL); } ] + + + + column = JsonTableColumn() { result.addColumn(column); } + ( + + column = JsonTableColumn() { result.addColumn(column); } + )* + + + { + return result; + } +} + +JsonTableColumn JsonTableColumn() : { + JsonTableColumn column = new JsonTableColumn(); + + Token columnName; + Token jsonPath; +} +{ + ( + (columnName = | columnName=) { column.setColumnName(columnName.image); } + ( { column.setType(JsonTableColumnType.ORDINALITY); } ) + | + ( jsonPath = { column.setJsonPath(jsonPath.image); } ) + ) + { + return column; + } +} + IntervalExpression IntervalExpression() : { IntervalExpression interval = new IntervalExpression(); Token token = null; diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java new file mode 100644 index 000000000..7788bdf54 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java @@ -0,0 +1,37 @@ +package net.sf.jsqlparser.expression; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.test.TestUtils; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +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))" + }) + void testExpression(String expressionStr) throws JSQLParserException { + JsonTable table = (JsonTable) CCJSqlParserUtil.parseExpression(expressionStr); + + assertThat(table.getColumns()).hasSize(1); + + } + +} From 8773eea1f3477e3a04f14c60afcda9d2340dd181 Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Wed, 15 Oct 2025 21:20:15 +0200 Subject: [PATCH 2/3] [feat] Additional support for JSON_TABLE --- .../expression/JsonOnEmptyType.java | 9 +- .../expression/JsonOnErrorType.java | 9 +- .../expression/JsonQueryWrapperType.java | 74 ++++++ .../expression/JsonReturnClause.java | 71 +++++ .../jsqlparser/expression/JsonReturnType.java | 73 +++++ .../sf/jsqlparser/expression/JsonTable.java | 62 ++++- .../expression/JsonTableColumn.java | 251 +++++++++++++++++- .../expression/JsonTableColumnType.java | 6 +- .../statement/select/AbstractFromitem.java | 53 ++++ .../statement/select/FromItemVisitor.java | 7 + .../select/FromItemVisitorAdapter.java | 7 + .../sf/jsqlparser/util/TablesNamesFinder.java | 6 + .../util/deparser/SelectDeParser.java | 14 +- .../validation/validator/SelectValidator.java | 8 + .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 109 +++++++- .../jsqlparser/expression/JsonTableTest.java | 247 ++++++++++++++++- 16 files changed, 947 insertions(+), 59 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonReturnClause.java create mode 100644 src/main/java/net/sf/jsqlparser/expression/JsonReturnType.java create mode 100644 src/main/java/net/sf/jsqlparser/statement/select/AbstractFromitem.java diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java b/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java index 26296d44e..bdcf9aafa 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java @@ -29,13 +29,8 @@ * */ public enum JsonOnEmptyType { - ERROR("ERROR"), - NULL("NULL"), - EMPTY("EMPTY"), - EMPTY_ARRAY("EMPTY ARRAY"), - EMPTY_OBJECT("EMPTY OBJECT"), - TRUE("TRUE"), - FALSE("FALSE"); + ERROR("ERROR"), NULL("NULL"), EMPTY("EMPTY"), EMPTY_ARRAY("EMPTY ARRAY"), EMPTY_OBJECT( + "EMPTY OBJECT"), TRUE("TRUE"), FALSE("FALSE"), DEFAULT("DEFAULT"); private final String value; diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java b/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java index 0a990e142..3661ef7fe 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java @@ -29,13 +29,8 @@ * */ public enum JsonOnErrorType { - ERROR("ERROR"), - NULL("NULL"), - EMPTY("EMPTY"), - EMPTY_ARRAY("EMPTY ARRAY"), - EMPTY_OBJECT("EMPTY OBJECT"), - TRUE("TRUE"), - FALSE("FALSE"); + ERROR("ERROR"), NULL("NULL"), EMPTY("EMPTY"), EMPTY_ARRAY("EMPTY ARRAY"), EMPTY_OBJECT( + "EMPTY OBJECT"), TRUE("TRUE"), FALSE("FALSE"), DEFAULT("DEFAULT"); private final String value; diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java b/src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java new file mode 100644 index 000000000..281f8b26d --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java @@ -0,0 +1,74 @@ +/*- + * #%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; + +/** + * + */ +public enum JsonQueryWrapperType { + 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"); + + 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/JsonReturnClause.java b/src/main/java/net/sf/jsqlparser/expression/JsonReturnClause.java new file mode 100644 index 000000000..cb00473d5 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/JsonReturnClause.java @@ -0,0 +1,71 @@ +package net.sf.jsqlparser.expression; + +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/JsonReturnType.java b/src/main/java/net/sf/jsqlparser/expression/JsonReturnType.java new file mode 100644 index 000000000..a131f518f --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/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; + +/** + * + */ +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/expression/JsonTable.java b/src/main/java/net/sf/jsqlparser/expression/JsonTable.java index 35dcdbbaf..ea38c8af0 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTable.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonTable.java @@ -1,11 +1,14 @@ package net.sf.jsqlparser.expression; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; +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 ASTNodeAccessImpl implements Expression { +public class JsonTable extends AbstractFromitem implements FromItem { private Expression expression; private boolean isFormatJson = false; @@ -18,7 +21,6 @@ public class JsonTable extends ASTNodeAccessImpl implements Expression { private List jsonColumns = new ArrayList<>(); - public StringBuilder append(StringBuilder builder) { builder.append("JSON_TABLE("); builder.append(expression.toString()); @@ -28,8 +30,9 @@ public StringBuilder append(StringBuilder builder) { } if (pathExpression != null) { - builder.append(", "); + builder.append(", '"); builder.append(pathExpression); + builder.append("'"); } if (onErrorType != null) { @@ -57,12 +60,10 @@ public StringBuilder append(StringBuilder builder) { } builder.append("))"); - return builder; - } - @Override - public T accept(ExpressionVisitor expressionVisitor, S context) { - return expressionVisitor.visit(this, context); + super.appendTo(builder, getAlias(), getSampleClause(), getPivot(), getUnPivot()); + + return builder; } public void setExpression(Expression expression) { @@ -105,9 +106,25 @@ public JsonTable withFormatJson(boolean isFormatJson) { } 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; } @@ -131,9 +148,24 @@ public JsonTable withType(JsonTableType type) { } 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; } @@ -147,8 +179,22 @@ 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/expression/JsonTableColumn.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java index 052602f92..ace93224c 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java @@ -1,18 +1,34 @@ package net.sf.jsqlparser.expression; -public class JsonTableColumn { +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; - private String columnName; +public class JsonTableColumn extends ASTNodeAccessImpl { + + private String name; private JsonTableColumnType type; + private JsonReturnClause returnClause; + + private boolean isFormatJson = false; private String jsonPath; - public void setColumnName(String columnName) { - this.columnName = columnName; + // 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 String getColumnName() { - return columnName; + public JsonTableColumn withName(String columnName) { + setName(columnName); + return this; } public void setJsonPath(String jsonPath) { @@ -23,26 +39,245 @@ 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(columnName); + 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/expression/JsonTableColumnType.java b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java index d1c63ff23..fddac24fd 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java +++ b/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java @@ -1,9 +1,5 @@ package net.sf.jsqlparser.expression; public enum JsonTableColumnType { - JSON_EXISTS, - JSON_QUERY, - JSON_VALUE, - JSON_NESTED_PATH, - ORDINALITY + JSON_EXISTS, JSON_QUERY, JSON_VALUE, JSON_NESTED_PATH, ORDINALITY } 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..e3d9f7428 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.statement.select; +import net.sf.jsqlparser.expression.JsonTable; import net.sf.jsqlparser.schema.Table; 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..ecccda3af 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java @@ -12,6 +12,7 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; +import net.sf.jsqlparser.expression.JsonTable; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.imprt.Import; @@ -148,4 +149,10 @@ 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) { + // TODO: Implement + return null; + } } diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index b5d568eea..d87c01145 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -335,6 +335,12 @@ public void visit(PlainSelect plainSelect) { SelectVisitor.super.visit(plainSelect); } + @Override + public void visit(JsonTable jsonTable) { + FromItemVisitor.super.visit(jsonTable); + // TODO: Implement + } + /** * Override to adapt the tableName generation (e.g. with / without schema). * 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 e36e1038a..43406c5d9 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -9,13 +9,7 @@ */ package net.sf.jsqlparser.util.deparser; -import net.sf.jsqlparser.expression.Alias; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.ExpressionVisitor; -import net.sf.jsqlparser.expression.MySQLIndexHint; -import net.sf.jsqlparser.expression.OracleHint; -import net.sf.jsqlparser.expression.SQLServerHints; -import net.sf.jsqlparser.expression.WindowDefinition; +import net.sf.jsqlparser.expression.*; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.imprt.Import; @@ -902,6 +896,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 bacd4e1af..cd03a6c68 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.expression.JsonTable; import net.sf.jsqlparser.expression.MySQLIndexHint; import net.sf.jsqlparser.expression.SQLServerHints; import net.sf.jsqlparser.parser.feature.Feature; @@ -421,6 +422,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); } @@ -437,4 +443,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 e976dd127..739b21ebc 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -246,6 +246,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -271,6 +272,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -294,6 +296,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -304,6 +307,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -335,6 +339,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -507,6 +512,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -590,6 +596,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -653,6 +660,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -675,6 +683,8 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| +| | | | @@ -692,6 +702,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */ | | | +| | | | @@ -746,8 +757,9 @@ TOKEN : /* Data Types */ ) > | <#TYPE_BIT: "BISTRING"> - | <#TYPE_BLOB: "BLOB" | "BYTEA" | | "VARBINARY" | > + | <#TYPE_BLOB: | "BYTEA" | | "VARBINARY" | > | <#TYPE_BOOLEAN: | "BOOL" > + | <#TYPE_CLOB: > | <#TYPE_ENUM: "ENUM" > | <#TYPE_MAP: "MAP" > | <#TYPE_DECIMAL: "DECIMAL" | "NUMBER" | "NUMERIC" > @@ -764,6 +776,7 @@ TOKEN : /* Data Types */ | <#TYPE_REAL: "REAL" | "FLOAT4" | "FLOAT"> | <#TYPE_DOUBLE: "DOUBLE" | "PRECISION" | "FLOAT8" | "FLOAT64"> | <#TYPE_VARCHAR: "NVARCHAR" | "VARCHAR" | "NCHAR" | | "BPCHAR" | "TEXT" | "STRING" | | "VARYING"> + | <#TYPE_VARCHAR2: > | <#TYPE_TIME: "TIMETZ" > | <#TYPE_TIMESTAMP: "TIMESTAMP_NS" | "TIMESTAMP_MS" | "TIMESTAMP_S" > @@ -3271,7 +3284,7 @@ String RelObjectNameWithoutValue() : { Token tk = null; } { ( tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= | tk= - | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="AGGREGATE" | tk="ALGORITHM" | tk="ALIGN" | tk="ALTER" | tk="ALWAYS" | tk="ANALYZE" | tk="APPEND_ONLY" | tk="APPLY" | tk="APPROXIMATE" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="ASYMMETRIC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="AUTO_INCREMENT" | tk="AZURE" | tk="BASE64" | tk="BEFORE" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOBSTORAGE" | tk="BLOCK" | tk="BOOLEAN" | tk="BRANCH" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CERTIFICATE" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="CLOUD" | tk="COALESCE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMENTS" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="CORRESPONDING" | tk="COSTS" | tk="COUNT" | tk="CREATED" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DATETIME" | tk="DBA_RECYCLEBIN" | tk="DDL" | tk="DECLARE" | tk="DEFAULTS" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DELIMIT" | tk="DELIMITER" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISCARD" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DRIVER" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="EMPTY" | tk="ENABLE" | tk="ENCODING" | tk="ENCRYPTION" | tk="END" | tk="ENFORCED" | tk="ENGINE" | tk="ERROR" | tk="ESCAPE" | tk="EXA" | tk="EXCHANGE" | tk="EXCLUDE" | tk="EXCLUDING" | tk="EXCLUSIVE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXPORT" | tk="EXTENDED" | tk="EXTRACT" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GROUP_CONCAT" | tk="GUARD" | tk="HASH" | tk="HIGH" | tk="HIGH_PRIORITY" | tk="HISTORY" | tk="HOPPING" | tk="IDENTIFIED" | tk="IDENTITY" | tk="INCLUDE" | tk="INCLUDE_NULL_VALUES" | tk="INCLUDING" | tk="INCREMENT" | tk="INDEX" | tk="INFORMATION" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="INVERSE" | tk="INVISIBLE" | tk="ISNULL" | tk="JDBC" | tk="JSON" | tk="JSON_ARRAY" | tk="JSON_ARRAYAGG" | tk="JSON_OBJECT" | tk="JSON_OBJECTAGG" | tk="JSON_TABLE" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="KEY_BLOCK_SIZE" | tk="KILL" | tk="LAST" | tk="LAX" | tk="LEADING" | tk="LESS" | tk="LINK" | tk="LOCAL" | tk="LOCK" | tk="LOCKED" | tk="LOG" | tk="LONGTEXT" | tk="LOOP" | tk="LOW" | tk="LOW_PRIORITY" | tk="LTRIM" | tk="MATCH" | tk="MATCHED" | tk="MATCH_ALL" | tk="MATCH_ANY" | tk="MATCH_PHRASE" | tk="MATCH_PHRASE_PREFIX" | tk="MATCH_REGEXP" | tk="MATERIALIZED" | tk="MAX" | tk="MAXVALUE" | tk="MEDIUMTEXT" | tk="MEMBER" | tk="MERGE" | tk="MIN" | tk="MINVALUE" | tk="MODE" | tk="MODIFY" | tk="MOVEMENT" | tk="NAME" | tk="NAMES" | tk="NEVER" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NONE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OF" | tk="OFF" | tk="OPEN" | tk="ORA" | tk="ORDINALITY" | tk="OVER" | tk="OVERFLOW" | tk="OVERLAPS" | tk="OVERRIDING" | tk="OVERWRITE" | tk="PADDING" | tk="PARALLEL" | tk="PARENT" | tk="PARSER" | tk="PARTITION" | tk="PARTITIONING" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PLAN" | tk="PLUS" | tk="PRECEDING" | tk="PRIMARY" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="REBUILD" | tk="RECURSIVE" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGEXP" | tk="REGEXP_LIKE" | tk="REGISTER" | tk="REJECT" | tk="REMOTE" | tk="REMOVE" | tk="RENAME" | tk="REORGANIZE" | tk="REPAIR" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESPECT" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="RTRIM" | tk="SAFE_CAST" | tk="SAFE_CONVERT" | tk="SAVEPOINT" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="SPATIAL" | tk="STORED" | tk="STREAM" | tk="STRICT" | tk="STRING" | tk="STRUCT" | tk="SUMMARIZE" | tk="SUSPEND" | tk="SWITCH" | tk="SYMMETRIC" | tk="SYNONYM" | tk="SYSTEM" | tk="SYSTEM_TIME" | tk="SYSTEM_TIMESTAMP" | tk="SYSTEM_VERSION" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="TEXT" | tk="THAN" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TIMEZONE" | tk="TINYTEXT" | tk="TO" | tk="TRIGGER" | tk="TRUNCATE" | tk="TRY_CAST" | tk="TRY_CONVERT" | tk="TUMBLING" | tk="TYPE" | tk="UNLIMITED" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VALIDATION" | tk="VERBOSE" | tk="VERSION" | tk="VIEW" | tk="VISIBLE" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WITHOUT_ARRAY_WRAPPER" | tk="WORK" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) + | tk="ACTION" | tk="ACTIVE" | tk="ADD" | tk="ADVANCE" | tk="ADVISE" | tk="AGAINST" | tk="AGGREGATE" | tk="ALGORITHM" | tk="ALIGN" | tk="ALLOW" | tk="ALTER" | tk="ALWAYS" | tk="ANALYZE" | tk="APPEND_ONLY" | tk="APPLY" | tk="APPROXIMATE" | tk="ARCHIVE" | tk="ARRAY" | tk="ASC" | tk="ASYMMETRIC" | tk="AT" | tk="AUTHORIZATION" | tk="AUTO" | tk="AUTO_INCREMENT" | tk="AZURE" | tk="BASE64" | tk="BEFORE" | tk="BEGIN" | tk="BERNOULLI" | tk="BINARY" | tk="BIT" | tk="BLOBSTORAGE" | tk="BLOCK" | tk="BOOLEAN" | tk="BRANCH" | tk="BROWSE" | tk="BUFFERS" | tk="BY" | tk="BYTE" | tk="BYTES" | tk="CACHE" | tk="CALL" | tk="CASCADE" | tk="CASE" | tk="CAST" | tk="CERTIFICATE" | tk="CHANGE" | tk="CHANGES" | tk="CHAR" | tk="CHARACTER" | tk="CHECKPOINT" | tk="CLOSE" | tk="CLOUD" | tk="COALESCE" | tk="COLLATE" | tk="COLUMN" | tk="COLUMNS" | tk="COMMENT" | tk="COMMENTS" | tk="COMMIT" | tk="CONCURRENTLY" | tk="CONDITIONAL" | tk="CONFLICT" | tk="CONSTRAINTS" | tk="CONVERT" | tk="CORRESPONDING" | tk="COSTS" | tk="COUNT" | tk="CREATED" | tk="CS" | tk="CYCLE" | tk="DATA" | tk="DATABASE" | tk="DATETIME" | tk="DBA_RECYCLEBIN" | tk="DDL" | tk="DECLARE" | tk="DEFAULTS" | tk="DEFERRABLE" | tk="DELAYED" | tk="DELETE" | tk="DELIMIT" | tk="DELIMITER" | tk="DESC" | tk="DESCRIBE" | tk="DISABLE" | tk="DISALLOW" | tk="DISCARD" | tk="DISCONNECT" | tk="DIV" | tk="DML" | tk="DO" | tk="DOMAIN" | tk="DRIVER" | tk="DROP" | tk="DUMP" | tk="DUPLICATE" | tk="ELEMENTS" | tk="EMIT" | tk="EMPTY" | tk="ENABLE" | tk="ENCODING" | tk="ENCRYPTION" | tk="END" | tk="ENFORCED" | tk="ENGINE" | tk="ERROR" | tk="ESCAPE" | tk="EXA" | tk="EXCHANGE" | tk="EXCLUDE" | tk="EXCLUDING" | tk="EXCLUSIVE" | tk="EXEC" | tk="EXECUTE" | tk="EXPLAIN" | tk="EXPLICIT" | tk="EXPORT" | tk="EXTENDED" | tk="EXTRACT" | tk="FILTER" | tk="FIRST" | tk="FLUSH" | tk="FN" | tk="FOLLOWING" | tk="FORMAT" | tk="FULLTEXT" | tk="FUNCTION" | tk="GRANT" | tk="GROUP_CONCAT" | tk="GUARD" | tk="HASH" | tk="HIGH" | tk="HIGH_PRIORITY" | tk="HISTORY" | tk="HOPPING" | tk="IDENTIFIED" | tk="IDENTITY" | tk="INCLUDE" | tk="INCLUDE_NULL_VALUES" | tk="INCLUDING" | tk="INCREMENT" | tk="INDEX" | tk="INFORMATION" | tk="INSERT" | tk="INTERLEAVE" | tk="INTERPRET" | tk="INVALIDATE" | tk="INVERSE" | tk="INVISIBLE" | tk="ISNULL" | tk="JDBC" | tk="JSON" | tk="JSON_ARRAY" | tk="JSON_ARRAYAGG" | tk="JSON_OBJECT" | tk="JSON_OBJECTAGG" | tk="JSON_TABLE" | tk="KEEP" | tk="KEY" | tk="KEYS" | tk="KEY_BLOCK_SIZE" | tk="KILL" | tk="LAST" | tk="LAX" | tk="LEADING" | tk="LESS" | tk="LINK" | tk="LOCAL" | tk="LOCK" | tk="LOCKED" | tk="LOG" | tk="LONGTEXT" | tk="LOOP" | tk="LOW" | tk="LOW_PRIORITY" | tk="LTRIM" | tk="MATCH" | tk="MATCHED" | tk="MATCH_ALL" | tk="MATCH_ANY" | tk="MATCH_PHRASE" | tk="MATCH_PHRASE_PREFIX" | tk="MATCH_REGEXP" | tk="MATERIALIZED" | tk="MAX" | tk="MAXVALUE" | tk="MEDIUMTEXT" | tk="MEMBER" | tk="MERGE" | tk="MIN" | tk="MINVALUE" | tk="MODE" | tk="MODIFY" | tk="MOVEMENT" | tk="NAME" | tk="NAMES" | tk="NEVER" | tk="NEXT" | tk="NO" | tk="NOCACHE" | tk="NOKEEP" | tk="NOLOCK" | tk="NOMAXVALUE" | tk="NOMINVALUE" | tk="NONE" | tk="NOORDER" | tk="NOTHING" | tk="NOTNULL" | tk="NOVALIDATE" | tk="NOWAIT" | tk="NULLS" | tk="OBJECT" | tk="OF" | tk="OFF" | tk="OPEN" | tk="ORA" | tk="ORDINALITY" | tk="OVER" | tk="OVERFLOW" | tk="OVERLAPS" | tk="OVERRIDING" | tk="OVERWRITE" | tk="PADDING" | tk="PARALLEL" | tk="PARENT" | tk="PARSER" | tk="PARTITION" | tk="PARTITIONING" | tk="PATH" | tk="PERCENT" | tk="PLACING" | tk="PLAN" | tk="PLUS" | tk="PRECEDING" | tk="PRIMARY" | tk="PURGE" | tk="QUERY" | tk="QUICK" | tk="QUIESCE" | tk="RANGE" | tk="RAW" | tk="READ" | tk="REBUILD" | tk="RECURSIVE" | tk="RECYCLEBIN" | tk="REFERENCES" | tk="REFRESH" | tk="REGEXP" | tk="REGEXP_LIKE" | tk="REGISTER" | tk="REJECT" | tk="REMOTE" | tk="REMOVE" | tk="RENAME" | tk="REORGANIZE" | tk="REPAIR" | tk="REPEATABLE" | tk="REPLACE" | tk="RESET" | tk="RESPECT" | tk="RESTART" | tk="RESTRICT" | tk="RESTRICTED" | tk="RESUMABLE" | tk="RESUME" | tk="RETURN" | tk="RLIKE" | tk="ROLLBACK" | tk="ROLLUP" | tk="ROOT" | tk="ROW" | tk="ROWS" | tk="RR" | tk="RS" | tk="RTRIM" | tk="SAFE_CAST" | tk="SAFE_CONVERT" | tk="SAVEPOINT" | tk="SCALARS" | tk="SCHEMA" | tk="SECURE" | tk="SEED" | tk="SEPARATOR" | tk="SEQUENCE" | tk="SESSION" | tk="SETS" | tk="SHARE" | tk="SHOW" | tk="SHUTDOWN" | tk="SIBLINGS" | tk="SIGNED" | tk="SIMILAR" | tk="SIZE" | tk="SKIP" | tk="SPATIAL" | tk="STORED" | tk="STREAM" | tk="STRICT" | tk="STRING" | tk="STRUCT" | tk="SUMMARIZE" | tk="SUSPEND" | tk="SWITCH" | tk="SYMMETRIC" | tk="SYNONYM" | tk="SYSTEM" | tk="SYSTEM_TIME" | tk="SYSTEM_TIMESTAMP" | tk="SYSTEM_VERSION" | tk="TABLE" | tk="TABLESPACE" | tk="TEMP" | tk="TEMPORARY" | tk="TEXT" | tk="THAN" | tk="THEN" | tk="TIMEOUT" | tk="TIMESTAMPTZ" | tk="TIMEZONE" | tk="TINYTEXT" | tk="TO" | tk="TRIGGER" | tk="TRUNCATE" | tk="TRY_CAST" | tk="TRY_CONVERT" | tk="TUMBLING" | tk="TYPE" | tk="UNCONDITIONAL" | tk="UNLIMITED" | tk="UNLOGGED" | tk="UNQIESCE" | tk="UNSIGNED" | tk="UPDATE" | tk="UPSERT" | tk="UR" | tk="USER" | tk="VALIDATE" | tk="VALIDATION" | tk="VERBOSE" | tk="VERSION" | tk="VIEW" | tk="VISIBLE" | tk="VOLATILE" | tk="WAIT" | tk="WITHIN" | tk="WITHOUT" | tk="WITHOUT_ARRAY_WRAPPER" | tk="WORK" | tk="WRAPPER" | tk="XML" | tk="XMLAGG" | tk="XMLDATA" | tk="XMLSCHEMA" | tk="XMLTEXT" | tk="XSINIL" | tk="YAML" | tk="YES" | tk="ZONE" ) { return tk.image; } } @@ -4859,6 +4872,8 @@ FromItem FromItem() #FromItem: | LOOKAHEAD(16) fromItem=TableFunction() | + fromItem=JsonTable() + | LOOKAHEAD(3) fromItem=Table() | LOOKAHEAD(ParenthesedFromItem()) fromItem = ParenthesedFromItem() @@ -6559,9 +6574,6 @@ Expression PrimaryExpression() #PrimaryExpression: | LOOKAHEAD(3, { !interrupted}) retval = JsonFunction() | LOOKAHEAD(3, { !interrupted}) retval = JsonAggregateFunction() - - | LOOKAHEAD(2, { !interrupted}) retval = JsonTable() - | LOOKAHEAD(3, { !interrupted}) retval = FullTextSearch() | LOOKAHEAD(2, {!interrupted}) retval=CastExpression() @@ -7255,10 +7267,10 @@ JsonTable JsonTable() : { expression = Expression() { result.setExpression(expression); } [ { result.setFormatJson( true ); } ] - [ pathExpression = { result.setPathExpression(pathExpression.image); } ] - [ LOOKAHEAD(3) ( { result.setOnErrorType(JsonOnErrorType.ERROR); } | { result.setOnErrorType(JsonOnErrorType.NULL); } ) ] - [ { result.setType(JsonTableType.STRICT ); } | { result.setType(JsonTableType.LAX ); } ] - [ { result.setOnEmptyType(JsonOnEmptyType.ERROR); } | { result.setOnEmptyType(JsonOnEmptyType.NULL); } ] + [ pathExpression = { result.setPathExpression( new StringValue(pathExpression.image).getValue() ); } ] + [ LOOKAHEAD(3) ( { result.setOnErrorType(JsonOnErrorType.ERROR); } | { result.setOnErrorType(JsonOnErrorType.NULL); } ) ] + [ ( { result.setType(JsonTableType.STRICT ); } | { result.setType(JsonTableType.LAX ); } ) ] + [ ( { result.setOnEmptyType(JsonOnEmptyType.ERROR); } | { result.setOnEmptyType(JsonOnEmptyType.NULL); } ) ] @@ -7279,13 +7291,84 @@ JsonTableColumn JsonTableColumn() : { Token columnName; Token jsonPath; + + boolean isUnconditional = false; + boolean isConditional = false; + boolean isArray = false; + + Token returnTypeNumber; + JsonReturnClause returnClause; + + // We use JSON_QUERY as the default type + column.setType(JsonTableColumnType.JSON_QUERY); } { ( - (columnName = | columnName=) { column.setColumnName(columnName.image); } - ( { column.setType(JsonTableColumnType.ORDINALITY); } ) - | - ( jsonPath = { column.setJsonPath(jsonPath.image); } ) + (columnName = | columnName = ) { column.setName(new StringValue(columnName.image).getValue()); } + + ( + ( { column.setType(JsonTableColumnType.ORDINALITY); } ) + | + ( + ( + { column.setReturnClause(new JsonReturnClause( JsonReturnType.CLOB )); } + | + { column.setReturnClause(new JsonReturnClause( JsonReturnType.BLOB )); } + | + { column.setReturnClause(new JsonReturnClause( JsonReturnType.JSON )); } + | + { column.setReturnClause(new JsonReturnClause( JsonReturnType.VECTOR )); } + | + ( { returnClause = new JsonReturnClause( JsonReturnType.VARCHAR2 ); } + [ + + returnTypeNumber= { returnClause.setVarcharSize(Long.valueOf(returnTypeNumber.image)); } + [ + { returnClause.setType(JsonReturnType.VARCHAR2_BYTE); } + | + { returnClause.setType(JsonReturnType.VARCHAR2_CHAR); } + ] + + ] + ) { column.setReturnClause(returnClause); } + )? + [ { column.setFormatJson(true); } ] + [ { column.setAllowScalars(true); } ] + [ { column.setAllowScalars(false); } ] + [ { column.setQueryWrapperType(JsonQueryWrapperType.WITHOUT_WRAPPER); } [ { column.setQueryWrapperType(JsonQueryWrapperType.WITHOUT_ARRAY_WRAPPER); } ] ] + [ + [ { isUnconditional = true; } | { isConditional = true; } ] [ { isArray = true; } ] { + column.setQueryWrapperType(JsonQueryWrapperType.fromWithParts(isArray, isConditional, isUnconditional)); + } + ] + jsonPath = { column.setJsonPath(new StringValue(jsonPath.image).getValue()); } + [ ( + { column.setOnErrorType(JsonOnErrorType.ERROR); } + | { column.setOnErrorType(JsonOnErrorType.NULL); } + | ( + { column.setOnErrorType(JsonOnErrorType.EMPTY); } + [ { column.setOnErrorType(JsonOnErrorType.EMPTY_ARRAY); } | { column.setOnErrorType(JsonOnErrorType.EMPTY_OBJECT); } ] + ) + ) + ] + ) + | + ( { column.setType(JsonTableColumnType.JSON_EXISTS); } + [ jsonPath = { column.setJsonPath(new StringValue(jsonPath.image).getValue()); } ] + [ LOOKAHEAD(3) ( + { column.setOnErrorType(JsonOnErrorType.ERROR); } + | { column.setOnErrorType(JsonOnErrorType.TRUE); } + | { column.setOnErrorType(JsonOnErrorType.FALSE); } + ) + ] + [ ( + { column.setOnEmptyType(JsonOnEmptyType.ERROR); } + | { column.setOnEmptyType(JsonOnEmptyType.TRUE); } + | { column.setOnEmptyType(JsonOnEmptyType.FALSE); } + ) + ] + ) + ) ) { return column; diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java b/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java index 7788bdf54..dbc4c99f8 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java @@ -2,11 +2,16 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.FromItem; +import net.sf.jsqlparser.statement.select.PlainSelect; 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.assertThat; +import static org.assertj.core.api.Assertions.*; public class JsonTableTest { @@ -25,13 +30,247 @@ void testObjectOracle(String sqlStr) throws JSQLParserException { @ParameterizedTest @ValueSource(strings = { - "JSON_TABLE(document COLUMNS( id FOR ORDINALITY))" + "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 expressionStr) throws JSQLParserException { - JsonTable table = (JsonTable) CCJSqlParserUtil.parseExpression(expressionStr); + void testExpression(String jsonTableStr) throws JSQLParserException { + JsonTable table = parseTable(jsonTableStr); assertThat(table.getColumns()).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 { + JsonTable table = parseTable(jsonTableStr); + + assertThat(table.getColumns()).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'))", + "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 { + JsonTable table = parseTable(jsonTableStr); + + assertThat(table.getColumns()).hasSize(1); + } + + @Test + void testFormatJson() throws JSQLParserException { + String expression = "JSON_TABLE(document FORMAT JSON COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.isFormatJson()).isTrue(); + } + + @Test + void testPathExpression() throws JSQLParserException { + String expression = "JSON_TABLE(document, '$.SubPath' COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.getPathExpression()).isEqualTo("$.SubPath"); + } + + @Test + void testNullOnError() throws JSQLParserException { + String expression = "JSON_TABLE(document NULL ON ERROR COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.getOnErrorType()).isEqualTo(JsonOnErrorType.NULL); + } + + @Test + void testErrorOnError() throws JSQLParserException { + String expression = "JSON_TABLE(document ERROR ON ERROR COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.getOnErrorType()).isEqualTo(JsonOnErrorType.ERROR); + } + + @Test + void testNullOnEmpty() throws JSQLParserException { + String expression = "JSON_TABLE(document NULL ON EMPTY COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.getOnEmptyType()).isEqualTo(JsonOnEmptyType.NULL); + } + + @Test + void testErrorOnEmpty() throws JSQLParserException { + String expression = "JSON_TABLE(document ERROR ON EMPTY COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + assertThat(table.getOnEmptyType()).isEqualTo(JsonOnEmptyType.ERROR); } + @Test + void testTableTypeLax() throws JSQLParserException { + String expression = "JSON_TABLE(document TYPE(LAX) COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.getType()).isEqualTo(JsonTableType.LAX); + } + + @Test + void testTableTypeStrict() throws JSQLParserException { + String expression = "JSON_TABLE(document TYPE(STRICT) COLUMNS( id FOR ORDINALITY))"; + JsonTable table = parseTable(expression); + + assertThat(table.getType()).isEqualTo(JsonTableType.STRICT); + } + + @Test + void testColumnTypeExists() throws JSQLParserException { + String expression = "JSON_TABLE(document COLUMNS( hasValue EXISTS PATH '$.pathTest'))"; + JsonTable table = parseTable(expression); + + assertThat(table.getColumns()).hasSize(1); + + JsonTableColumn col = table.getColumns().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() { + JsonTable table = new JsonTable(); + + assertThatNoException().isThrownBy(() -> { + table.setOnEmptyType(null); + table.setOnEmptyType(JsonOnEmptyType.NULL); + table.setOnEmptyType(JsonOnEmptyType.ERROR); + + table.setOnErrorType(null); + table.setOnErrorType(JsonOnErrorType.NULL); + table.setOnErrorType(JsonOnErrorType.ERROR); + + table.setType(null); + table.setType(JsonTableType.LAX); + table.setType(JsonTableType.STRICT); + }); + } + + @Test + void testInvalidSetters() { + JsonTable 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 JsonTable parseTable(String jsonTableStr) throws JSQLParserException { + String sql = "SELECT * FROM " + jsonTableStr; + Statement stmt = CCJSqlParserUtil.parse(sql); + + TestUtils.assertSqlCanBeParsedAndDeparsed(sql, true); + + FromItem fromItem = ((PlainSelect) stmt).getFromItem(); + return (JsonTable) fromItem; + } + + } From 0c5526889e87fb22ac49ae17619c979460a7363c Mon Sep 17 00:00:00 2001 From: Andreas Neumann Date: Fri, 17 Oct 2025 16:46:31 +0200 Subject: [PATCH 3/3] [chore] Code Review --- src/main/java/module-info.java | 2 + .../expression/ExpressionVisitor.java | 6 -- .../expression/ExpressionVisitorAdapter.java | 6 -- .../{ => json}/JsonOnEmptyType.java | 2 +- .../{ => json}/JsonOnErrorType.java | 2 +- .../{ => json}/JsonQueryWrapperType.java | 19 +++--- .../{ => json}/JsonReturnClause.java | 2 +- .../expression/{ => json}/JsonReturnType.java | 2 +- .../from}/JsonTable.java | 6 +- .../from}/JsonTableColumn.java | 6 +- .../from}/JsonTableColumnType.java | 2 +- .../from}/JsonTableType.java | 2 +- .../statement/select/FromItemVisitor.java | 2 +- .../select/FromItemVisitorAdapter.java | 5 +- .../sf/jsqlparser/util/TablesNamesFinder.java | 10 +-- .../util/deparser/ExpressionDeParser.java | 7 +- .../util/deparser/SelectDeParser.java | 9 ++- .../validator/ExpressionValidator.java | 67 +++++++++++++++++-- .../validation/validator/SelectValidator.java | 2 +- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 2 + .../from}/JsonTableTest.java | 4 +- .../util/TablesNamesFinderTest.java | 12 ++++ 22 files changed, 120 insertions(+), 57 deletions(-) rename src/main/java/net/sf/jsqlparser/expression/{ => json}/JsonOnEmptyType.java (97%) rename src/main/java/net/sf/jsqlparser/expression/{ => json}/JsonOnErrorType.java (97%) rename src/main/java/net/sf/jsqlparser/expression/{ => json}/JsonQueryWrapperType.java (76%) rename src/main/java/net/sf/jsqlparser/expression/{ => json}/JsonReturnClause.java (97%) rename src/main/java/net/sf/jsqlparser/expression/{ => json}/JsonReturnType.java (97%) rename src/main/java/net/sf/jsqlparser/{expression => statement/from}/JsonTable.java (96%) rename src/main/java/net/sf/jsqlparser/{expression => statement/from}/JsonTableColumn.java (96%) rename src/main/java/net/sf/jsqlparser/{expression => statement/from}/JsonTableColumnType.java (71%) rename src/main/java/net/sf/jsqlparser/{expression => statement/from}/JsonTableType.java (96%) rename src/test/java/net/sf/jsqlparser/{expression => statement/from}/JsonTableTest.java (98%) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 6487a6b97..cbc0832ec 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -57,4 +57,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/ExpressionVisitor.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java index 94ded2955..8b5ade13d 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java @@ -652,12 +652,6 @@ default void visit(JsonFunction jsonFunction) { this.visit(jsonFunction, null); } - T visit(JsonTable jsonTable, S context); - - default void visit(JsonTable jsonTable) { - this.visit(jsonTable, null); - } - T visit(ConnectByRootOperator connectByRootOperator, S context); default void visit(ConnectByRootOperator connectByRootOperator) { diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index 28fe1f8a8..96d80d514 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -728,12 +728,6 @@ public T visit(JsonFunction jsonFunction, S context) { return visitExpressions(jsonFunction, context, subExpressions); } - @Override - public T visit(JsonTable jsonTable, S context) { - // TODO: Implement - return null; - } - @Override public T visit(ConnectByRootOperator connectByRootOperator, S context) { return connectByRootOperator.getColumn().accept(this, context); diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnEmptyType.java similarity index 97% rename from src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java rename to src/main/java/net/sf/jsqlparser/expression/json/JsonOnEmptyType.java index bdcf9aafa..c78a852d1 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonOnEmptyType.java +++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnEmptyType.java @@ -23,7 +23,7 @@ * 02110-1301 USA */ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.expression.json; /** * diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnErrorType.java similarity index 97% rename from src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java rename to src/main/java/net/sf/jsqlparser/expression/json/JsonOnErrorType.java index 3661ef7fe..3c5b50b88 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonOnErrorType.java +++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonOnErrorType.java @@ -23,7 +23,7 @@ * 02110-1301 USA */ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.expression.json; /** * diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonQueryWrapperType.java similarity index 76% rename from src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java rename to src/main/java/net/sf/jsqlparser/expression/json/JsonQueryWrapperType.java index 281f8b26d..bd719511f 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonQueryWrapperType.java +++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonQueryWrapperType.java @@ -23,19 +23,22 @@ * 02110-1301 USA */ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.expression.json; /** * */ public enum JsonQueryWrapperType { - 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: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; diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonReturnClause.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnClause.java similarity index 97% rename from src/main/java/net/sf/jsqlparser/expression/JsonReturnClause.java rename to src/main/java/net/sf/jsqlparser/expression/json/JsonReturnClause.java index cb00473d5..b58c29c9c 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonReturnClause.java +++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnClause.java @@ -1,4 +1,4 @@ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.expression.json; import net.sf.jsqlparser.parser.ASTNodeAccessImpl; diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonReturnType.java b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnType.java similarity index 97% rename from src/main/java/net/sf/jsqlparser/expression/JsonReturnType.java rename to src/main/java/net/sf/jsqlparser/expression/json/JsonReturnType.java index a131f518f..24b57739e 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonReturnType.java +++ b/src/main/java/net/sf/jsqlparser/expression/json/JsonReturnType.java @@ -23,7 +23,7 @@ * 02110-1301 USA */ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.expression.json; /** * diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTable.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTable.java similarity index 96% rename from src/main/java/net/sf/jsqlparser/expression/JsonTable.java rename to src/main/java/net/sf/jsqlparser/statement/from/JsonTable.java index ea38c8af0..47f73c3d9 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTable.java +++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTable.java @@ -1,6 +1,8 @@ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.statement.from; -import net.sf.jsqlparser.parser.ASTNodeAccessImpl; +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; diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumn.java similarity index 96% rename from src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java rename to src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumn.java index ace93224c..44a791c52 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumn.java +++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumn.java @@ -1,5 +1,9 @@ -package net.sf.jsqlparser.expression; +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 { diff --git a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumnType.java similarity index 71% rename from src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java rename to src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumnType.java index fddac24fd..82c978bbc 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTableColumnType.java +++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableColumnType.java @@ -1,4 +1,4 @@ -package net.sf.jsqlparser.expression; +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/expression/JsonTableType.java b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableType.java similarity index 96% rename from src/main/java/net/sf/jsqlparser/expression/JsonTableType.java rename to src/main/java/net/sf/jsqlparser/statement/from/JsonTableType.java index 5275f309c..5b2ae37cd 100644 --- a/src/main/java/net/sf/jsqlparser/expression/JsonTableType.java +++ b/src/main/java/net/sf/jsqlparser/statement/from/JsonTableType.java @@ -23,7 +23,7 @@ * 02110-1301 USA */ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.statement.from; /** * 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 e3d9f7428..4a3c43d67 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitor.java @@ -9,8 +9,8 @@ */ package net.sf.jsqlparser.statement.select; -import net.sf.jsqlparser.expression.JsonTable; 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; 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 ecccda3af..b634adf25 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/FromItemVisitorAdapter.java @@ -12,9 +12,9 @@ import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.expression.ExpressionVisitorAdapter; -import net.sf.jsqlparser.expression.JsonTable; 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; @@ -152,7 +152,6 @@ public T visit(FromQuery fromQuery, S context) { @Override public T visit(JsonTable jsonTable, S context) { - // TODO: Implement - return null; + 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 d87c01145..cbb0ae946 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -115,6 +115,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; @@ -335,12 +336,6 @@ public void visit(PlainSelect plainSelect) { SelectVisitor.super.visit(plainSelect); } - @Override - public void visit(JsonTable jsonTable) { - FromItemVisitor.super.visit(jsonTable); - // TODO: Implement - } - /** * Override to adapt the tableName generation (e.g. with / without schema). * @@ -1729,8 +1724,7 @@ public Void visit(JsonFunction expression, S context) { @Override public Void visit(JsonTable jsonTable, S context) { - // TODO: Implement - return null; + return jsonTable.getExpression().accept(this, context); } @Override 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 5176646b6..1512a6212 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -1570,12 +1570,6 @@ public StringBuilder visit(JsonFunction expression, S context) { return builder; } - @Override - public StringBuilder visit(JsonTable expression, S context) { - expression.append(builder); - return builder; - } - @Override public StringBuilder visit(ConnectByRootOperator connectByRootOperator, S context) { builder.append("CONNECT_BY_ROOT "); @@ -1777,4 +1771,5 @@ public StringBuilder visit(CosineSimilarity cosineSimilarity, S context) { public StringBuilder visit(FromQuery fromQuery, S context) { return null; } + } 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 43406c5d9..5e6bb6a8e 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -9,7 +9,14 @@ */ package net.sf.jsqlparser.util.deparser; -import net.sf.jsqlparser.expression.*; +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; +import net.sf.jsqlparser.expression.WindowDefinition; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.imprt.Import; 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 b6b9cb4f8..a1fa87a55 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 @@ -9,7 +9,66 @@ */ package net.sf.jsqlparser.util.validation.validator; -import net.sf.jsqlparser.expression.*; +import net.sf.jsqlparser.expression.AllValue; +import net.sf.jsqlparser.expression.AnalyticExpression; +import net.sf.jsqlparser.expression.AnyComparisonExpression; +import net.sf.jsqlparser.expression.ArrayConstructor; +import net.sf.jsqlparser.expression.ArrayExpression; +import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.BooleanValue; +import net.sf.jsqlparser.expression.CaseExpression; +import net.sf.jsqlparser.expression.CastExpression; +import net.sf.jsqlparser.expression.CollateExpression; +import net.sf.jsqlparser.expression.ConnectByPriorOperator; +import net.sf.jsqlparser.expression.ConnectByRootOperator; +import net.sf.jsqlparser.expression.DateTimeLiteralExpression; +import net.sf.jsqlparser.expression.DateValue; +import net.sf.jsqlparser.expression.DoubleValue; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.ExpressionVisitor; +import net.sf.jsqlparser.expression.ExtractExpression; +import net.sf.jsqlparser.expression.Function; +import net.sf.jsqlparser.expression.HexValue; +import net.sf.jsqlparser.expression.HighExpression; +import net.sf.jsqlparser.expression.IntervalExpression; +import net.sf.jsqlparser.expression.Inverse; +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.LambdaExpression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.LowExpression; +import net.sf.jsqlparser.expression.MySQLGroupConcat; +import net.sf.jsqlparser.expression.NextValExpression; +import net.sf.jsqlparser.expression.NotExpression; +import net.sf.jsqlparser.expression.NullValue; +import net.sf.jsqlparser.expression.NumericBind; +import net.sf.jsqlparser.expression.OracleHierarchicalExpression; +import net.sf.jsqlparser.expression.OracleHint; +import net.sf.jsqlparser.expression.OracleNamedFunctionParameter; +import net.sf.jsqlparser.expression.OverlapsCondition; +import net.sf.jsqlparser.expression.RangeExpression; +import net.sf.jsqlparser.expression.RowConstructor; +import net.sf.jsqlparser.expression.RowGetExpression; +import net.sf.jsqlparser.expression.SignedExpression; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.StructType; +import net.sf.jsqlparser.expression.TimeKeyExpression; +import net.sf.jsqlparser.expression.TimeValue; +import net.sf.jsqlparser.expression.TimestampValue; +import net.sf.jsqlparser.expression.TimezoneExpression; +import net.sf.jsqlparser.expression.TranscodingFunction; +import net.sf.jsqlparser.expression.TrimFunction; +import net.sf.jsqlparser.expression.UserVariable; +import net.sf.jsqlparser.expression.VariableAssignment; +import net.sf.jsqlparser.expression.WhenClause; +import net.sf.jsqlparser.expression.WindowElement; +import net.sf.jsqlparser.expression.WindowOffset; +import net.sf.jsqlparser.expression.WindowRange; +import net.sf.jsqlparser.expression.XMLSerializeExpr; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -974,12 +1033,6 @@ public Void visit(JsonFunction expression, S context) { return null; } - @Override - public Void visit(JsonTable jsonTable, S context) { - // TODO: Implement - return null; - } - @Override public Void visit(ConnectByRootOperator connectByRootOperator, S context) { connectByRootOperator.getColumn().accept(this, context); 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 cd03a6c68..d56b4c762 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,7 +12,7 @@ import java.util.List; import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.JsonTable; +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; diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 739b21ebc..b307493e2 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -39,6 +39,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.*; diff --git a/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java b/src/test/java/net/sf/jsqlparser/statement/from/JsonTableTest.java similarity index 98% rename from src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java rename to src/test/java/net/sf/jsqlparser/statement/from/JsonTableTest.java index dbc4c99f8..2ea02fd0c 100644 --- a/src/test/java/net/sf/jsqlparser/expression/JsonTableTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/from/JsonTableTest.java @@ -1,6 +1,8 @@ -package net.sf.jsqlparser.expression; +package net.sf.jsqlparser.statement.from; import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.json.JsonOnEmptyType; +import net.sf.jsqlparser.expression.json.JsonOnErrorType; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; diff --git a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java index dd7b93c7f..16530ac37 100644 --- a/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java +++ b/src/test/java/net/sf/jsqlparser/util/TablesNamesFinderTest.java @@ -733,5 +733,17 @@ 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"); + + } + }