diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractRowCountQuery.java b/jOOQ/src/main/java/org/jooq/impl/AbstractRowCountQuery.java index 9e4c73d202..023e92e73b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractRowCountQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractRowCountQuery.java @@ -62,7 +62,7 @@ abstract class AbstractRowCountQuery extends AbstractQuery implements Ro ConnectionFactory cf = configuration().connectionFactory(); if (!(cf instanceof NoConnectionFactory)) - subscriber.onSubscribe(new QuerySubscription<>(this, subscriber, (t, u) -> new RowCountSubscriber(u))); + subscriber.onSubscribe(new QuerySubscription<>(this, subscriber, (t, u, s) -> new RowCountSubscriber(u, s))); else subscriber.onSubscribe(new BlockingRowCountSubscription(this, subscriber)); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index deb7201113..b89f3c189c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -872,10 +872,30 @@ public class DefaultBinding implements Binding { } if (dataType.getType() == OffsetDateTime.class || - dataType.getType() == OffsetTime.class || dataType.getType() == Instant.class ) { switch (ctx.family()) { + + + + + + + + case TRINO: + return true; + } + } + + if (dataType.getType() == OffsetTime.class) { + switch (ctx.family()) { + + + + + + + case TRINO: return true; } @@ -3610,6 +3630,29 @@ public class DefaultBinding implements Binding { } } + @Override + final void sqlBind0(BindingSQLContext ctx, OffsetDateTime value) throws SQLException { + switch (ctx.family()) { + + // [#17088] The R2DBC H2 driver binds strings as CLOB, which cannot be converted to TIMESTAMPTZ + case H2: + if (isR2dbc(ctx)) { + Cast.renderCast(ctx.render(), + c -> super.sqlBind0(ctx, value), + c -> c.sql(VARCHAR.getCastTypeName(c.configuration())) + ); + } + else + super.sqlBind0(ctx, value); + + break; + + default: + super.sqlBind0(ctx, value); + break; + } + } + @Override final void set0(BindingSetStatementContext ctx, OffsetDateTime value) throws SQLException { SQLDialect family = ctx.family(); @@ -3622,6 +3665,7 @@ public class DefaultBinding implements Binding { + else ctx.statement().setString(ctx.index(), format(value, family)); } @@ -3651,6 +3695,10 @@ public class DefaultBinding implements Binding { + + + + else return OffsetDateTimeParser.offsetDateTime(ctx.resultSet().getString(ctx.index())); } @@ -3895,7 +3943,7 @@ public class DefaultBinding implements Binding { (BiFunction & Serializable) (i, x) -> OffsetDateTime.ofInstant(i, ZoneOffset.UTC) ); - private final DefaultOffsetDateTimeBinding delegate; + private final DefaultOffsetDateTimeBinding delegate; DefaultInstantBinding(DataType dataType, Converter converter) { super(dataType, converter); @@ -3913,6 +3961,11 @@ public class DefaultBinding implements Binding { delegate.sqlInline0(ctx, CONVERTER.to(value, ctx.converterContext())); } + @Override + final void sqlBind0(BindingSQLContext ctx, Instant value) throws SQLException { + delegate.sqlBind0(ctx, CONVERTER.to(value, ctx.converterContext())); + } + @Override final void set0(BindingSetStatementContext ctx, Instant value) throws SQLException { delegate.set0(ctx, CONVERTER.to(value, ctx.converterContext())); diff --git a/jOOQ/src/main/java/org/jooq/impl/R2DBC.java b/jOOQ/src/main/java/org/jooq/impl/R2DBC.java index 687216a92f..8bf3338879 100644 --- a/jOOQ/src/main/java/org/jooq/impl/R2DBC.java +++ b/jOOQ/src/main/java/org/jooq/impl/R2DBC.java @@ -37,6 +37,7 @@ */ package org.jooq.impl; +import static java.lang.Boolean.TRUE; import static org.jooq.ContextConverter.scoped; import static org.jooq.SQLDialect.MARIADB; import static org.jooq.SQLDialect.MYSQL; @@ -51,6 +52,7 @@ import static org.jooq.impl.Tools.fields; import static org.jooq.impl.Tools.recordFactory; import static org.jooq.impl.Tools.translate; import static org.jooq.impl.Tools.visitAll; +import static org.jooq.impl.Tools.BooleanDataKey.DATA_RENDER_FOR_R2DBC; import static org.jooq.tools.StringUtils.defaultIfNull; import static org.jooq.tools.jdbc.JDBCUtils.safeClose; @@ -91,6 +93,7 @@ import org.jooq.Cursor; import org.jooq.DSLContext; import org.jooq.DataType; import org.jooq.Field; +import org.jooq.Function3; import org.jooq.JSON; import org.jooq.JSONB; import org.jooq.Param; @@ -98,6 +101,7 @@ import org.jooq.Param; import org.jooq.Query; import org.jooq.Record; import org.jooq.SQLDialect; +import org.jooq.Scope; import org.jooq.TransactionalPublishable; import org.jooq.XML; import org.jooq.conf.Settings; @@ -268,11 +272,16 @@ final class R2DBC { final AbstractNonBlockingSubscription downstream; final AtomicBoolean completed; final AtomicBoolean completionRequested; + final R2DBCPreparedStatement statement; - AbstractResultSubscriber(AbstractNonBlockingSubscription downstream) { + AbstractResultSubscriber( + AbstractNonBlockingSubscription downstream, + R2DBCPreparedStatement statement + ) { this.downstream = downstream; this.completed = new AtomicBoolean(); this.completionRequested = new AtomicBoolean(); + this.statement = statement; } @Override @@ -302,8 +311,11 @@ final class R2DBC { } static final class RowCountSubscriber extends AbstractResultSubscriber { - RowCountSubscriber(AbstractNonBlockingSubscription downstream) { - super(downstream); + RowCountSubscriber( + AbstractNonBlockingSubscription downstream, + R2DBCPreparedStatement statement + ) { + super(downstream, statement); } @SuppressWarnings({ "unchecked", "rawtypes" }) @@ -333,8 +345,12 @@ final class R2DBC { final Q query; - ResultSubscriber(Q query, AbstractNonBlockingSubscription downstream) { - super(downstream); + ResultSubscriber( + Q query, + AbstractNonBlockingSubscription downstream, + R2DBCPreparedStatement statement + ) { + super(downstream, statement); this.query = query; } @@ -355,7 +371,7 @@ final class R2DBC { // TODO: What data to pass here? DefaultBindingGetResultSetContext ctx = new DefaultBindingGetResultSetContext( new SimpleExecuteContext(query.configuration(), query.configuration().data()), - new R2DBCResultSet(query.configuration(), row, meta), + new R2DBCResultSet(query.configuration(), statement, row, meta), 0 ); @@ -406,15 +422,15 @@ final class R2DBC { static final class QueryExecutionSubscriber extends ConnectionSubscriber { - final Q query; - final Configuration configuration; - final BiFunction, Subscriber> resultSubscriber; - volatile String sql; + final Q query; + final Configuration configuration; + final Function3, R2DBCPreparedStatement, Subscriber> resultSubscriber; + volatile String sql; QueryExecutionSubscriber( Q query, QuerySubscription downstream, - BiFunction, Subscriber> resultSubscriber + Function3, R2DBCPreparedStatement, Subscriber> resultSubscriber ) { super(downstream); @@ -429,7 +445,8 @@ final class R2DBC { if (query.isExecutable()) { Rendered rendered = rendered(configuration, query); Statement stmt = c.createStatement(sql = rendered.sql); - new DefaultBindContext(configuration, null, new R2DBCPreparedStatement(configuration, stmt)).visit(rendered.bindValues); + R2DBCPreparedStatement s = new R2DBCPreparedStatement(configuration, stmt); + new DefaultBindContext(configuration, null, s).visit(rendered.bindValues); // TODO: Reuse org.jooq.impl.Tools.setFetchSize(ExecuteContext ctx, int fetchSize) AbstractResultQuery q1 = abstractResultQuery(query); @@ -453,13 +470,13 @@ final class R2DBC { && !q2.nativeSupportReturningOrDataChangeDeltaTable(configuration.dsl())) stmt.returnGeneratedValues(Tools.map(q2.returningResolvedAsterisks, Field::getName, String[]::new)); - stmt.execute().subscribe(resultSubscriber.apply(query, downstream)); + stmt.execute().subscribe(resultSubscriber.apply(query, downstream, s)); } else { if (log.isDebugEnabled()) log.debug("Query is not executable", query); - Subscriber s = resultSubscriber.apply(query, downstream); + Subscriber s = resultSubscriber.apply(query, downstream, null); s.onSubscribe(new NoOpSubscription(s)); } } @@ -505,7 +522,7 @@ final class R2DBC { for (int i = 0; i < batch.queries.length; i++) b = b.add(DSL.using(batch.configuration).renderInlined(batch.queries[i])); - b.execute().subscribe(new RowCountSubscriber(downstream)); + b.execute().subscribe(new RowCountSubscriber(downstream, null)); } // [#13343] Cancel the downstream in case of a rendering bug in jOOQ @@ -535,6 +552,7 @@ final class R2DBC { batch.checkBindValues(); Rendered rendered = rendered(batch.configuration, batch.query); Statement stmt = c.createStatement(rendered.sql); + R2DBCPreparedStatement s = new R2DBCPreparedStatement(batch.query.configuration(), stmt); Param[] params = rendered.bindValues.toArray(EMPTY_PARAM); boolean first = true; @@ -546,19 +564,19 @@ final class R2DBC { if (first) first = false; else - stmt = stmt.add(); + s = new R2DBCPreparedStatement(batch.query.configuration(), stmt = stmt.add()); // [#1371] [#2139] Don't bind variables directly onto statement, bind them through the collected params // list to preserve type information // [#3547] The original query may have no Params specified - e.g. when it was constructed with // plain SQL. In that case, infer the bind value type directly from the bind value - visitAll(new DefaultBindContext(batch.configuration, null, new R2DBCPreparedStatement(batch.query.configuration(), stmt)), + visitAll(new DefaultBindContext(batch.configuration, null, s), (params.length > 0) ? fields(bindValues, params) : fields(bindValues)); } - stmt.execute().subscribe(new RowCountSubscriber(downstream)); + stmt.execute().subscribe(new RowCountSubscriber(downstream, s)); } // [#13343] Cancel the downstream in case of a rendering bug in jOOQ @@ -680,7 +698,7 @@ final class R2DBC { QuerySubscription( Q query, Subscriber subscriber, - BiFunction, Subscriber> resultSubscriber + Function3, R2DBCPreparedStatement, Subscriber> resultSubscriber ) { super(query.configuration(), subscriber); @@ -804,6 +822,9 @@ final class R2DBC { setParamType(configuration.dialect(), s) ), null); + // [#17088] Some rendering decisions may be made based on whether we're using R2DBC + render.data(DATA_RENDER_FOR_R2DBC, true); + return new Rendered( render.paramType(render.settings().getParamType()).visit(query).render(), render.bindValues(), @@ -1181,8 +1202,8 @@ final class R2DBC { final RowMetadata m; boolean wasNull; - R2DBCResultSet(Configuration c, Row r, RowMetadata m) { - super(null, null, () -> new SQLFeatureNotSupportedException("Unsupported operation of the JDBC to R2DBC bridge.")); + R2DBCResultSet(Configuration c, R2DBCPreparedStatement s, Row r, RowMetadata m) { + super(null, s, () -> new SQLFeatureNotSupportedException("Unsupported operation of the JDBC to R2DBC bridge.")); this.c = c; this.r = new DefaultRow(c, r); @@ -1653,6 +1674,10 @@ final class R2DBC { return statement instanceof R2DBCPreparedStatement; } + static final boolean isR2dbc(Scope ctx) { + return TRUE.equals(ctx.data(DATA_RENDER_FOR_R2DBC)); + } + /** * [#13343] Prevent debug rendering errors from influencing control flow. */ diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index b38a170a2d..1e998b7f6f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -688,6 +688,12 @@ final class Tools { * not the query itself. */ DATA_RENDER_IMPLICIT_JOIN, + + /** + * [#17088] Tell the {@link RenderContext} that we're rendering for + * R2DBC. + */ + DATA_RENDER_FOR_R2DBC ; private final boolean resetInSubqueryScope;