Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#189 - Accept StatementFilterFunction in DatabaseClient #308

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>1.1.0.gh-189-SNAPSHOT</version>

<name>Spring Data R2DBC</name>
<description>Spring Data module for R2DBC</description>
Expand Down
3 changes: 2 additions & 1 deletion src/main/asciidoc/new-features.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
== What's New in Spring Data R2DBC 1.1.0 RELEASE

* Introduction of `R2dbcEntityTemplate` for entity-oriented operations.
* Support interface projections with `DatabaseClient.as(…)`
* Support interface projections with `DatabaseClient.as(…)`.
* <<r2dbc.datbaseclient.filter,Support for `ExecuteFunction` and `StatementFilterFunction` via `DatabaseClient.filter(…)`>>.

[[new-features.1-0-0-RELEASE]]
== What's New in Spring Data R2DBC 1.0.0 RELEASE
Expand Down
38 changes: 35 additions & 3 deletions src/main/asciidoc/reference/r2dbc-sql.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ In JDBC, the actual drivers translate `?` bind markers to database-native marker

Spring Data R2DBC lets you use native bind markers or named bind markers with the `:name` syntax.

Named parameter support leverages a `R2dbcDialect` instance to expand named parameters to native bind markers at the time of query execution, which gives you a certain degree of query portability across various database vendors.
Named parameter support leverages a `R2dbcDialect` instance to expand named parameters to native bind markers at the time of query execution, which gives you a certain degree of query portability across various database vendors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like the name filter. I would expect it to accept a Predicate.

I don't have a name I really like but maybe somthing like registerStatementCallback, or modifyStatement?
Or to go with the more functional nomenclature it would be a map, maybe a mapStatement?

****

The query-preprocessor unrolls named `Collection` parameters into a series of bind markers to remove the need of dynamic query creation based on the number of arguments.
Expand All @@ -159,7 +159,7 @@ tuples.add(new Object[] {"John", 35});
tuples.add(new Object[] {"Ann", 50});

db.execute("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
.bind("tuples", tuples);
.bind("tuples", tuples)
----
====

Expand All @@ -171,6 +171,38 @@ The following example shows a simpler variant using `IN` predicates:
[source,java]
----
db.execute("SELECT id, name, state FROM table WHERE age IN (:ages)")
.bind("ages", Arrays.asList(35, 50));
.bind("ages", Arrays.asList(35, 50))
----
====

[[r2dbc.datbaseclient.filter]]
== Statement Filters

You can register a `Statement` filter (`StatementFilterFunction`) through `DatabaseClient` to intercept and modify statements in their execution, as the following example shows:

====
[source,java]
----
db.execute("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
.bind("name", …)
.bind("state", …)
----
====

`DatabaseClient` exposes also simplified `filter(…)` overload accepting `UnaryOperator<Statement>`:

====
[source,java]
----
db.execute("INSERT INTO table (name, state) VALUES(:name, :state)")
.filter(s -> s.returnGeneratedValues("id"))
.bind("name", …)
.bind("state", …)

db.execute("SELECT id, name, state FROM table")
.filter(s -> s.fetchSize(25))
----
====

`StatementFilterFunction` allow filtering of the executed `Statement` and filtering of `Result` objects.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import io.r2dbc.spi.Statement;
import reactor.core.publisher.Mono;

import java.util.Arrays;
Expand All @@ -37,6 +38,7 @@
import org.springframework.data.r2dbc.query.Update;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.util.Assert;

/**
* A non-blocking, reactive client for performing database calls requests with Reactive Streams back pressure. Provides
Expand Down Expand Up @@ -142,6 +144,16 @@ interface Builder {
*/
Builder exceptionTranslator(R2dbcExceptionTranslator exceptionTranslator);

/**
* Configures a {@link ExecuteFunction} to execute {@link Statement} objects.
*
* @param executeFunction must not be {@literal null}.
* @return {@code this} {@link Builder}.
* @since 1.1
* @see Statement#execute()
*/
Builder executeFunction(ExecuteFunction executeFunction);

/**
* Configures a {@link ReactiveDataAccessStrategy}.
*
Expand Down Expand Up @@ -186,7 +198,7 @@ interface Builder {
/**
* Contract for specifying a SQL call along with options leading to the exchange.
*/
interface GenericExecuteSpec extends BindSpec<GenericExecuteSpec> {
interface GenericExecuteSpec extends BindSpec<GenericExecuteSpec>, StatementFilterSpec<GenericExecuteSpec> {

/**
* Define the target type the result should be mapped to. <br />
Expand Down Expand Up @@ -231,7 +243,7 @@ interface GenericExecuteSpec extends BindSpec<GenericExecuteSpec> {
/**
* Contract for specifying a SQL call along with options leading to the exchange.
*/
interface TypedExecuteSpec<T> extends BindSpec<TypedExecuteSpec<T>> {
interface TypedExecuteSpec<T> extends BindSpec<TypedExecuteSpec<T>>, StatementFilterSpec<TypedExecuteSpec<T>> {

/**
* Define the target type the result should be mapped to. <br />
Expand Down Expand Up @@ -866,4 +878,31 @@ interface BindSpec<S extends BindSpec<S>> {
*/
S bindNull(String name, Class<?> type);
}

/**
* Contract for applying a {@link StatementFilterFunction}.
*
* @since 1.1
*/
interface StatementFilterSpec<S extends StatementFilterSpec<S>> {

/**
* Add the given filter to the end of the filter chain.
*
* @param filter the filter to be added to the chain.
*/
default S filter(Function<? super Statement, ? extends Statement> filter) {

Assert.notNull(filter, "Statement FilterFunction must not be null!");

return filter((statement, next) -> next.execute(filter.apply(statement)));
}

/**
* Add the given filter to the end of the filter chain.
*
* @param filter the filter to be added to the chain.
*/
S filter(StatementFilterFunction filter);
}
}
Loading