From 69c055477d85fb64faef898d7a8189042780ab53 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 23 Apr 2020 17:07:50 +0200 Subject: [PATCH] [jOOQ/jOOQ#10112] Add QueryPartInternal.rendersContent(Context) --- .../main/java/org/jooq/QueryPartInternal.java | 9 ++ .../java/org/jooq/impl/AbstractQueryPart.java | 8 ++ .../main/java/org/jooq/impl/FieldProxy.java | 5 ++ .../main/java/org/jooq/impl/JSONArray.java | 27 +++--- .../main/java/org/jooq/impl/JSONArrayAgg.java | 20 +++-- .../src/main/java/org/jooq/impl/JSONNull.java | 87 +++++++++++++++++++ .../java/org/jooq/impl/JSONNullClause.java | 45 ---------- .../main/java/org/jooq/impl/JSONObject.java | 39 +++------ .../java/org/jooq/impl/JSONObjectAgg.java | 30 ++++--- .../main/java/org/jooq/impl/ParserImpl.java | 39 +++++---- .../jooq/impl/QueryPartCollectionView.java | 43 +++++++-- .../java/org/jooq/impl/QueryPartList.java | 8 +- .../java/org/jooq/impl/QueryPartListView.java | 5 ++ .../main/java/org/jooq/impl/ScopeMarkers.java | 5 ++ .../java/org/jooq/impl/SelectFieldList.java | 7 +- .../java/org/jooq/impl/SelectQueryImpl.java | 2 +- .../main/java/org/jooq/impl/TableList.java | 5 ++ jOOQ/src/main/java/org/jooq/impl/Tools.java | 9 +- 18 files changed, 255 insertions(+), 138 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/JSONNull.java delete mode 100644 jOOQ/src/main/java/org/jooq/impl/JSONNullClause.java diff --git a/jOOQ/src/main/java/org/jooq/QueryPartInternal.java b/jOOQ/src/main/java/org/jooq/QueryPartInternal.java index 8ff5facf2a..291f76608a 100644 --- a/jOOQ/src/main/java/org/jooq/QueryPartInternal.java +++ b/jOOQ/src/main/java/org/jooq/QueryPartInternal.java @@ -50,6 +50,15 @@ import org.jooq.exception.DataAccessException; @Internal public interface QueryPartInternal extends QueryPart { + /** + * Whether a call to {@link #accept(Context)} would render content. + * + * @deprecated - Calling {@link #rendersContent(Context)} directly on a + * {@link QueryPart} is almost always a mistake. + */ + @Deprecated + boolean rendersContent(Context ctx); + /** * This {@link QueryPart} can accept a {@link Context} object * in order to render a SQL string or to bind its variables. diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java b/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java index b52aecd083..2935463fff 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java @@ -102,6 +102,14 @@ abstract class AbstractQueryPart implements QueryPartInternal { // The QueryPart and QueryPart internal API // ------------------------------------------------------------------------- + /** + * Subclasses may override this + */ + @Override + public boolean rendersContent(Context ctx) { + return true; + } + /** * Subclasses may override this */ diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java b/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java index e5fd06ea5b..980a4c7f7d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldProxy.java @@ -187,6 +187,11 @@ final class FieldProxy implements TableField, QueryPartInternal { return delegate.generatesCast(); } + @Override + public final boolean rendersContent(Context ctx) { + return delegate.rendersContent(ctx); + } + @Override public final void accept(Context ctx) { delegate.accept(ctx); diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONArray.java b/jOOQ/src/main/java/org/jooq/impl/JSONArray.java index 4fee3617a2..061642aa6d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONArray.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONArray.java @@ -43,12 +43,9 @@ import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.unquotedName; import static org.jooq.impl.DSL.values; -import static org.jooq.impl.JSONNullClause.ABSENT_ON_NULL; -import static org.jooq.impl.JSONNullClause.NULL_ON_NULL; -import static org.jooq.impl.JSONObject.acceptJSONNullClause; +import static org.jooq.impl.JSONNull.JSONNullType.ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.NULL_ON_NULL; import static org.jooq.impl.Keywords.K_JSON_ARRAY; -import static org.jooq.impl.Keywords.K_NULL; -import static org.jooq.impl.Keywords.K_ON; import static org.jooq.impl.Names.N_JSON_ARRAY; import java.util.Collection; @@ -59,6 +56,7 @@ import org.jooq.Field; import org.jooq.JSONArrayNullStep; import org.jooq.Row1; import org.jooq.Table; +import org.jooq.impl.JSONNull.JSONNullType; /** @@ -73,17 +71,17 @@ final class JSONArray extends AbstractField implements JSONArrayNullStep> args; - private final JSONNullClause nullClause; + private final JSONNullType nullType; JSONArray(DataType type, Collection> args) { this(type, args, null); } - JSONArray(DataType type, Collection> args, JSONNullClause nullClause) { + JSONArray(DataType type, Collection> args, JSONNullType nullType) { super(N_JSON_ARRAY, type); this.args = new QueryPartList<>(args); - this.nullClause = nullClause; + this.nullType = nullType; } // ------------------------------------------------------------------------- @@ -114,7 +112,7 @@ final class JSONArray extends AbstractField implements JSONArrayNullStep extends AbstractField implements JSONArrayNullStep(args, jsonNull).separator("")).sql(')'); break; + } } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java b/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java index 6b11d420ad..8a1aa3c0ee 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONArrayAgg.java @@ -42,9 +42,8 @@ import static org.jooq.SQLDialect.MARIADB; import static org.jooq.SQLDialect.MYSQL; import static org.jooq.impl.DSL.groupConcat; import static org.jooq.impl.DSL.inline; -import static org.jooq.impl.JSONNullClause.ABSENT_ON_NULL; -import static org.jooq.impl.JSONNullClause.NULL_ON_NULL; -import static org.jooq.impl.JSONObject.acceptJSONNullClause; +import static org.jooq.impl.JSONNull.JSONNullType.ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.NULL_ON_NULL; import static org.jooq.impl.Names.N_JSONB_AGG; import static org.jooq.impl.Names.N_JSON_AGG; import static org.jooq.impl.Names.N_JSON_ARRAYAGG; @@ -61,6 +60,7 @@ import org.jooq.Field; import org.jooq.JSONArrayAggOrderByStep; import org.jooq.OrderField; import org.jooq.SQLDialect; +import org.jooq.impl.JSONNull.JSONNullType; /** @@ -78,7 +78,7 @@ implements JSONArrayAggOrderByStep { private static final long serialVersionUID = 1772007627336725780L; static final Set EMULATE_WITH_GROUP_CONCAT = SQLDialect.supportedBy(MARIADB, MYSQL); - private JSONNullClause nullClause; + private JSONNullType nullType; JSONArrayAgg(DataType type, Field arg) { super(false, N_JSON_ARRAYAGG, type, arg); @@ -114,7 +114,7 @@ implements JSONArrayAggOrderByStep { ctx.sql(')'); // TODO: What about a user-defined filter clause? - if (nullClause == ABSENT_ON_NULL) + if (nullType == ABSENT_ON_NULL) acceptFilterClause(ctx, arguments.get(0).isNotNull()); break; @@ -139,19 +139,23 @@ implements JSONArrayAggOrderByStep { ctx.visit(N_JSON_ARRAYAGG).sql('('); ctx.visit(arguments.get(0)); acceptOrderBy(ctx); - acceptJSONNullClause(ctx, nullClause); + + JSONNull jsonNull = new JSONNull(nullType); + if (jsonNull.rendersContent(ctx)) + ctx.sql(' ').visit(jsonNull); + ctx.sql(')'); } @Override public final JSONArrayAgg nullOnNull() { - nullClause = NULL_ON_NULL; + nullType = NULL_ON_NULL; return this; } @Override public final JSONArrayAgg absentOnNull() { - nullClause = ABSENT_ON_NULL; + nullType = ABSENT_ON_NULL; return this; } diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONNull.java b/jOOQ/src/main/java/org/jooq/impl/JSONNull.java new file mode 100644 index 0000000000..cacb2ec986 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/JSONNull.java @@ -0,0 +1,87 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import static org.jooq.SQLDialect.MARIADB; +import static org.jooq.SQLDialect.MYSQL; +import static org.jooq.impl.JSONNull.JSONNullType.ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.NULL_ON_NULL; +import static org.jooq.impl.Keywords.K_ABSENT; +import static org.jooq.impl.Keywords.K_NULL; +import static org.jooq.impl.Keywords.K_ON; + +import java.util.Set; + +import org.jooq.Context; +import org.jooq.SQLDialect; + +/** + * @author Lukas Eder + */ +final class JSONNull extends AbstractQueryPart { + + /** + * Generated UID + */ + private static final long serialVersionUID = 3121287280045911346L; + static final Set NO_SUPPORT_ABSENT_ON_NULL = SQLDialect.supportedBy(MARIADB, MYSQL); + + final JSONNullType type; + + JSONNull(JSONNullType type) { + this.type = type; + } + + @Override + public final boolean rendersContent(Context ctx) { + return !NO_SUPPORT_ABSENT_ON_NULL.contains(ctx.dialect()) && type != null; + } + + @Override + public final void accept(Context ctx) { + if (!NO_SUPPORT_ABSENT_ON_NULL.contains(ctx.dialect())) + if (type == NULL_ON_NULL) + ctx.visit(K_NULL).sql(' ').visit(K_ON).sql(' ').visit(K_NULL); + else if (type == ABSENT_ON_NULL) + ctx.visit(K_ABSENT).sql(' ').visit(K_ON).sql(' ').visit(K_NULL); + } + + enum JSONNullType { + NULL_ON_NULL, ABSENT_ON_NULL + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONNullClause.java b/jOOQ/src/main/java/org/jooq/impl/JSONNullClause.java deleted file mode 100644 index 8587e05241..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/JSONNullClause.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Other licenses: - * ----------------------------------------------------------------------------- - * Commercial licenses for this work are available. These replace the above - * ASL 2.0 and offer limited warranties, support, maintenance, and commercial - * database integrations. - * - * For more information, please visit: http://www.jooq.org/licenses - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ -package org.jooq.impl; - -/** - * @author Lukas Eder - */ -enum JSONNullClause { - NULL_ON_NULL, ABSENT_ON_NULL -} \ No newline at end of file diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONObject.java b/jOOQ/src/main/java/org/jooq/impl/JSONObject.java index ad770ddd91..f74ecf8de8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONObject.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONObject.java @@ -38,8 +38,6 @@ package org.jooq.impl; import static org.jooq.SQLDialect.H2; -import static org.jooq.SQLDialect.MARIADB; -import static org.jooq.SQLDialect.MYSQL; // ... import static org.jooq.impl.DSL.asterisk; import static org.jooq.impl.DSL.inline; @@ -48,18 +46,14 @@ import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.unquotedName; import static org.jooq.impl.DSL.values; -import static org.jooq.impl.JSONNullClause.ABSENT_ON_NULL; -import static org.jooq.impl.JSONNullClause.NULL_ON_NULL; -import static org.jooq.impl.Keywords.K_ABSENT; +import static org.jooq.impl.JSONNull.JSONNullType.ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.NULL_ON_NULL; import static org.jooq.impl.Keywords.K_JSON_OBJECT; -import static org.jooq.impl.Keywords.K_NULL; -import static org.jooq.impl.Keywords.K_ON; import static org.jooq.impl.Names.N_JSON_MERGE; import static org.jooq.impl.Names.N_JSON_OBJECT; import static org.jooq.impl.Names.N_T; import java.util.Collection; -import java.util.Set; import org.jooq.Context; import org.jooq.DataType; @@ -68,8 +62,8 @@ import org.jooq.JSONB; import org.jooq.JSONEntry; import org.jooq.JSONObjectNullStep; import org.jooq.Name; -import org.jooq.SQLDialect; // ... +import org.jooq.impl.JSONNull.JSONNullType; /** @@ -83,20 +77,19 @@ final class JSONObject extends AbstractField implements JSONObjectNullStep * Generated UID */ private static final long serialVersionUID = 1772007627336725780L; - static final Set NO_SUPPORT_ABSENT_ON_NULL = SQLDialect.supportedBy(MARIADB, MYSQL); private final QueryPartList> args; - private final JSONNullClause nullClause; + private final JSONNullType nullType; JSONObject(DataType type, Collection> args) { this(type, args, null); } - JSONObject(DataType type, Collection> args, JSONNullClause nullClause) { + JSONObject(DataType type, Collection> args, JSONNullType nullType) { super(N_JSON_OBJECT, type); this.args = new QueryPartList<>(args); - this.nullClause = nullClause; + this.nullType = nullType; } // ------------------------------------------------------------------------- @@ -126,12 +119,12 @@ final class JSONObject extends AbstractField implements JSONObjectNullStep case POSTGRES: - if (nullClause == ABSENT_ON_NULL) + if (nullType == ABSENT_ON_NULL) ctx.visit(unquotedName(getDataType().getType() == JSONB.class ? "jsonb_strip_nulls" : "json_strip_nulls")).sql('('); ctx.visit(unquotedName(getDataType().getType() == JSONB.class ? "jsonb_build_object" : "json_build_object")).sql('(').visit(args).sql(')'); - if (nullClause == ABSENT_ON_NULL) + if (nullType == ABSENT_ON_NULL) ctx.sql(')'); break; @@ -202,27 +195,19 @@ final class JSONObject extends AbstractField implements JSONObjectNullStep } private final void acceptStandard(Context ctx) { - ctx.visit(K_JSON_OBJECT).sql('(').visit(args); + JSONNull jsonNull; // Workaround for https://github.com/h2database/h2database/issues/2496 if (args.isEmpty() && ctx.family() == H2) - ctx.visit(K_NULL).sql(' ').visit(K_ON).sql(' ').visit(K_NULL); + jsonNull = new JSONNull(NULL_ON_NULL); else - acceptJSONNullClause(ctx, nullClause); + jsonNull = new JSONNull(nullType); - ctx.sql(')'); - } - - static final void acceptJSONNullClause(Context ctx, JSONNullClause nullClause) { - if (!NO_SUPPORT_ABSENT_ON_NULL.contains(ctx.dialect())) - if (nullClause == NULL_ON_NULL) - ctx.sql(' ').visit(K_NULL).sql(' ').visit(K_ON).sql(' ').visit(K_NULL); - else if (nullClause == ABSENT_ON_NULL) - ctx.sql(' ').visit(K_ABSENT).sql(' ').visit(K_ON).sql(' ').visit(K_NULL); + ctx.visit(K_JSON_OBJECT).sql('(').visit(new QueryPartList<>(args, jsonNull).separator("")).sql(')'); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java b/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java index 0c3ffb0444..be415e561d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONObjectAgg.java @@ -43,9 +43,8 @@ import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.jsonObject; import static org.jooq.impl.DSL.jsonValue; import static org.jooq.impl.DSL.when; -import static org.jooq.impl.JSONNullClause.ABSENT_ON_NULL; -import static org.jooq.impl.JSONNullClause.NULL_ON_NULL; -import static org.jooq.impl.JSONObject.acceptJSONNullClause; +import static org.jooq.impl.JSONNull.JSONNullType.ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.NULL_ON_NULL; import static org.jooq.impl.Names.N_JSONB_OBJECT_AGG; import static org.jooq.impl.Names.N_JSON_OBJECTAGG; import static org.jooq.impl.Names.N_JSON_OBJECT_AGG; @@ -57,6 +56,7 @@ import org.jooq.Field; import org.jooq.JSON; import org.jooq.JSONEntry; import org.jooq.JSONObjectAggNullStep; +import org.jooq.impl.JSONNull.JSONNullType; /** @@ -74,7 +74,7 @@ implements JSONObjectAggNullStep { private static final long serialVersionUID = 1772007627336725780L; private final JSONEntry entry; - private JSONNullClause nullClause; + private JSONNullType nullType; JSONObjectAgg(DataType type, JSONEntry entry) { super(false, N_JSON_OBJECTAGG, type, entry.key(), entry.value()); @@ -101,7 +101,7 @@ implements JSONObjectAggNullStep { // [#10089] These dialects support non-standard JSON_OBJECTAGG without ABSENT ON NULL support case MARIADB: case MYSQL: - if (nullClause == ABSENT_ON_NULL) + if (nullType == ABSENT_ON_NULL) acceptGroupConcat(ctx); @@ -124,7 +124,7 @@ implements JSONObjectAggNullStep { ctx.sql(')'); // TODO: What about a user-defined filter clause? - if (nullClause == ABSENT_ON_NULL) + if (nullType == ABSENT_ON_NULL) acceptFilterClause(ctx, entry.value().isNotNull()); acceptOverClause(ctx); @@ -152,7 +152,7 @@ implements JSONObjectAggNullStep { break; } - if (nullClause == ABSENT_ON_NULL) + if (nullType == ABSENT_ON_NULL) value = when(entry.value().isNull(), inline((String) null)).else_((Field) value); } @@ -164,7 +164,7 @@ implements JSONObjectAggNullStep { inline('"'), DSL.replace(entry.key(), inline('"'), inline("\\\"")), inline("\":"), - nullClause == ABSENT_ON_NULL ? value1 : DSL.coalesce(value1, inline("null")) + nullType == ABSENT_ON_NULL ? value1 : DSL.coalesce(value1, inline("null")) ))); acceptOverClause(c); } @@ -174,23 +174,25 @@ implements JSONObjectAggNullStep { } private final void acceptStandard(Context ctx) { - ctx.visit(N_JSON_OBJECTAGG).sql('('); - ctx.visit(entry); - acceptJSONNullClause(ctx, nullClause); - ctx.sql(')'); + ctx.visit(N_JSON_OBJECTAGG).sql('(').visit(entry); + JSONNull jsonNull = new JSONNull(nullType); + if (jsonNull.rendersContent(ctx)) + ctx.sql(' ').visit(jsonNull); + + ctx.sql(')'); acceptOverClause(ctx); } @Override public final JSONObjectAgg nullOnNull() { - nullClause = NULL_ON_NULL; + nullType = NULL_ON_NULL; return this; } @Override public final JSONObjectAgg absentOnNull() { - nullClause = ABSENT_ON_NULL; + nullType = ABSENT_ON_NULL; return this; } } diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 2945791da0..ee73a3f7b7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -309,8 +309,8 @@ import static org.jooq.impl.DSL.xmlquery; import static org.jooq.impl.DSL.xmltable; import static org.jooq.impl.DSL.year; import static org.jooq.impl.DSL.zero; -import static org.jooq.impl.JSONNullClause.ABSENT_ON_NULL; -import static org.jooq.impl.JSONNullClause.NULL_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.JSONNullType.NULL_ON_NULL; import static org.jooq.impl.Keywords.K_DELETE; import static org.jooq.impl.Keywords.K_INSERT; import static org.jooq.impl.Keywords.K_SELECT; @@ -540,6 +540,7 @@ import org.jooq.conf.RenderNameCase; import org.jooq.conf.RenderQuotedNames; import org.jooq.conf.Settings; import org.jooq.conf.SettingsTools; +import org.jooq.impl.JSONNull.JSONNullType; import org.jooq.impl.XMLParse.DocumentOrContent; import org.jooq.tools.StringUtils; import org.jooq.tools.reflect.Reflect; @@ -7196,19 +7197,19 @@ final class ParserImpl implements Parser { return DSL.jsonArray(); List> result = null; - JSONNullClause nullClause = parseJSONObjectNullClauseIf(ctx); + JSONNullType nullType = parseJSONNullTypeIf(ctx); - if (nullClause == null) { + if (nullType == null) { result = parseFields(ctx); - nullClause = parseJSONObjectNullClauseIf(ctx); + nullType = parseJSONNullTypeIf(ctx); } parse(ctx, ')'); JSONArrayNullStep a = result == null ? DSL.jsonArray() : DSL.jsonArray(result); - return nullClause == NULL_ON_NULL + return nullType == NULL_ON_NULL ? a.nullOnNull() - : nullClause == ABSENT_ON_NULL + : nullType == ABSENT_ON_NULL ? a.absentOnNull() : a; } @@ -7221,7 +7222,7 @@ final class ParserImpl implements Parser { Field result; JSONArrayAggOrderByStep s1; JSONArrayAggNullStep s2; - JSONNullClause nullClause; + JSONNullType nullType; parse(ctx, '('); result = s2 = s1 = DSL.jsonArrayAgg(parseField(ctx)); @@ -7229,8 +7230,8 @@ final class ParserImpl implements Parser { if (parseKeywordIf(ctx, "ORDER BY")) result = s2 = s1.orderBy(parseSortSpecification(ctx)); - if ((nullClause = parseJSONObjectNullClauseIf(ctx)) != null) - result = nullClause == ABSENT_ON_NULL ? s2.absentOnNull() : s2.nullOnNull(); + if ((nullType = parseJSONNullTypeIf(ctx)) != null) + result = nullType == ABSENT_ON_NULL ? s2.absentOnNull() : s2.nullOnNull(); parse(ctx, ')'); return result; @@ -7246,22 +7247,22 @@ final class ParserImpl implements Parser { return DSL.jsonObject(); List> result = new ArrayList<>(); - JSONNullClause nullClause = parseJSONObjectNullClauseIf(ctx); + JSONNullType nullType = parseJSONNullTypeIf(ctx); - if (nullClause == null) { + if (nullType == null) { do { result.add(parseJSONEntry(ctx)); } while (parseIf(ctx, ',')); - nullClause = parseJSONObjectNullClauseIf(ctx); + nullType = parseJSONNullTypeIf(ctx); } parse(ctx, ')'); JSONObjectNullStep o = DSL.jsonObject(result); - return nullClause == NULL_ON_NULL + return nullType == NULL_ON_NULL ? o.nullOnNull() - : nullClause == ABSENT_ON_NULL + : nullType == ABSENT_ON_NULL ? o.absentOnNull() : o; } @@ -7273,13 +7274,13 @@ final class ParserImpl implements Parser { if (parseFunctionNameIf(ctx, "JSON_OBJECTAGG")) { Field result; JSONObjectAggNullStep s1; - JSONNullClause nullClause; + JSONNullType nullType; parse(ctx, '('); result = s1 = DSL.jsonObjectAgg(parseJSONEntry(ctx)); - if ((nullClause = parseJSONObjectNullClauseIf(ctx)) != null) - result = nullClause == ABSENT_ON_NULL ? s1.absentOnNull() : s1.nullOnNull(); + if ((nullType = parseJSONNullTypeIf(ctx)) != null) + result = nullType == ABSENT_ON_NULL ? s1.absentOnNull() : s1.nullOnNull(); parse(ctx, ')'); return result; @@ -7288,7 +7289,7 @@ final class ParserImpl implements Parser { return null; } - private static final JSONNullClause parseJSONObjectNullClauseIf(ParserContext ctx) { + private static final JSONNullType parseJSONNullTypeIf(ParserContext ctx) { if (parseKeywordIf(ctx, "NULL ON NULL")) return NULL_ON_NULL; else if (parseKeywordIf(ctx, "ABSENT ON NULL")) diff --git a/jOOQ/src/main/java/org/jooq/impl/QueryPartCollectionView.java b/jOOQ/src/main/java/org/jooq/impl/QueryPartCollectionView.java index 2f3d8a27f2..c8bf501e71 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QueryPartCollectionView.java +++ b/jOOQ/src/main/java/org/jooq/impl/QueryPartCollectionView.java @@ -42,12 +42,14 @@ import static java.lang.Boolean.TRUE; import static org.jooq.impl.Tools.BooleanDataKey.DATA_LIST_ALREADY_INDENTED; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.jooq.Context; import org.jooq.QueryPart; +import org.jooq.QueryPartInternal; import org.jooq.Statement; /** @@ -62,6 +64,7 @@ class QueryPartCollectionView extends AbstractQueryPart imp final Collection wrapped; int indentSize; Boolean qualify; + String separator; static QueryPartCollectionView wrap(Collection wrapped) { return new QueryPartCollectionView<>(wrapped); @@ -70,6 +73,7 @@ class QueryPartCollectionView extends AbstractQueryPart imp QueryPartCollectionView(Collection wrapped) { this.wrapped = wrapped; this.indentSize = 2; + this.separator = ","; } /** @@ -85,13 +89,29 @@ class QueryPartCollectionView extends AbstractQueryPart imp return this; } + QueryPartCollectionView separator(String newSeparator) { + this.separator = newSeparator; + return this; + } + Collection wrapped() { return wrapped; } + @Override + public boolean rendersContent(Context ctx) { + return !isEmpty(); + } + @Override public /* non-final */ void accept(Context ctx) { - int size = size(); + BitSet rendersContent = new BitSet(size()); + int i = 0; + + for (T e : this) + rendersContent.set(i++, ((QueryPartInternal) e).rendersContent(ctx)); + + int size = rendersContent.cardinality(); boolean format = size >= indentSize; boolean previousQualify = ctx.qualify(); @@ -110,18 +130,23 @@ class QueryPartCollectionView extends AbstractQueryPart imp } else { - boolean indent = format && !TRUE.equals(ctx.data(DATA_LIST_ALREADY_INDENTED)); + Object previousIndented = ctx.data(DATA_LIST_ALREADY_INDENTED); + boolean indent = format && !TRUE.equals(previousIndented); if (indent) ctx.formatIndentStart(); - int i = 0; + int j = 0; + int k = 0; for (T part : this) { - if (i++ > 0) { + if (!rendersContent.get(j++)) + continue; + + if (k++ > 0) { // [#3607] Procedures and functions are not separated by comma if (!(part instanceof Statement)) - ctx.sql(','); + ctx.sql(separator); if (format) ctx.formatSeparator(); @@ -131,7 +156,13 @@ class QueryPartCollectionView extends AbstractQueryPart imp else if (indent) ctx.formatNewLine(); - ctx.visit(part); + if (indent) { + ctx.data(DATA_LIST_ALREADY_INDENTED, part instanceof QueryPartCollectionView); + ctx.visit(part); + ctx.data(DATA_LIST_ALREADY_INDENTED, previousIndented); + } + else + ctx.visit(part); } if (indent) diff --git a/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java b/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java index c46fb5002b..7fad47b509 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java +++ b/jOOQ/src/main/java/org/jooq/impl/QueryPartList.java @@ -56,7 +56,8 @@ class QueryPartList extends QueryPartListView { this((Collection) null); } - QueryPartList(T[] wrappedList) { + @SafeVarargs + QueryPartList(T... wrappedList) { this(asList(wrappedList)); } @@ -77,4 +78,9 @@ class QueryPartList extends QueryPartListView { QueryPartList qualify(boolean newQualify) { return (QueryPartList) super.qualify(newQualify); } + + @Override + QueryPartList separator(String newSeparator) { + return (QueryPartList) super.separator(newSeparator); + } } diff --git a/jOOQ/src/main/java/org/jooq/impl/QueryPartListView.java b/jOOQ/src/main/java/org/jooq/impl/QueryPartListView.java index 709b13c6e9..d68a30c841 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QueryPartListView.java +++ b/jOOQ/src/main/java/org/jooq/impl/QueryPartListView.java @@ -83,6 +83,11 @@ class QueryPartListView extends QueryPartCollectionView return (QueryPartListView) super.qualify(newQualify); } + @Override + QueryPartListView separator(String newSeparator) { + return (QueryPartListView) super.separator(newSeparator); + } + @Override List wrapped() { return (List) super.wrapped(); diff --git a/jOOQ/src/main/java/org/jooq/impl/ScopeMarkers.java b/jOOQ/src/main/java/org/jooq/impl/ScopeMarkers.java index 82325d37c2..8bd278bc2f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ScopeMarkers.java +++ b/jOOQ/src/main/java/org/jooq/impl/ScopeMarkers.java @@ -51,6 +51,11 @@ enum ScopeMarkers implements QueryPartInternal { BEFORE_FIRST_TOP_LEVEL_CTE, AFTER_LAST_TOP_LEVEL_CTE; + @Override + public boolean rendersContent(Context ctx) { + return false; + } + @Override public final void accept(Context ctx) {} diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java b/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java index 941e4fd459..468d893fc6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectFieldList.java @@ -63,7 +63,12 @@ final class SelectFieldList extends QueryPartLi } @Override - protected void toSQLEmptyList(Context ctx) { + public final boolean rendersContent(Context ctx) { + return true; + } + + @Override + protected final void toSQLEmptyList(Context ctx) { ctx.visit(AsteriskImpl.INSTANCE); } diff --git a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java index f7f3786db9..dab460a466 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/SelectQueryImpl.java @@ -115,7 +115,7 @@ import static org.jooq.impl.DSL.xmlagg; import static org.jooq.impl.DSL.xmlattributes; import static org.jooq.impl.DSL.xmlelement; import static org.jooq.impl.JSONArrayAgg.EMULATE_WITH_GROUP_CONCAT; -import static org.jooq.impl.JSONObject.NO_SUPPORT_ABSENT_ON_NULL; +import static org.jooq.impl.JSONNull.NO_SUPPORT_ABSENT_ON_NULL; import static org.jooq.impl.Keywords.K_AND; import static org.jooq.impl.Keywords.K_BY; import static org.jooq.impl.Keywords.K_CONNECT_BY; diff --git a/jOOQ/src/main/java/org/jooq/impl/TableList.java b/jOOQ/src/main/java/org/jooq/impl/TableList.java index be7b60d056..f6ff2d10ed 100644 --- a/jOOQ/src/main/java/org/jooq/impl/TableList.java +++ b/jOOQ/src/main/java/org/jooq/impl/TableList.java @@ -69,6 +69,11 @@ final class TableList extends QueryPartList> { super(wrappedList); } + @Override + public final boolean rendersContent(Context ctx) { + return true; + } + @Override protected void toSQLEmptyList(Context ctx) { ctx.visit(new Dual()); diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index b69dc6e2be..29e3b143bd 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -5125,12 +5125,17 @@ final class Tools { static final QueryPartList qualify(final Table table, SelectFieldList fields) { QueryPartList result = new QueryPartList() { @Override - protected void toSQLEmptyList(Context context) { + public final boolean rendersContent(Context ctx) { + return super.rendersContent(ctx); + } + + @Override + protected final void toSQLEmptyList(Context context) { table.asterisk(); } @Override - public boolean declaresFields() { + public final boolean declaresFields() { return true; } };