Browse Source
This commit modifies the `DefaultDatabaseClient` implementation in order to ensure lazier usage of the `Supplier<String>` passed to the sql method (`DatabaseClient#sql(Supplier)`). Since technically `DatabaseClient` is an interface that could have 3rd party implementations, the lazyness expectation is only hinted at in the `DatabaseClient#sql` javadoc. Possible caveat: some log statements attempt to reflect the now lazily resolved SQL string. Similarly, some exceptions can capture the SQL that caused the issue if known. We expect that these always occur after the execution of the statement has been attempted (see `ResultFunction`). At this point the SQL string will be accessible and logs and exceptions should reflect it as before. Keep an eye out for such strings turning into `null` after this change, which would indicate the opposite. Closes gh-29367pull/29890/head
7 changed files with 204 additions and 52 deletions
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* 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 |
||||
* |
||||
* https://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. |
||||
*/ |
||||
|
||||
package org.springframework.r2dbc.core; |
||||
|
||||
import java.util.function.Function; |
||||
|
||||
import io.r2dbc.spi.Connection; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* A {@link ConnectionFunction} that delegates to a {@code SqlProvider} and a plain |
||||
* {@code Function}. |
||||
* |
||||
* @author Simon Baslé |
||||
* @since 5.3.26 |
||||
* @param <R> the type of the result of the function. |
||||
*/ |
||||
final class DelegateConnectionFunction<R> implements ConnectionFunction<R> { |
||||
|
||||
private final SqlProvider sql; |
||||
|
||||
private final Function<Connection, R> function; |
||||
|
||||
|
||||
DelegateConnectionFunction(SqlProvider sql, Function<Connection, R> function) { |
||||
this.sql = sql; |
||||
this.function = function; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public R apply(Connection t) { |
||||
return this.function.apply(t); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public String getSql() { |
||||
return this.sql.getSql(); |
||||
} |
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/* |
||||
* Copyright 2002-2023 the original author or authors. |
||||
* |
||||
* 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 |
||||
* |
||||
* https://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. |
||||
*/ |
||||
|
||||
package org.springframework.r2dbc.core; |
||||
|
||||
import java.util.function.BiFunction; |
||||
import java.util.function.Supplier; |
||||
|
||||
import io.r2dbc.spi.Connection; |
||||
import io.r2dbc.spi.Result; |
||||
import io.r2dbc.spi.Statement; |
||||
import reactor.core.publisher.Flux; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* A {@link ConnectionFunction} that produces a {@code Flux} of {@link Result} and that |
||||
* defers generation of the SQL until the function has been applied. |
||||
* Beforehand, the {@code getSql()} method simply returns {@code null}. The sql String is |
||||
* also memoized during application, so that subsequent calls to {@link #getSql()} return |
||||
* the same {@code String} without further calls to the {@code Supplier}. |
||||
* |
||||
* @author Mark Paluch |
||||
* @author Simon Baslé |
||||
* @since 5.3.26 |
||||
*/ |
||||
final class ResultFunction implements ConnectionFunction<Flux<Result>> { |
||||
|
||||
final Supplier<String> sqlSupplier; |
||||
final BiFunction<Connection, String, Statement> statementFunction; |
||||
final StatementFilterFunction filterFunction; |
||||
final ExecuteFunction executeFunction; |
||||
|
||||
@Nullable |
||||
String resolvedSql = null; |
||||
|
||||
ResultFunction(Supplier<String> sqlSupplier, BiFunction<Connection, String, Statement> statementFunction, StatementFilterFunction filterFunction, ExecuteFunction executeFunction) { |
||||
this.sqlSupplier = sqlSupplier; |
||||
this.statementFunction = statementFunction; |
||||
this.filterFunction = filterFunction; |
||||
this.executeFunction = executeFunction; |
||||
} |
||||
|
||||
@Override |
||||
public Flux<Result> apply(Connection connection) { |
||||
String sql = this.sqlSupplier.get(); |
||||
Assert.state(StringUtils.hasText(sql), "SQL returned by supplier must not be empty"); |
||||
this.resolvedSql = sql; |
||||
Statement statement = this.statementFunction.apply(connection, sql); |
||||
return Flux.from(this.filterFunction.filter(statement, this.executeFunction)) |
||||
.cast(Result.class).checkpoint("SQL \"" + sql + "\" [DatabaseClient]"); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public String getSql() { |
||||
return this.resolvedSql; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue