From ee187a4c04d250e3b9bf4247082e2b4b1d435f85 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 10 May 2022 12:27:57 +0200 Subject: [PATCH] [jOOQ/jOOQ#13534] Avoid rendering PostgreSQL native cast operator :: which cannot be used in Hibernate native queries --- .../bindings/AbstractPostgresBinding.java | 53 +++++--- jOOQ/src/main/java/org/jooq/impl/Array.java | 26 ++-- jOOQ/src/main/java/org/jooq/impl/Cast.java | 64 ++++++--- jOOQ/src/main/java/org/jooq/impl/DateAdd.java | 2 +- .../java/org/jooq/impl/DefaultBinding.java | 121 ++++++++++-------- jOOQ/src/main/java/org/jooq/impl/Extract.java | 27 ++++ .../main/java/org/jooq/impl/JSONExists.java | 5 +- .../main/java/org/jooq/impl/JSONTable.java | 6 +- .../main/java/org/jooq/impl/JSONValue.java | 2 +- jOOQ/src/main/java/org/jooq/impl/Names.java | 2 + .../jooq/impl/QualifiedRecordConstant.java | 53 ++++---- 11 files changed, 224 insertions(+), 137 deletions(-) diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractPostgresBinding.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractPostgresBinding.java index 2cfac8f95b..faa249fb42 100644 --- a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractPostgresBinding.java +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractPostgresBinding.java @@ -45,6 +45,7 @@ import java.sql.SQLFeatureNotSupportedException; import org.jooq.BindingGetSQLInputContext; import org.jooq.BindingSQLContext; import org.jooq.BindingSetSQLOutputContext; +import org.jooq.Context; import org.jooq.impl.AbstractBinding; import org.jooq.impl.DSL; @@ -66,34 +67,48 @@ public abstract class AbstractPostgresBinding extends AbstractBinding ctx) throws SQLException { - if (ctx.value() instanceof Object[]) { - ctx.render().visit(keyword("ARRAY")).sql('['); + /** + * A checked exception throwing {@link Consumer}. + */ + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws SQLException; + } - String separator = ""; - for (Object value : ((Object[]) ctx.value())) { - ctx.render().sql(separator).visit(value == null ? keyword("NULL") : DSL.inline("" + value)); - separator = ", "; - } + private void castIfNeeded(Context ctx, ThrowingRunnable content) throws SQLException { + String castType = castType(); - ctx.render().sql(']'); + if (castType != null) { + ctx.visit(keyword("cast")).sql('('); + content.run(); + ctx.sql(' ').visit(keyword("as")).sql(' ').sql(castType).sql(')'); } else - super.sqlInline(ctx); + content.run(); + } - String castType = castType(); - if (castType != null) - ctx.render().sql("::").sql(castType); + @Override + protected void sqlInline(BindingSQLContext ctx) throws SQLException { + castIfNeeded(ctx.render(), () -> { + if (ctx.value() instanceof Object[]) { + ctx.render().visit(keyword("ARRAY")).sql('['); + + String separator = ""; + for (Object value : ((Object[]) ctx.value())) { + ctx.render().sql(separator).visit(value == null ? keyword("NULL") : DSL.inline("" + value)); + separator = ", "; + } + + ctx.render().sql(']'); + } + else + super.sqlInline(ctx); + }); } @Override protected void sqlBind(BindingSQLContext ctx) throws SQLException { - super.sqlBind(ctx); - - String castType = castType(); - if (castType != null) - ctx.render().sql("::").sql(castType); + castIfNeeded(ctx.render(), () -> super.sqlBind(ctx)); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Array.java b/jOOQ/src/main/java/org/jooq/impl/Array.java index 4147d3f6d9..6c5783d544 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Array.java +++ b/jOOQ/src/main/java/org/jooq/impl/Array.java @@ -40,21 +40,19 @@ package org.jooq.impl; import static java.util.Arrays.asList; // ... // ... -// ... import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.SQLDialect.YUGABYTEDB; +import static org.jooq.impl.Cast.renderCastIf; import static org.jooq.impl.Keywords.K_ARRAY; import static org.jooq.impl.Keywords.K_INT; import static org.jooq.impl.Names.N_ARRAY; import java.util.Collection; import java.util.Set; -import java.util.function.Predicate; import org.jooq.Context; import org.jooq.DataType; import org.jooq.Field; -import org.jooq.Function1; import org.jooq.QueryPart; import org.jooq.Record; // ... @@ -95,15 +93,23 @@ final class Array extends AbstractField implements QOM.Array { default: - boolean squareBrackets = true; + renderCastIf(ctx, + c -> { + switch (ctx.family()) { - ctx.visit(K_ARRAY) - .sql(squareBrackets ? '[' : '(') - .visit(fields) - .sql(squareBrackets ? ']' : ')'); - if (fields.fields.length == 0 && REQUIRES_CAST.contains(ctx.dialect())) - ctx.sql("::").visit(K_INT).sql("[]"); + + + + + default: + ctx.visit(K_ARRAY).sql('[').visit(fields).sql(']'); + break; + } + }, + c -> c.visit(K_INT).sql("[]"), + () -> fields.fields.length == 0 && REQUIRES_CAST.contains(ctx.dialect()) + ); break; } diff --git a/jOOQ/src/main/java/org/jooq/impl/Cast.java b/jOOQ/src/main/java/org/jooq/impl/Cast.java index e99d61b8ff..dd553e823c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Cast.java +++ b/jOOQ/src/main/java/org/jooq/impl/Cast.java @@ -58,13 +58,11 @@ import static org.jooq.impl.SQLDataType.VARCHAR; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; -import java.util.function.BiFunction; -import java.util.function.Predicate; +import java.util.function.BooleanSupplier; import org.jooq.Context; import org.jooq.DataType; import org.jooq.Field; -import org.jooq.Function1; import org.jooq.Keyword; import org.jooq.LanguageContext; // ... @@ -330,31 +328,57 @@ final class Cast extends AbstractField implements QOM.Cast { @Override public void accept(Context ctx) { - - // Avoid casting bind values inside an explicit cast... - CastMode castMode = ctx.castMode(); - - // Default rendering, if no special case has applied yet - ctx.visit(K_CAST).sql('(') - .castMode(CastMode.NEVER) - .visit(expression) - .castMode(castMode) - .sql(' ').visit(K_AS).sql(' '); - - if (typeAsKeyword != null) - ctx.visit(typeAsKeyword); + renderCast(ctx, + c -> c.visit(expression), + c -> { + if (typeAsKeyword != null) + c.visit(typeAsKeyword); - else - ctx.sql(type.getCastTypeName(ctx.configuration())); - - ctx.sql(')'); + else + c.sql(type.getCastTypeName(c.configuration())); + } + ); } } + static void renderCast( + Context ctx, + ThrowingConsumer, E> expression, + ThrowingConsumer, E> type + ) throws E { + + // Avoid casting bind values inside an explicit cast... + CastMode castMode = ctx.castMode(); + + // Default rendering, if no special case has applied yet + ctx.visit(K_CAST).sql('(') + .castMode(CastMode.NEVER); + + expression.accept(ctx); + + ctx.castMode(castMode) + .sql(' ').visit(K_AS).sql(' '); + + type.accept(ctx); + ctx.sql(')'); + } + + static void renderCastIf( + Context ctx, + ThrowingConsumer, E> expression, + ThrowingConsumer, E> type, + BooleanSupplier test + ) throws E { + if (test.getAsBoolean()) + renderCast(ctx, expression, type); + else + expression.accept(ctx); + } + // ------------------------------------------------------------------------- // XXX: Query Object Model // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/DateAdd.java b/jOOQ/src/main/java/org/jooq/impl/DateAdd.java index 0c77fafc0f..e51c5dc624 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DateAdd.java +++ b/jOOQ/src/main/java/org/jooq/impl/DateAdd.java @@ -219,7 +219,7 @@ implements // [#3824] Ensure that the output for DATE arithmetic will also be of type DATE, not TIMESTAMP else - ctx.sql('(').visit(date).sql(" + ").visit(interval).sql(" * ").visit(K_INTERVAL).sql(' ').visit(inline(string)).sql(")::date"); + ctx.sql("cast((").visit(date).sql(" + ").visit(interval).sql(" * ").visit(K_INTERVAL).sql(' ').visit(inline(string)).sql(") as date)"); else ctx.sql('(').visit(date).sql(" + ").visit(interval).sql(" * ").visit(K_INTERVAL).sql(' ').visit(inline(string)).sql(")"); diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index e89d3c5e07..e61015c236 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -94,6 +94,8 @@ import static org.jooq.impl.DSL.using; import static org.jooq.impl.DefaultBinding.DefaultDoubleBinding.REQUIRES_LITERAL_CAST; import static org.jooq.impl.DefaultBinding.DefaultDoubleBinding.infinity; import static org.jooq.impl.DefaultBinding.DefaultDoubleBinding.nan; +import static org.jooq.impl.DefaultBinding.DefaultEnumTypeBinding.pgEnumValue; +import static org.jooq.impl.DefaultBinding.DefaultEnumTypeBinding.pgRenderEnumCast; import static org.jooq.impl.DefaultBinding.DefaultJSONBBinding.EMULATE_AS_BLOB; import static org.jooq.impl.DefaultBinding.DefaultResultBinding.readMultisetJSON; import static org.jooq.impl.DefaultBinding.DefaultResultBinding.readMultisetXML; @@ -123,6 +125,7 @@ import static org.jooq.impl.Keywords.K_TIME_WITH_TIME_ZONE; import static org.jooq.impl.Keywords.K_TRUE; import static org.jooq.impl.Keywords.K_YEAR_TO_DAY; import static org.jooq.impl.Keywords.K_YEAR_TO_FRACTION; +import static org.jooq.impl.Names.N_BYTEA; import static org.jooq.impl.Names.N_ST_GEOMFROMTEXT; import static org.jooq.impl.Names.N_ST_GEOMFROMWKB; import static org.jooq.impl.R2DBC.isR2dbc; @@ -1223,11 +1226,6 @@ public class DefaultBinding implements Binding { } ctx.render().sql(squareBrackets ? ']' : ')'); - - // [#3214] Some PostgreSQL array type literals need explicit casting - // TODO: This seems mutually exclusive with the previous branch. Still needed? - if ((REQUIRES_ARRAY_CAST.contains(ctx.dialect())) && dataType.getArrayComponentDataType().isEnum()) - DefaultEnumTypeBinding.pgRenderEnumCast(ctx.render(), dataType.getType()); } } @@ -1243,24 +1241,22 @@ public class DefaultBinding implements Binding { @Override final void sqlBind0(BindingSQLContext ctx, Object[] value) throws SQLException { - super.sqlBind0(ctx, value); - - // In Postgres, some additional casting must be done in some cases... - switch (ctx.family()) { - - - case POSTGRES: - case YUGABYTEDB: + Cast.renderCastIf(ctx.render(), + c -> super.sqlBind0(ctx, value), + c -> { // Postgres needs explicit casting for enum (array) types if (EnumType.class.isAssignableFrom(dataType.getType().getComponentType())) - DefaultEnumTypeBinding.pgRenderEnumCast(ctx.render(), dataType.getType()); + pgRenderEnumCast(ctx.render(), dataType.getType(), pgEnumValue(dataType.getType())); // ... and also for other array types else - ctx.render().sql("::") - .sql(dataType.getCastTypeName(ctx.render().configuration())); - } + ctx.render().sql(dataType.getCastTypeName(ctx.render().configuration())); + }, + + // In Postgres, some additional casting must be done in some cases... + () -> REQUIRES_ARRAY_CAST.contains(ctx.family()) + ); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -2063,11 +2059,10 @@ public class DefaultBinding implements Binding { case POSTGRES: case YUGABYTEDB: - ctx.render() - .sql("E'") - .sql(PostgresUtils.toPGString(value)) - .sql("'::bytea"); - + Cast.renderCast(ctx.render(), + c -> c.sql("E'").sql(PostgresUtils.toPGString(value)).sql("'"), + c -> c.visit(N_BYTEA) + ); break; default: @@ -2673,19 +2668,28 @@ public class DefaultBinding implements Binding { @Override final void sqlInline0(BindingSQLContext ctx, EnumType value) throws SQLException { - binding(VARCHAR).sql(new DefaultBindingSQLContext<>(ctx.configuration(), ctx.data(), ctx.render(), value.getLiteral())); + EnumType enumValue = pgEnumValue(dataType.getType()); - if (REQUIRE_ENUM_CAST.contains(ctx.dialect())) - pgRenderEnumCast(ctx.render(), dataType.getType()); + Cast.renderCastIf(ctx.render(), + c -> binding(VARCHAR).sql(new DefaultBindingSQLContext<>(ctx.configuration(), ctx.data(), ctx.render(), value.getLiteral())), + c -> pgRenderEnumCast(c, dataType.getType(), enumValue), + + // Postgres needs explicit casting for enum (array) types + () -> REQUIRE_ENUM_CAST.contains(ctx.dialect()) && enumValue.getSchema() != null + ); } @Override final void sqlBind0(BindingSQLContext ctx, EnumType value) throws SQLException { - super.sqlBind0(ctx, value); + EnumType enumValue = pgEnumValue(dataType.getType()); - // Postgres needs explicit casting for enum (array) types - if (REQUIRE_ENUM_CAST.contains(ctx.dialect())) - pgRenderEnumCast(ctx.render(), dataType.getType()); + Cast.renderCastIf(ctx.render(), + c -> super.sqlBind0(ctx, value), + c -> pgRenderEnumCast(c, dataType.getType(), enumValue), + + // Postgres needs explicit casting for enum (array) types + () -> REQUIRE_ENUM_CAST.contains(ctx.dialect()) && enumValue.getSchema() != null + ); } @Override @@ -2718,7 +2722,7 @@ public class DefaultBinding implements Binding { return Types.VARCHAR; } - static final void pgRenderEnumCast(RenderContext render, Class type) { + static final EnumType pgEnumValue(Class type) { @SuppressWarnings("unchecked") Class enumType = (Class) ( @@ -2731,21 +2735,23 @@ public class DefaultBinding implements Binding { if (enums == null || enums.length == 0) throw new IllegalArgumentException("Not a valid EnumType : " + type); - Schema schema = enums[0].getSchema(); - if (schema != null) { - render.sql("::"); + return enums[0]; + } - schema = using(render.configuration()).map(schema); - if (schema != null && TRUE.equals(render.configuration().settings().isRenderSchema())) { - render.visit(schema); - render.sql('.'); + static final void pgRenderEnumCast(Context ctx, Class type, EnumType value) { + Schema schema = value.getSchema(); + if (schema != null) { + schema = using(ctx.configuration()).map(schema); + if (schema != null && TRUE.equals(ctx.configuration().settings().isRenderSchema())) { + ctx.visit(schema); + ctx.sql('.'); } - render.visit(name(enums[0].getName())); - } + ctx.visit(name(value.getName())); - if (type.isArray()) - render.sql("[]"); + if (type.isArray()) + ctx.sql("[]"); + } } static final E getEnumType(Class type, String literal) { @@ -3678,7 +3684,7 @@ public class DefaultBinding implements Binding { } static final class DefaultRecordBinding extends InternalBinding { - private static final Set REQUIRE_RECORD_CAST = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); + static final Set REQUIRE_RECORD_CAST = SQLDialect.supportedBy(POSTGRES, YUGABYTEDB); DefaultRecordBinding(DataType dataType, Converter converter) { super(dataType, converter); @@ -3686,20 +3692,25 @@ public class DefaultBinding implements Binding { @Override void sqlBind0(BindingSQLContext ctx, Record value) throws SQLException { - super.sqlBind0(ctx, value); - - if (REQUIRE_RECORD_CAST.contains(ctx.dialect()) && value != null) - pgRenderRecordCast(ctx.render(), value); + Cast.renderCastIf(ctx.render(), + c -> super.sqlBind0(ctx, value), + c -> pgRenderRecordCast(ctx.render(), value), + () -> REQUIRE_RECORD_CAST.contains(ctx.dialect()) && value != null + ); } @Override final void sqlInline0(BindingSQLContext ctx, Record value) throws SQLException { - if (REQUIRE_RECORD_CAST.contains(ctx.dialect())) { - ctx.render().visit(inline(PostgresUtils.toPGString(value))); - pgRenderRecordCast(ctx.render(), value); - } - else - ctx.render().sql("[UDT]"); + Cast.renderCastIf(ctx.render(), + c -> { + if (REQUIRE_RECORD_CAST.contains(ctx.dialect())) + ctx.render().visit(inline(PostgresUtils.toPGString(value))); + else + ctx.render().sql("[UDT]"); + }, + c -> pgRenderRecordCast(ctx.render(), value), + () -> REQUIRE_RECORD_CAST.contains(ctx.dialect()) + ); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -3817,11 +3828,11 @@ public class DefaultBinding implements Binding { // interfaces. Instead, a string representation of a UDT has to be parsed // ------------------------------------------------------------------------- - static final void pgRenderRecordCast(RenderContext render, Record value) { + static final void pgRenderRecordCast(Context ctx, Record value) { if (value instanceof UDTRecord) - render.sql("::").visit(((UDTRecord) value).getUDT().getQualifiedName()); + ctx.visit(((UDTRecord) value).getUDT().getQualifiedName()); else if (value instanceof TableRecord) - render.sql("::").visit(((TableRecord) value).getTable().getQualifiedName()); + ctx.visit(((TableRecord) value).getTable().getQualifiedName()); } @SuppressWarnings("unchecked") diff --git a/jOOQ/src/main/java/org/jooq/impl/Extract.java b/jOOQ/src/main/java/org/jooq/impl/Extract.java index bb426e67c0..7080f961d8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Extract.java +++ b/jOOQ/src/main/java/org/jooq/impl/Extract.java @@ -165,6 +165,33 @@ final class Extract extends AbstractField implements QOM.Extract { + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONExists.java b/jOOQ/src/main/java/org/jooq/impl/JSONExists.java index d56ce14bbf..6278c8fe69 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONExists.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONExists.java @@ -138,7 +138,10 @@ final class JSONExists extends AbstractCondition implements JSONExistsOnStep, UN case POSTGRES: case YUGABYTEDB: - ctx.visit(N_JSONB_PATH_EXISTS).sql('(').visit(castIfNeeded(json, JSONB)).sql(", ").visit(path).sql("::jsonpath)"); + ctx.visit(N_JSONB_PATH_EXISTS).sql('(') + .visit(castIfNeeded(json, JSONB)).sql(", "); + Cast.renderCast(ctx, c -> c.visit(path), c -> c.visit(Names.N_JSONPATH)); + ctx.sql(')'); break; default: diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONTable.java b/jOOQ/src/main/java/org/jooq/impl/JSONTable.java index 775c7d00e2..dab4b2f5b5 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONTable.java @@ -219,13 +219,13 @@ implements ctx, select(map(columns, col -> col.forOrdinality ? DSL.field("o").as(col.field) - : DSL.field("(jsonb_path_query_first(j, {0}::jsonpath)->>0)::{1}", + : DSL.field("cast((jsonb_path_query_first(j, cast({0} as jsonpath))->>0) as {1})", col.path != null ? val(col.path) : inline("$." + col.field.getName()), keyword(col.type.getCastTypeName(ctx.configuration())) ).as(col.field))) .from(hasOrdinality - ? "jsonb_path_query({0}, {1}::jsonpath) {with} {ordinality} {as} t(j, o)" - : "jsonb_path_query({0}, {1}::jsonpath) {as} t(j)", + ? "jsonb_path_query({0}, cast({1} as jsonpath)) {with} {ordinality} {as} t(j, o)" + : "jsonb_path_query({0}, cast({1} as jsonpath)) {as} t(j)", json.getType() == JSONB.class ? json : json.cast(JSONB), path ), diff --git a/jOOQ/src/main/java/org/jooq/impl/JSONValue.java b/jOOQ/src/main/java/org/jooq/impl/JSONValue.java index bf443cfb48..de12613791 100644 --- a/jOOQ/src/main/java/org/jooq/impl/JSONValue.java +++ b/jOOQ/src/main/java/org/jooq/impl/JSONValue.java @@ -184,7 +184,7 @@ implements case POSTGRES: case YUGABYTEDB: - ctx.visit(function(N_JSONB_PATH_QUERY_FIRST, json.getDataType(), castIfNeeded(json, JSONB), DSL.field("{0}::jsonpath", path))); + ctx.visit(function(N_JSONB_PATH_QUERY_FIRST, json.getDataType(), castIfNeeded(json, JSONB), DSL.field("cast({0} as jsonpath)", path))); break; default: { diff --git a/jOOQ/src/main/java/org/jooq/impl/Names.java b/jOOQ/src/main/java/org/jooq/impl/Names.java index 29a008320f..43e27f7a41 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Names.java +++ b/jOOQ/src/main/java/org/jooq/impl/Names.java @@ -97,6 +97,7 @@ final class Names { static final Name N_BIT_X_NOR = systemName("bit_xnor"); static final Name N_BOOLAND_AGG = systemName("booland_agg"); static final Name N_BOOLOR_AGG = systemName("boolor_agg"); + static final Name N_BYTEA = systemName("bytea"); static final Name N_BYTE_LENGTH = systemName("byte_length"); static final Name N_CAST = systemName("cast"); static final Name N_CEILING = systemName("ceiling"); @@ -173,6 +174,7 @@ final class Names { static final Name N_JSONB_OBJECT_AGG = systemName("jsonb_object_agg"); static final Name N_JSONB_PATH_EXISTS = systemName("jsonb_path_exists"); static final Name N_JSONB_PATH_QUERY_FIRST = systemName("jsonb_path_query_first"); + static final Name N_JSONPATH = systemName("jsonpath"); static final Name N_JSON_AGG = systemName("json_agg"); static final Name N_JSON_ARRAYAGG = systemName("json_arrayagg"); static final Name N_JSON_BUILD_ARRAY = systemName("json_build_array"); diff --git a/jOOQ/src/main/java/org/jooq/impl/QualifiedRecordConstant.java b/jOOQ/src/main/java/org/jooq/impl/QualifiedRecordConstant.java index f430b0fc0f..b824cc4251 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QualifiedRecordConstant.java +++ b/jOOQ/src/main/java/org/jooq/impl/QualifiedRecordConstant.java @@ -40,6 +40,7 @@ package org.jooq.impl; import static org.jooq.conf.ParamType.INLINED; import static org.jooq.impl.DSL.val; +import static org.jooq.impl.DefaultBinding.DefaultRecordBinding.REQUIRE_RECORD_CAST; import static org.jooq.impl.Keywords.K_ROW; import static org.jooq.impl.Tools.getMappedUDTName; @@ -129,40 +130,38 @@ final class QualifiedRecordConstant> extends Abstra } private final void toSQLInline(RenderContext ctx) { - switch (ctx.family()) { + Cast.renderCastIf(ctx, + c -> { + switch (c.family()) { - case POSTGRES: - case YUGABYTEDB: - ctx.visit(K_ROW); - break; + case POSTGRES: + case YUGABYTEDB: + c.visit(K_ROW); + break; - default: { - ctx.visit(value.getQualifier()); - break; - } - } + default: { + c.visit(value.getQualifier()); + break; + } + } - ctx.sql('('); + c.sql('('); - String separator = ""; - for (Field field : value.fields()) { - ctx.sql(separator); - ctx.visit(val(value.get(field), field)); - separator = ", "; - } + String separator = ""; + for (Field field : value.fields()) { + c.sql(separator); + c.visit(val(value.get(field), field)); + separator = ", "; + } - ctx.sql(')'); + c.sql(')'); + }, - // [#13174] Need to cast inline UDT ROW expressions to the UDT type - switch (ctx.family()) { - - - case POSTGRES: - case YUGABYTEDB: - ctx.sql("::").visit(value.getQualifier()); - break; - } + // [#13174] Need to cast inline UDT ROW expressions to the UDT type + c -> c.visit(value.getQualifier()), + () -> REQUIRE_RECORD_CAST.contains(ctx.dialect()) + ); } @Deprecated