From 75b6ede3bdfa1157488d9e84ade0e9aad085002e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 12 Mar 2019 11:06:15 +0100 Subject: [PATCH 01/22] #73 - Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 03f3665d..5be8a917 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-r2dbc - 1.0.0.BUILD-SNAPSHOT + 1.0.0.gh-73-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC. From cd28c6573420114f0e95b654329f957d35580800 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 18 Mar 2019 14:52:30 +0100 Subject: [PATCH 02/22] #73 - Introduce PreparedOperation. We now encapsulate prepared operations from the StatementFactory within PreparedOperation that renders SQL and provides binding values. StatementFactory supports SELECT/INSERT/UPDATE/DELETE statement creation considering Dialect-specific rendering. StatementFactory replaces String-based statement methods in ReactiveDataAccessStrategy. PreparedOperation operation = accessStrategy.getStatements().update(entity.getTableName(), binder -> { binder.bind("name", "updated value"); binder.filterBy("id", SettableValue.from(42)); }); databaseClient.execute().sql(operation).then(); --- .../data/r2dbc/function/BindIdOperation.java | 32 -- .../data/r2dbc/function/DatabaseClient.java | 5 + .../r2dbc/function/DefaultDatabaseClient.java | 65 ++- .../DefaultReactiveDataAccessStrategy.java | 339 +------------ .../function/DefaultStatementFactory.java | 474 ++++++++++++++++++ .../r2dbc/function/PreparedOperation.java | 48 ++ .../function/ReactiveDataAccessStrategy.java | 43 +- .../data/r2dbc/function/StatementFactory.java | 102 ++++ .../support/SimpleR2dbcRepository.java | 100 ++-- ...ltReactiveDataAccessStrategyUnitTests.java | 103 ---- .../function/StatementFactoryUnitTests.java | 244 +++++++++ 11 files changed, 969 insertions(+), 586 deletions(-) delete mode 100644 src/main/java/org/springframework/data/r2dbc/function/BindIdOperation.java create mode 100644 src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java create mode 100644 src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java create mode 100644 src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java delete mode 100644 src/test/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategyUnitTests.java create mode 100644 src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java diff --git a/src/main/java/org/springframework/data/r2dbc/function/BindIdOperation.java b/src/main/java/org/springframework/data/r2dbc/function/BindIdOperation.java deleted file mode 100644 index 71f437ca..00000000 --- a/src/main/java/org/springframework/data/r2dbc/function/BindIdOperation.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.springframework.data.r2dbc.function; - -import io.r2dbc.spi.Statement; - -/** - * Extension to {@link BindableOperation} for operations that allow parameter substitution for a single {@code id} - * column that accepts either a single value or multiple values, depending on the underlying operation. - * - * @author Mark Paluch - * @see Statement#bind - * @see Statement#bindNull - */ -public interface BindIdOperation extends BindableOperation { - - /** - * Bind the given {@code value} to the {@link Statement} using the underlying binding strategy. - * - * @param statement the statement to bind the value to. - * @param value the actual value. Must not be {@literal null}. - * @see Statement#bind - */ - void bindId(Statement statement, Object value); - - /** - * Bind the given {@code values} to the {@link Statement} using the underlying binding strategy. - * - * @param statement the statement to bind the value to. - * @param values the actual values. - * @see Statement#bind - */ - void bindIds(Statement statement, Iterable values); -} diff --git a/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java index 7062fca5..df95db78 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java @@ -26,6 +26,7 @@ import java.util.function.Supplier; import org.reactivestreams.Publisher; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; @@ -137,6 +138,9 @@ interface Builder { * Contract for specifying a SQL call along with options leading to the exchange. The SQL string can contain either * native parameter bind markers (e.g. {@literal $1, $2} for Postgres, {@literal @P0, @P1} for SQL Server) or named * parameters (e.g. {@literal :foo, :bar}) when {@link NamedParameterExpander} is enabled. + *

+ * Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}. + *

* * @see NamedParameterExpander * @see DatabaseClient.Builder#namedParameters(NamedParameterExpander) @@ -156,6 +160,7 @@ interface SqlSpec { * * @param sqlSupplier must not be {@literal null}. * @return a new {@link GenericExecuteSpec}. + * @see PreparedOperation */ GenericExecuteSpec sql(Supplier sqlSupplier); } diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index b7c52268..d706cb2b 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -37,7 +37,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; import java.util.function.Function; @@ -49,6 +48,7 @@ import org.reactivestreams.Publisher; import org.springframework.dao.DataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.UncategorizedR2dbcException; @@ -57,6 +57,7 @@ import org.springframework.data.r2dbc.function.connectionfactory.ConnectionProxy; import org.springframework.data.r2dbc.function.convert.ColumnMapRowMapper; import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; +import org.springframework.data.relational.core.sql.Insert; import org.springframework.jdbc.core.SqlProvider; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -337,9 +338,17 @@ FetchSpec exchange(String sql, BiFunction mappingFun logger.debug("Executing SQL statement [" + sql + "]"); } + if (sqlSupplier instanceof PreparedOperation) { + return ((PreparedOperation) sqlSupplier).bind(it.createStatement(sql)); + } + BindableOperation operation = namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), new MapBindParameterSource(byName)); + if (logger.isTraceEnabled()) { + logger.trace("Expanded SQL [" + operation.toQuery() + "]"); + } + Statement statement = it.createStatement(operation.toQuery()); byName.forEach((name, o) -> { @@ -367,6 +376,7 @@ FetchSpec exchange(String sql, BiFunction mappingFun public ExecuteSpecSupport bind(int index, Object value) { + assertNotPreparedOperation(); Assert.notNull(value, () -> String.format("Value at index %d must not be null. Use bindNull(…) instead.", index)); Map byIndex = new LinkedHashMap<>(this.byIndex); @@ -377,6 +387,8 @@ public ExecuteSpecSupport bind(int index, Object value) { public ExecuteSpecSupport bindNull(int index, Class type) { + assertNotPreparedOperation(); + Map byIndex = new LinkedHashMap<>(this.byIndex); byIndex.put(index, SettableValue.empty(type)); @@ -385,6 +397,8 @@ public ExecuteSpecSupport bindNull(int index, Class type) { public ExecuteSpecSupport bind(String name, Object value) { + assertNotPreparedOperation(); + Assert.hasText(name, "Parameter name must not be null or empty!"); Assert.notNull(value, () -> String.format("Value for parameter %s must not be null. Use bindNull(…) instead.", name)); @@ -397,6 +411,7 @@ public ExecuteSpecSupport bind(String name, Object value) { public ExecuteSpecSupport bindNull(String name, Class type) { + assertNotPreparedOperation(); Assert.hasText(name, "Parameter name must not be null or empty!"); Map byName = new LinkedHashMap<>(this.byName); @@ -405,6 +420,12 @@ public ExecuteSpecSupport bindNull(String name, Class type) { return createInstance(this.byIndex, byName, this.sqlSupplier); } + private void assertNotPreparedOperation() { + if (sqlSupplier instanceof PreparedOperation) { + throw new InvalidDataAccessApiUsageException("Cannot add bindings to a PreparedOperation"); + } + } + protected ExecuteSpecSupport createInstance(Map byIndex, Map byName, Supplier sqlSupplier) { return new ExecuteSpecSupport(byIndex, byName, sqlSupplier); @@ -882,20 +903,19 @@ private FetchSpec exchange(BiFunction mappingFunctio throw new IllegalStateException("Insert fields is empty!"); } - BindableOperation bindableInsert = dataAccessStrategy.insertAndReturnGeneratedKeys(table, byName.keySet()); + PreparedOperation operation = dataAccessStrategy.getStatements().insert(table, Collections.emptyList(), + it -> { + byName.forEach(it::bind); + }); - String sql = bindableInsert.toQuery(); + String sql = operation.toQuery(); Function insertFunction = it -> { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } - Statement statement = it.createStatement(sql).returnGeneratedValues(); - - byName.forEach((k, v) -> bindableInsert.bind(statement, k, v)); - - return statement; + return operation.bind(it.createStatement(sql)); }; Function> resultFunction = it -> Flux.from(insertFunction.apply(it).execute()); @@ -999,18 +1019,17 @@ private FetchSpec exchange(Object toInsert, BiFunction columns = new LinkedHashSet<>(); - - outboundRow.forEach((k, v) -> { - - if (v.hasValue()) { - columns.add(k); - } - }); + PreparedOperation operation = dataAccessStrategy.getStatements().insert(table, Collections.emptyList(), + it -> { + outboundRow.forEach((k, v) -> { - BindableOperation bindableInsert = dataAccessStrategy.insertAndReturnGeneratedKeys(table, columns); + if (v.hasValue()) { + it.bind(k, v); + } + }); + }); - String sql = bindableInsert.toQuery(); + String sql = operation.toQuery(); Function insertFunction = it -> { @@ -1018,15 +1037,7 @@ private FetchSpec exchange(Object toInsert, BiFunction { - if (v.hasValue()) { - bindableInsert.bind(statement, k, v); - } - }); - - return statement; + return operation.bind(it.createStatement(sql)); }; Function> resultFunction = it -> Flux.from(insertFunction.apply(it).execute()); diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java index d36ea69c..76f17940 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java @@ -17,18 +17,14 @@ import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; -import io.r2dbc.spi.Statement; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.OptionalLong; import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Function; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.convert.CustomConversions.StoreConversions; @@ -37,8 +33,6 @@ import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.r2dbc.dialect.ArrayColumns; -import org.springframework.data.r2dbc.dialect.BindMarker; -import org.springframework.data.r2dbc.dialect.BindMarkers; import org.springframework.data.r2dbc.dialect.BindMarkersFactory; import org.springframework.data.r2dbc.dialect.Dialect; import org.springframework.data.r2dbc.domain.OutboundRow; @@ -56,10 +50,12 @@ import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndOrderBy; import org.springframework.data.relational.core.sql.StatementBuilder; import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.render.NamingStrategies; +import org.springframework.data.relational.core.sql.render.RenderContext; +import org.springframework.data.relational.core.sql.render.RenderNamingStrategy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * Default {@link ReactiveDataAccessStrategy} implementation. @@ -71,6 +67,7 @@ public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStra private final Dialect dialect; private final R2dbcConverter converter; private final MappingContext, ? extends RelationalPersistentProperty> mappingContext; + private final StatementFactory statements; /** * Creates a new {@link DefaultReactiveDataAccessStrategy} given {@link Dialect}. @@ -118,6 +115,15 @@ public DefaultReactiveDataAccessStrategy(Dialect dialect, R2dbcConverter convert this.mappingContext = (MappingContext, ? extends RelationalPersistentProperty>) this.converter .getMappingContext(); this.dialect = dialect; + + RenderContext renderContext = new RenderContext() { + @Override + public RenderNamingStrategy getNamingStrategy() { + return NamingStrategies.asIs(); + } + }; + + this.statements = new DefaultStatementFactory(this.dialect, renderContext); } /* @@ -218,7 +224,6 @@ public Sort getMappedSort(Class typeToRead, Sort sort) { * (non-Javadoc) * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getRowMapper(java.lang.Class) */ - @SuppressWarnings("unchecked") @Override public BiFunction getRowMapper(Class typeToRead) { return new EntityRowMapper<>(typeToRead, converter); @@ -233,6 +238,15 @@ public String getTableName(Class type) { return getRequiredPersistentEntity(type).getTableName(); } + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getStatements() + */ + @Override + public StatementFactory getStatements() { + return this.statements; + } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getBindMarkersFactory() @@ -251,15 +265,6 @@ private RelationalPersistentEntity getPersistentEntity(Class typeToRead) { return mappingContext.getPersistentEntity(typeToRead); } - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#insertAndReturnGeneratedKeys(java.lang.String, java.util.Set) - */ - @Override - public BindableOperation insertAndReturnGeneratedKeys(String table, Set columns) { - return new DefaultBindableInsert(dialect.getBindMarkersFactory().create(), table, columns); - } - /* * (non-Javadoc) * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#select(java.lang.String, java.util.Set, org.springframework.data.domain.Sort, org.springframework.data.domain.Pageable) @@ -310,304 +315,4 @@ private Collection createOrderByFields(Table table, Sort return fields; } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#updateById(java.lang.String, java.util.Set, java.lang.String) - */ - @Override - public BindIdOperation updateById(String table, Set columns, String idColumn) { - return new DefaultBindableUpdate(dialect.getBindMarkersFactory().create(), table, columns, idColumn); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#deleteById(java.lang.String, java.lang.String) - */ - @Override - public BindIdOperation deleteById(String table, String idColumn) { - - return new DefaultBindIdOperation(dialect.getBindMarkersFactory().create(), - marker -> String.format("DELETE FROM %s WHERE %s = %s", table, idColumn, marker.getPlaceholder())); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#deleteByIdIn(java.lang.String, java.lang.String) - */ - @Override - public BindIdOperation deleteByIdIn(String table, String idColumn) { - - String query = String.format("DELETE FROM %s", table); - return new DefaultBindIdIn(dialect.getBindMarkersFactory().create(), query, idColumn); - } - - /** - * Default {@link BindableOperation} implementation for a {@code INSERT} operation. - */ - static class DefaultBindableInsert implements BindableOperation { - - private final Map markers = new LinkedHashMap<>(); - private final String query; - - DefaultBindableInsert(BindMarkers bindMarkers, String table, Collection columns) { - - StringBuilder builder = new StringBuilder(); - List placeholders = new ArrayList<>(columns.size()); - - for (String column : columns) { - BindMarker marker = markers.computeIfAbsent(column, bindMarkers::next); - placeholders.add(marker.getPlaceholder()); - } - - String columnsString = StringUtils.collectionToDelimitedString(columns, ", "); - String placeholdersString = StringUtils.collectionToDelimitedString(placeholders, ", "); - - builder.append("INSERT INTO ").append(table).append(" (").append(columnsString).append(")").append(" VALUES(") - .append(placeholdersString).append(")"); - - this.query = builder.toString(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bind(io.r2dbc.spi.Statement, java.lang.String, java.lang.Object) - */ - @Override - public void bind(Statement statement, String identifier, Object value) { - markers.get(identifier).bind(statement, value); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(io.r2dbc.spi.Statement, java.lang.String, java.lang.Class) - */ - @Override - public void bindNull(Statement statement, String identifier, Class valueType) { - markers.get(identifier).bindNull(statement, valueType); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() - */ - @Override - public String toQuery() { - return this.query; - } - } - - /** - * Default {@link BindIdOperation} implementation for a {@code UPDATE} operation using a single key. - */ - static class DefaultBindableUpdate implements BindIdOperation { - - private final Map markers = new LinkedHashMap<>(); - private final BindMarker idMarker; - private final String query; - - DefaultBindableUpdate(BindMarkers bindMarkers, String tableName, Set columns, String idColumnName) { - - this.idMarker = bindMarkers.next(); - - StringBuilder setClause = new StringBuilder(); - - for (String column : columns) { - - BindMarker marker = markers.computeIfAbsent(column, bindMarkers::next); - - if (setClause.length() != 0) { - setClause.append(", "); - } - - setClause.append(column).append(" = ").append(marker.getPlaceholder()); - } - - this.query = String.format("UPDATE %s SET %s WHERE %s = %s", tableName, setClause, idColumnName, - idMarker.getPlaceholder()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bind(io.r2dbc.spi.Statement, java.lang.String, java.lang.Object) - */ - @Override - public void bind(Statement statement, String identifier, Object value) { - markers.get(identifier).bind(statement, value); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(io.r2dbc.spi.Statement, java.lang.String, java.lang.Class) - */ - @Override - public void bindNull(Statement statement, String identifier, Class valueType) { - markers.get(identifier).bindNull(statement, valueType); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindIdOperation#bindId(io.r2dbc.spi.Statement, java.lang.Object) - */ - @Override - public void bindId(Statement statement, Object value) { - idMarker.bind(statement, value); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindIdOperation#bindIds(io.r2dbc.spi.Statement, java.lang.Iterable) - */ - @Override - public void bindIds(Statement statement, Iterable values) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() - */ - @Override - public String toQuery() { - return this.query; - } - } - - /** - * Default {@link BindIdOperation} implementation for a {@code SELECT} or {@code DELETE} operation using a single key - * in the {@code WHERE} predicate. - */ - static class DefaultBindIdOperation implements BindIdOperation { - - private final BindMarker idMarker; - private final String query; - - DefaultBindIdOperation(BindMarkers bindMarkers, Function queryFunction) { - - this.idMarker = bindMarkers.next(); - this.query = queryFunction.apply(this.idMarker); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bind(io.r2dbc.spi.Statement, java.lang.String, java.lang.Object) - */ - @Override - public void bind(Statement statement, String identifier, Object value) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(io.r2dbc.spi.Statement, java.lang.String, java.lang.Class) - */ - @Override - public void bindNull(Statement statement, String identifier, Class valueType) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindIdOperation#bindId(io.r2dbc.spi.Statement, java.lang.Object) - */ - @Override - public void bindId(Statement statement, Object value) { - idMarker.bind(statement, value); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindIdOperation#bindIds(io.r2dbc.spi.Statement, java.lang.Iterable) - */ - @Override - public void bindIds(Statement statement, Iterable values) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() - */ - @Override - public String toQuery() { - return this.query; - } - } - - /** - * Default {@link BindIdOperation} implementation for a {@code SELECT … WHERE id IN (…)} or - * {@code DELETE … WHERE id IN (…)}. - */ - static class DefaultBindIdIn implements BindIdOperation { - - private final List markers = new ArrayList<>(); - private final BindMarkers bindMarkers; - private final String baseQuery; - private final String idColumnName; - - DefaultBindIdIn(BindMarkers bindMarkers, String baseQuery, String idColumnName) { - - this.bindMarkers = bindMarkers; - this.baseQuery = baseQuery; - this.idColumnName = idColumnName; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bind(io.r2dbc.spi.Statement, java.lang.String, java.lang.Object) - */ - @Override - public void bind(Statement statement, String identifier, Object value) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(io.r2dbc.spi.Statement, java.lang.String, java.lang.Class) - */ - @Override - public void bindNull(Statement statement, String identifier, Class valueType) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindIdOperation#bindId(io.r2dbc.spi.Statement, java.lang.Object) - */ - @Override - public void bindId(Statement statement, Object value) { - - BindMarker bindMarker = bindMarkers.next(); - markers.add(bindMarker.getPlaceholder()); - bindMarker.bind(statement, value); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindIdOperation#bindIds(io.r2dbc.spi.Statement, java.lang.Iterable) - */ - @Override - public void bindIds(Statement statement, Iterable values) { - - for (Object value : values) { - bindId(statement, value); - } - } - - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() - */ - @Override - public String toQuery() { - - if (this.markers.isEmpty()) { - throw new UnsupportedOperationException(); - } - - String in = StringUtils.collectionToDelimitedString(this.markers, ", "); - - return String.format("%s WHERE %s IN (%s)", this.baseQuery, this.idColumnName, in); - } - } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java new file mode 100644 index 00000000..06b9cc94 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -0,0 +1,474 @@ +/* + * Copyright 2019 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 + * + * 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. + */ +package org.springframework.data.r2dbc.function; + +import io.r2dbc.spi.Statement; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.r2dbc.dialect.BindMarker; +import org.springframework.data.r2dbc.dialect.BindMarkers; +import org.springframework.data.r2dbc.dialect.Dialect; +import org.springframework.data.r2dbc.domain.SettableValue; +import org.springframework.data.relational.core.sql.AssignValue; +import org.springframework.data.relational.core.sql.Assignment; +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.Condition; +import org.springframework.data.relational.core.sql.Delete; +import org.springframework.data.relational.core.sql.DeleteBuilder; +import org.springframework.data.relational.core.sql.Expression; +import org.springframework.data.relational.core.sql.Insert; +import org.springframework.data.relational.core.sql.SQL; +import org.springframework.data.relational.core.sql.Select; +import org.springframework.data.relational.core.sql.SelectBuilder; +import org.springframework.data.relational.core.sql.StatementBuilder; +import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.Update; +import org.springframework.data.relational.core.sql.UpdateBuilder; +import org.springframework.data.relational.core.sql.render.RenderContext; +import org.springframework.data.relational.core.sql.render.SqlRenderer; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Default {@link StatementFactory} implementation. + * + * @author Mark Paluch + */ +@RequiredArgsConstructor +class DefaultStatementFactory implements StatementFactory { + + private final Dialect dialect; + private final RenderContext renderContext; + + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.function.StatementFactory#select(java.lang.String, java.util.Collection, java.util.function.Consumer) + */ + @Override + public PreparedOperation select(String tableName, Collection columnNames, + Consumer binderConsumer); + + /** + * Creates a {@link Insert} statement. + * + * @param tableName must not be {@literal null} or empty. + * @param generatedKeysNames must not be {@literal null}. + * @param binderConsumer customizer for bindings. Supports only + * {@link StatementBinderBuilder#bind(String, SettableValue)} bindings. + * @return the {@link PreparedOperation} to update values in {@code tableName} assigning bound values. + */ + PreparedOperation insert(String tableName, Collection generatedKeysNames, + Consumer binderConsumer); + + /** + * Creates a {@link Update} statement. + * + * @param tableName must not be {@literal null} or empty. + * @param binderConsumer customizer for bindings. + * @return the {@link PreparedOperation} to update values in {@code tableName} assigning bound values. + */ + PreparedOperation update(String tableName, Consumer binderConsumer); + + /** + * Creates a {@link Delete} statement. + * + * @param tableName must not be {@literal null} or empty. + * @param binderConsumer customizer for bindings. Supports only + * {@link StatementBinderBuilder#filterBy(String, SettableValue)} bindings. + * @return the {@link PreparedOperation} to delete rows from {@code tableName}. + */ + PreparedOperation delete(String tableName, Consumer binderConsumer); + + /** + * Binder to specify parameter bindings by name. Bindings match to equals comparisons. + */ + interface StatementBinderBuilder { + + /** + * Bind the given Id {@code value} to this builder using the underlying binding strategy to express a filter + * condition. {@link Collection} type values translate to {@code IN} matching. + * + * @param identifier named identifier that is considered by the underlying binding strategy. + * @param settable must not be {@literal null}. Use {@link SettableValue#empty(Class)} for {@code NULL} values. + */ + void filterBy(String identifier, SettableValue settable); + + /** + * Bind the given {@code value} to this builder using the underlying binding strategy. + * + * @param identifier named identifier that is considered by the underlying binding strategy. + * @param settable must not be {@literal null}. Use {@link SettableValue#empty(Class)} for {@code NULL} values. + */ + void bind(String identifier, SettableValue settable); + } +} diff --git a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java index 19636168..cda23d9a 100644 --- a/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java +++ b/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java @@ -20,29 +20,25 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; import org.reactivestreams.Publisher; -import org.springframework.data.r2dbc.dialect.BindMarker; -import org.springframework.data.r2dbc.dialect.BindMarkers; import org.springframework.data.r2dbc.domain.SettableValue; -import org.springframework.data.r2dbc.function.BindIdOperation; import org.springframework.data.r2dbc.function.DatabaseClient; -import org.springframework.data.r2dbc.function.DatabaseClient.GenericExecuteSpec; +import org.springframework.data.r2dbc.function.PreparedOperation; import org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy; +import org.springframework.data.r2dbc.function.StatementFactory; import org.springframework.data.r2dbc.function.convert.R2dbcConverter; -import org.springframework.data.relational.core.sql.Conditions; -import org.springframework.data.relational.core.sql.Expression; +import org.springframework.data.relational.core.sql.Delete; import org.springframework.data.relational.core.sql.Functions; -import org.springframework.data.relational.core.sql.SQL; import org.springframework.data.relational.core.sql.Select; import org.springframework.data.relational.core.sql.StatementBuilder; import org.springframework.data.relational.core.sql.Table; +import org.springframework.data.relational.core.sql.Update; import org.springframework.data.relational.core.sql.render.SqlRenderer; import org.springframework.data.relational.repository.query.RelationalEntityInformation; import org.springframework.data.repository.reactive.ReactiveCrudRepository; @@ -83,15 +79,13 @@ public Mono save(S objectToSave) { Map columns = accessStrategy.getOutboundRow(objectToSave); columns.remove(getIdColumnName()); // do not update the Id column. String idColumnName = getIdColumnName(); - BindIdOperation update = accessStrategy.updateById(entity.getTableName(), columns.keySet(), idColumnName); - GenericExecuteSpec exec = databaseClient.execute().sql(update); - - BindSpecAdapter wrapper = BindSpecAdapter.create(exec); - columns.forEach((k, v) -> update.bind(wrapper, k, v)); - update.bindId(wrapper, id); + PreparedOperation operation = accessStrategy.getStatements().update(entity.getTableName(), binder -> { + columns.forEach(binder::bind); + binder.filterBy(idColumnName, SettableValue.from(id)); + }); - return wrapper.getBoundOperation().as(entity.getJavaType()) // + return databaseClient.execute().sql(operation).as(entity.getJavaType()) // .then() // .thenReturn(objectToSave); } @@ -129,18 +123,14 @@ public Mono findById(ID id) { Set columns = new LinkedHashSet<>(accessStrategy.getAllColumns(entity.getJavaType())); String idColumnName = getIdColumnName(); - BindMarkers bindMarkers = accessStrategy.getBindMarkersFactory().create(); - BindMarker bindMarker = bindMarkers.next("id"); + StatementFactory statements; - Table table = Table.create(entity.getTableName()); - Select select = StatementBuilder // - .select(table.columns(columns)) // - .from(table) // - .where(Conditions.isEqual(table.column(idColumnName), SQL.bindMarker(bindMarker.getPlaceholder()))) // - .build(); + PreparedOperation operation = accessStrategy.getStatements().select(entity.getTableName(), + Collections.singleton(idColumnName), binder -> { + binder.filterBy(idColumnName, SettableValue.from(id)); + }); - return databaseClient.execute().sql(SqlRenderer.toString(select)) // - .bind(0, id) // + return databaseClient.execute().sql(operation) // .map((r, md) -> r) // .first() // .hasElement(); @@ -225,25 +209,12 @@ public Flux findAllById(Publisher idPublisher) { Set columns = new LinkedHashSet<>(accessStrategy.getAllColumns(entity.getJavaType())); String idColumnName = getIdColumnName(); - BindMarkers bindMarkers = accessStrategy.getBindMarkersFactory().create(); - - List markers = new ArrayList<>(); - - for (int i = 0; i < ids.size(); i++) { - markers.add(SQL.bindMarker(bindMarkers.next("id").getPlaceholder())); - } - - Table table = Table.create(entity.getTableName()); - Select select = StatementBuilder.select(table.columns(columns)).from(table) - .where(Conditions.in(table.column(idColumnName), markers)).build(); - - GenericExecuteSpec executeSpec = databaseClient.execute().sql(SqlRenderer.toString(select)); + PreparedOperation select = statements.select("foo", Arrays.asList("bar", "baz"), it -> {}); + + assertThat(select.getSource()).isInstanceOf(Select.class); + assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo"); + + select.bind(statementMock); + verifyZeroInteractions(statementMock); + } + + @Test + public void shouldToQuerySimpleSelectWithSimpleFilter() { + + PreparedOperation select = statements.select("foo", Arrays.asList("bar", "baz"), it -> { + it.filterBy("doe", SettableValue.from("John")); + it.filterBy("baz", SettableValue.from("Jake")); + }); + + assertThat(select.getSource()).isInstanceOf(Select.class); + assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); + + select.bind(statementMock); + verify(statementMock).bind(0, "John"); + verify(statementMock).bind(1, "Jake"); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldToQuerySimpleSelectWithNullFilter() { + + PreparedOperation select = statements.select("foo", Arrays.asList("bar", "baz"), it -> { + it.filterBy("doe", SettableValue.from(Arrays.asList("John", "Jake"))); + }); + + assertThat(select.getSource()).isInstanceOf(Select.class); + assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IN ($1, $2)"); + + select.bind(statementMock); + verify(statementMock).bind(0, "John"); + verify(statementMock).bind(1, "Jake"); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldFailInsertToQueryingWithoutValueBindings() { + + assertThatThrownBy(() -> statements.insert("foo", Collections.emptyList(), it -> {})) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void shouldToQuerySimpleInsert() { + + PreparedOperation insert = statements.insert("foo", Collections.emptyList(), it -> { + it.bind("bar", SettableValue.from("Foo")); + }); + + assertThat(insert.getSource()).isInstanceOf(Insert.class); + assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES($1)"); + + insert.bind(statementMock); + verify(statementMock).bind(0, "Foo"); + verify(statementMock).returnGeneratedValues(any(String[].class)); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldFailUpdateToQueryingWithoutValueBindings() { + + assertThatThrownBy(() -> statements.update("foo", it -> it.filterBy("foo", SettableValue.empty(Object.class)))) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void shouldToQuerySimpleUpdate() { + + PreparedOperation update = statements.update("foo", it -> { + it.bind("bar", SettableValue.from("Foo")); + }); + + assertThat(update.getSource()).isInstanceOf(Update.class); + assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); + + update.bind(statementMock); + verify(statementMock).bind(0, "Foo"); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldToQueryNullUpdate() { + + PreparedOperation update = statements.update("foo", it -> { + it.bind("bar", SettableValue.empty(String.class)); + }); + + assertThat(update.getSource()).isInstanceOf(Update.class); + assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); + + update.bind(statementMock); + verify(statementMock).bindNull(0, String.class); + + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldToQueryUpdateWithFilter() { + + PreparedOperation update = statements.update("foo", it -> { + it.bind("bar", SettableValue.from("Foo")); + it.filterBy("baz", SettableValue.from("Baz")); + }); + + assertThat(update.getSource()).isInstanceOf(Update.class); + assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1 WHERE foo.baz = $2"); + + update.bind(statementMock); + verify(statementMock).bind(0, "Foo"); + verify(statementMock).bind(1, "Baz"); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldToQuerySimpleDeleteWithSimpleFilter() { + + PreparedOperation delete = statements.delete("foo", it -> { + it.filterBy("doe", SettableValue.from("John")); + }); + + assertThat(delete.getSource()).isInstanceOf(Delete.class); + assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1"); + + delete.bind(statementMock); + verify(statementMock).bind(0, "John"); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldToQuerySimpleDeleteWithMultipleFilters() { + + PreparedOperation delete = statements.delete("foo", it -> { + it.filterBy("doe", SettableValue.from("John")); + it.filterBy("baz", SettableValue.from("Jake")); + }); + + assertThat(delete.getSource()).isInstanceOf(Delete.class); + assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); + + delete.bind(statementMock); + verify(statementMock).bind(0, "John"); + verify(statementMock).bind(1, "Jake"); + verifyNoMoreInteractions(statementMock); + } + + @Test + public void shouldToQuerySimpleDeleteWithNullFilter() { + + PreparedOperation delete = statements.delete("foo", it -> { + it.filterBy("doe", SettableValue.empty(String.class)); + }); + + assertThat(delete.getSource()).isInstanceOf(Delete.class); + assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe IS NULL"); + + delete.bind(statementMock); + verifyZeroInteractions(statementMock); + } +} From 8dce8ad9e6fbdbfb1f4a1417e01dafd7c5970c65 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 22 Mar 2019 11:18:43 +0200 Subject: [PATCH 03/22] #73 - Polishing. Switch license header URLs to HTTPS. Rebase onto master. --- .../DefaultReactiveDataAccessStrategy.java | 21 ++++++++++++++++++- .../function/DefaultStatementFactory.java | 2 +- .../r2dbc/function/PreparedOperation.java | 2 +- .../data/r2dbc/function/StatementFactory.java | 2 +- .../r2dbc/support/StatementRenderUtil.java | 1 + .../function/StatementFactoryUnitTests.java | 11 ++++++---- 6 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java index 76f17940..17f3ddbd 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultReactiveDataAccessStrategy.java @@ -25,6 +25,7 @@ import java.util.OptionalLong; import java.util.Set; import java.util.function.BiFunction; +import java.util.function.Function; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.convert.CustomConversions.StoreConversions; @@ -47,12 +48,14 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.Expression; import org.springframework.data.relational.core.sql.OrderByField; +import org.springframework.data.relational.core.sql.Select; import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndOrderBy; import org.springframework.data.relational.core.sql.StatementBuilder; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.relational.core.sql.render.NamingStrategies; import org.springframework.data.relational.core.sql.render.RenderContext; import org.springframework.data.relational.core.sql.render.RenderNamingStrategy; +import org.springframework.data.relational.core.sql.render.SelectRenderContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -121,6 +124,21 @@ public DefaultReactiveDataAccessStrategy(Dialect dialect, R2dbcConverter convert public RenderNamingStrategy getNamingStrategy() { return NamingStrategies.asIs(); } + + @Override + public SelectRenderContext getSelect() { + return new SelectRenderContext() { + @Override + public Function afterSelectList() { + return it -> ""; + } + + @Override + public Function afterOrderBy(boolean hasOrderBy) { + return it -> ""; + } + }; + } }; this.statements = new DefaultStatementFactory(this.dialect, renderContext); @@ -238,7 +256,7 @@ public String getTableName(Class type) { return getRequiredPersistentEntity(type).getTableName(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getStatements() */ @@ -295,6 +313,7 @@ public String select(String tableName, Set columns, Sort sort, Pageable offset = OptionalLong.of(page.getOffset()); } + // See https://github.com/spring-projects/spring-data-r2dbc/issues/55 return StatementRenderUtil.render(selectBuilder.build(), limit, offset, this.dialect); } diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 06b9cc94..6028fd18 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 9a394ac5..865ab261 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java index 235af87b..21380ed3 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/src/main/java/org/springframework/data/r2dbc/support/StatementRenderUtil.java b/src/main/java/org/springframework/data/r2dbc/support/StatementRenderUtil.java index ae8c7dd8..b0fb55ed 100644 --- a/src/main/java/org/springframework/data/r2dbc/support/StatementRenderUtil.java +++ b/src/main/java/org/springframework/data/r2dbc/support/StatementRenderUtil.java @@ -43,6 +43,7 @@ public static String render(Select select, OptionalLong limit, OptionalLong offs String sql = SqlRenderer.toString(select); // TODO: Replace with proper {@link Dialect} rendering for limit/offset. + // See https://github.com/spring-projects/spring-data-r2dbc/issues/55 if (limit.isPresent()) { LimitClause limitClause = dialect.limit(); diff --git a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java index aed49d36..b6587458 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -27,11 +27,11 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.domain.SettableValue; +import org.springframework.data.relational.core.dialect.RenderContextFactory; import org.springframework.data.relational.core.sql.Delete; import org.springframework.data.relational.core.sql.Insert; import org.springframework.data.relational.core.sql.Select; import org.springframework.data.relational.core.sql.Update; -import org.springframework.data.relational.core.sql.render.NamingStrategies; /** * Unit tests for {@link StatementFactory}. @@ -40,7 +40,10 @@ */ public class StatementFactoryUnitTests { - DefaultStatementFactory statements = new DefaultStatementFactory(PostgresDialect.INSTANCE, NamingStrategies::asIs); + // See https://github.com/spring-projects/spring-data-r2dbc/issues/55 + DefaultStatementFactory statements = new DefaultStatementFactory(PostgresDialect.INSTANCE, + new RenderContextFactory(org.springframework.data.relational.core.dialect.PostgresDialect.INSTANCE) + .createRenderContext()); Statement statementMock = mock(Statement.class); @@ -133,7 +136,7 @@ public void shouldToQuerySimpleInsert() { }); assertThat(insert.getSource()).isInstanceOf(Insert.class); - assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES($1)"); + assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES ($1)"); insert.bind(statementMock); verify(statementMock).bind(0, "Foo"); From 71fea91d1390b5a10a34070e99ad1a5d55368a1f Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 09:58:38 +0200 Subject: [PATCH 04/22] #73 - hacking - introduced alternative bind method. --- .../data/r2dbc/function/DefaultDatabaseClient.java | 11 +---------- .../data/r2dbc/function/DefaultStatementFactory.java | 12 ++++++++++++ .../data/r2dbc/function/PreparedOperation.java | 3 +++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index d706cb2b..64053757 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -1031,16 +1031,7 @@ private FetchSpec exchange(Object toInsert, BiFunction insertFunction = it -> { - - if (logger.isDebugEnabled()) { - logger.debug("Executing SQL statement [" + sql + "]"); - } - - return operation.bind(it.createStatement(sql)); - }; - - Function> resultFunction = it -> Flux.from(insertFunction.apply(it).execute()); + Function> resultFunction = it -> Flux.from(operation.bind(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // sql, // diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 6028fd18..776030d3 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.data.r2dbc.function; +import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -470,5 +471,16 @@ public Statement bind(Statement to) { binding.apply(to); return to; } + + @Override + public Statement bind(Connection connection) { + + // TODO add logging +// if (logger.isDebugEnabled()) { +// logger.debug("Executing SQL statement [" + sql + "]"); +// } + + return bind(connection.createStatement(toQuery())); + } } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 865ab261..5ec30ed9 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -15,6 +15,7 @@ */ package org.springframework.data.r2dbc.function; +import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; import java.util.function.Supplier; @@ -45,4 +46,6 @@ public interface PreparedOperation extends QueryOperation { * @return the bound statement. */ Statement bind(Statement to); + + Statement bind(Connection connection); } From f5d77917a6e8dd07668b0b538ff030c7b89f5027 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 10:13:55 +0200 Subject: [PATCH 05/22] #73 - hacking - removed call to old bind. --- .../data/r2dbc/function/DefaultDatabaseClient.java | 14 ++------------ .../r2dbc/function/DefaultStatementFactory.java | 2 +- .../data/r2dbc/function/PreparedOperation.java | 1 + 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index 64053757..1f135e05 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -908,20 +908,10 @@ private FetchSpec exchange(BiFunction mappingFunctio byName.forEach(it::bind); }); - String sql = operation.toQuery(); - Function insertFunction = it -> { - - if (logger.isDebugEnabled()) { - logger.debug("Executing SQL statement [" + sql + "]"); - } - - return operation.bind(it.createStatement(sql)); - }; - - Function> resultFunction = it -> Flux.from(insertFunction.apply(it).execute()); + Function> resultFunction = it -> Flux.from(operation.bind(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // - sql, // + operation.toQuery(), // resultFunction, // it -> resultFunction.apply(it).flatMap(Result::getRowsUpdated).next(), // mappingFunction); diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 776030d3..797e9afa 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -475,7 +475,7 @@ public Statement bind(Statement to) { @Override public Statement bind(Connection connection) { - // TODO add logging + // TODO add back logging // if (logger.isDebugEnabled()) { // logger.debug("Executing SQL statement [" + sql + "]"); // } diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 5ec30ed9..4e1d676f 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -45,6 +45,7 @@ public interface PreparedOperation extends QueryOperation { * @param to the target statement to bind parameters to. * @return the bound statement. */ + @Deprecated Statement bind(Statement to); Statement bind(Connection connection); From 3e558ba60b9d0c2de3451733783e028f9255444e Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 10:15:46 +0200 Subject: [PATCH 06/22] #73 - hacking - code symmetry. --- .../data/r2dbc/function/DefaultDatabaseClient.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index 1f135e05..6c6cf6df 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -1019,12 +1019,10 @@ private FetchSpec exchange(Object toInsert, BiFunction> resultFunction = it -> Flux.from(operation.bind(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // - sql, // + operation.toQuery(), // resultFunction, // it -> resultFunction // .apply(it) // From 7030d1c6b40fc2a950c5b84598e255fd258a7e87 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 10:47:11 +0200 Subject: [PATCH 07/22] #73 - hacking - extracted ParameterbindingPreparedOperation. --- .../r2dbc/function/DefaultDatabaseClient.java | 13 ++- .../PrameterbindingPreparedOperation.java | 98 +++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index 6c6cf6df..63ddf3e4 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -332,6 +332,17 @@ protected String getSql() { FetchSpec exchange(String sql, BiFunction mappingFunction) { + PreparedOperation pop; + + if (sqlSupplier instanceof PreparedOperation) { + pop = ((PreparedOperation) sqlSupplier); + } else { + pop = new PrameterbindingPreparedOperation(namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), + new MapBindParameterSource(byName)), byName, byIndex); + } + + + Function executeFunction = it -> { if (logger.isDebugEnabled()) { @@ -365,7 +376,7 @@ FetchSpec exchange(String sql, BiFunction mappingFun return statement; }; - Function> resultFunction = it -> Flux.from(executeFunction.apply(it).execute()); + Function> resultFunction = it -> Flux.from(pop.bind(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // sql, // diff --git a/src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java new file mode 100644 index 00000000..1469c51a --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 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.data.r2dbc.function; + +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.Statement; +import org.springframework.data.r2dbc.domain.SettableValue; + +import java.util.Map; + +/** + * @author Jens Schauder + */ +public class PrameterbindingPreparedOperation implements PreparedOperation { + + private final BindableOperation operation; + private final Map byName; + private final Map byIndex; + + public PrameterbindingPreparedOperation(BindableOperation operation, Map byName, Map byIndex) { + + this.operation = operation; + this.byName = byName; + this.byIndex = byIndex; + } + + @Override + public BindableOperation getSource() { + return operation; + } + + @Override + public Statement bind(Statement to) { + return null; + } + + @Override + public Statement bind(Connection connection) { + + Statement statement = connection.createStatement(operation.toQuery()); + + byName.forEach((name, o) -> { + + if (o.getValue() != null) { + operation.bind(statement, name, o.getValue()); + } else { + operation.bindNull(statement, name, o.getType()); + } + }); + + bindByIndex(statement, byIndex); + + return statement; } + + @Override + public String toQuery() { + return null; + } + + + + private static void bindByName(Statement statement, Map byName) { + + byName.forEach((name, o) -> { + + if (o.getValue() != null) { + statement.bind(name, o.getValue()); + } else { + statement.bindNull(name, o.getType()); + } + }); + } + + private static void bindByIndex(Statement statement, Map byIndex) { + + byIndex.forEach((i, o) -> { + + if (o.getValue() != null) { + statement.bind(i.intValue(), o.getValue()); + } else { + statement.bindNull(i.intValue(), o.getType()); + } + }); + } +} From 605e52eb6eb865cdfcb85e83c1f167ec378e70a7 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 11:00:55 +0200 Subject: [PATCH 08/22] #73 - hacking - minor code improvements. --- .../r2dbc/function/DefaultDatabaseClient.java | 38 +------------------ ...=> ParameterbindingPreparedOperation.java} | 28 ++++++++++---- 2 files changed, 21 insertions(+), 45 deletions(-) rename src/main/java/org/springframework/data/r2dbc/function/{PrameterbindingPreparedOperation.java => ParameterbindingPreparedOperation.java} (74%) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index 63ddf3e4..8bb1263b 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -337,45 +337,9 @@ FetchSpec exchange(String sql, BiFunction mappingFun if (sqlSupplier instanceof PreparedOperation) { pop = ((PreparedOperation) sqlSupplier); } else { - pop = new PrameterbindingPreparedOperation(namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), - new MapBindParameterSource(byName)), byName, byIndex); + pop = new ParameterbindingPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex); } - - - Function executeFunction = it -> { - - if (logger.isDebugEnabled()) { - logger.debug("Executing SQL statement [" + sql + "]"); - } - - if (sqlSupplier instanceof PreparedOperation) { - return ((PreparedOperation) sqlSupplier).bind(it.createStatement(sql)); - } - - BindableOperation operation = namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), - new MapBindParameterSource(byName)); - - if (logger.isTraceEnabled()) { - logger.trace("Expanded SQL [" + operation.toQuery() + "]"); - } - - Statement statement = it.createStatement(operation.toQuery()); - - byName.forEach((name, o) -> { - - if (o.getValue() != null) { - operation.bind(statement, name, o.getValue()); - } else { - operation.bindNull(statement, name, o.getType()); - } - }); - - bindByIndex(statement, byIndex); - - return statement; - }; - Function> resultFunction = it -> Flux.from(pop.bind(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // diff --git a/src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java similarity index 74% rename from src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java rename to src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java index 1469c51a..e44a6b91 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PrameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java @@ -17,26 +17,39 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; -import org.springframework.data.r2dbc.domain.SettableValue; import java.util.Map; +import org.springframework.data.r2dbc.domain.SettableValue; + /** * @author Jens Schauder */ -public class PrameterbindingPreparedOperation implements PreparedOperation { +public class ParameterbindingPreparedOperation implements PreparedOperation { private final BindableOperation operation; private final Map byName; private final Map byIndex; - public PrameterbindingPreparedOperation(BindableOperation operation, Map byName, Map byIndex) { + private ParameterbindingPreparedOperation(BindableOperation operation, Map byName, + Map byIndex) { this.operation = operation; this.byName = byName; this.byIndex = byIndex; } + ParameterbindingPreparedOperation(String sql, NamedParameterExpander namedParameters, + ReactiveDataAccessStrategy dataAccessStrategy, Map byName, + Map byIndex) { + + this( // + namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), new MapBindParameterSource(byName)), // + byName, // + byIndex // + ); + } + @Override public BindableOperation getSource() { return operation; @@ -44,7 +57,7 @@ public BindableOperation getSource() { @Override public Statement bind(Statement to) { - return null; + throw new UnsupportedOperationException("we don't do that here"); } @Override @@ -63,15 +76,14 @@ public Statement bind(Connection connection) { bindByIndex(statement, byIndex); - return statement; } + return statement; + } @Override public String toQuery() { - return null; + return operation.toQuery(); } - - private static void bindByName(Statement statement, Map byName) { byName.forEach((name, o) -> { From 58bd298320e384738f2ba29d3076db1ba77fbe9a Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 11:08:55 +0200 Subject: [PATCH 09/22] #73 - hacking - minor code improvements. --- .../ParameterbindingPreparedOperation.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java index e44a6b91..c087f105 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java @@ -65,15 +65,7 @@ public Statement bind(Connection connection) { Statement statement = connection.createStatement(operation.toQuery()); - byName.forEach((name, o) -> { - - if (o.getValue() != null) { - operation.bind(statement, name, o.getValue()); - } else { - operation.bindNull(statement, name, o.getType()); - } - }); - + bindByName(statement, byName); bindByIndex(statement, byIndex); return statement; @@ -84,14 +76,15 @@ public String toQuery() { return operation.toQuery(); } - private static void bindByName(Statement statement, Map byName) { + // todo that is a weird assymmetry between bindByName and bindByIndex + private void bindByName(Statement statement, Map byName) { byName.forEach((name, o) -> { if (o.getValue() != null) { - statement.bind(name, o.getValue()); + operation.bind(statement,name, o.getValue()); } else { - statement.bindNull(name, o.getType()); + operation.bindNull(statement, name, o.getType()); } }); } From a158c6bf57d5ad81a0daa71227c5030dfacee3aa Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 13:04:34 +0200 Subject: [PATCH 10/22] #73 - hacking - disassembling. --- .../data/r2dbc/function/DefaultStatementFactory.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 797e9afa..21bb1150 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -480,7 +480,12 @@ public Statement bind(Connection connection) { // logger.debug("Executing SQL statement [" + sql + "]"); // } - return bind(connection.createStatement(toQuery())); + Statement statement = connection.createStatement(toQuery()); + + // TODO there is too much binding going on. + // this looks silly and is by no means easy to understand (i.e. I don't) + binding.apply(statement); + return bind(statement); } } } From 565faf572548750254a421b6224e49ddecce5472 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 30 Apr 2019 13:16:33 +0200 Subject: [PATCH 11/22] #73 - hacking - removed old bind method from interface. --- .../function/DefaultStatementFactory.java | 10 ++---- .../ParameterbindingPreparedOperation.java | 5 --- .../r2dbc/function/PreparedOperation.java | 5 +-- .../function/StatementFactoryUnitTests.java | 33 ++++++++++++------- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 21bb1150..3b137e1f 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -465,8 +465,7 @@ public String toQuery() { * (non-Javadoc) * @see org.springframework.data.r2dbc.function.PreparedOperation#bind(io.r2dbc.spi.Statement) */ - @Override - public Statement bind(Statement to) { + protected Statement bind(Statement to) { binding.apply(to); return to; @@ -480,12 +479,7 @@ public Statement bind(Connection connection) { // logger.debug("Executing SQL statement [" + sql + "]"); // } - Statement statement = connection.createStatement(toQuery()); - - // TODO there is too much binding going on. - // this looks silly and is by no means easy to understand (i.e. I don't) - binding.apply(statement); - return bind(statement); + return bind(connection.createStatement(toQuery())); } } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java index c087f105..613303df 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java @@ -55,11 +55,6 @@ public BindableOperation getSource() { return operation; } - @Override - public Statement bind(Statement to) { - throw new UnsupportedOperationException("we don't do that here"); - } - @Override public Statement bind(Connection connection) { diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 4e1d676f..50ff1477 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -42,11 +42,8 @@ public interface PreparedOperation extends QueryOperation { /** * Bind query parameters to a {@link Statement}. * - * @param to the target statement to bind parameters to. + * @param connection the {@link Connection} used for constructing a statement * @return the bound statement. */ - @Deprecated - Statement bind(Statement to); - Statement bind(Connection connection); } diff --git a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java index b6587458..38e12004 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; import java.util.Arrays; @@ -46,6 +47,11 @@ public class StatementFactoryUnitTests { .createRenderContext()); Statement statementMock = mock(Statement.class); + Connection connectionMock = mock(Connection.class); + + { + when(connectionMock.createStatement(anyString())).thenReturn(statementMock); + } @Test public void shouldToQuerySimpleSelectWithoutBindings() { @@ -55,7 +61,8 @@ public void shouldToQuerySimpleSelectWithoutBindings() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo"); - select.bind(statementMock); + select.bind(connectionMock); + verifyZeroInteractions(statementMock); } @@ -69,7 +76,8 @@ public void shouldToQuerySimpleSelectWithSimpleFilter() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1"); - select.bind(statementMock); + select.bind(connectionMock); + verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); } @@ -85,7 +93,8 @@ public void shouldToQuerySimpleSelectWithMultipleFilters() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); - select.bind(statementMock); + select.bind(connectionMock); + verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -101,7 +110,7 @@ public void shouldToQuerySimpleSelectWithNullFilter() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IS NULL"); - select.bind(statementMock); + select.bind(connectionMock); verifyZeroInteractions(statementMock); } @@ -115,7 +124,7 @@ public void shouldToQuerySimpleSelectWithIterableFilter() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IN ($1, $2)"); - select.bind(statementMock); + select.bind(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -138,7 +147,7 @@ public void shouldToQuerySimpleInsert() { assertThat(insert.getSource()).isInstanceOf(Insert.class); assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES ($1)"); - insert.bind(statementMock); + insert.bind(connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).returnGeneratedValues(any(String[].class)); verifyNoMoreInteractions(statementMock); @@ -161,7 +170,7 @@ public void shouldToQuerySimpleUpdate() { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.bind(statementMock); + update.bind(connectionMock); verify(statementMock).bind(0, "Foo"); verifyNoMoreInteractions(statementMock); } @@ -176,7 +185,7 @@ public void shouldToQueryNullUpdate() { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.bind(statementMock); + update.bind(connectionMock); verify(statementMock).bindNull(0, String.class); verifyNoMoreInteractions(statementMock); @@ -193,7 +202,7 @@ public void shouldToQueryUpdateWithFilter() { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1 WHERE foo.baz = $2"); - update.bind(statementMock); + update.bind(connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).bind(1, "Baz"); verifyNoMoreInteractions(statementMock); @@ -209,7 +218,7 @@ public void shouldToQuerySimpleDeleteWithSimpleFilter() { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1"); - delete.bind(statementMock); + delete.bind(connectionMock); verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); } @@ -225,7 +234,7 @@ public void shouldToQuerySimpleDeleteWithMultipleFilters() { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); - delete.bind(statementMock); + delete.bind(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -241,7 +250,7 @@ public void shouldToQuerySimpleDeleteWithNullFilter() { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe IS NULL"); - delete.bind(statementMock); + delete.bind(connectionMock); verifyZeroInteractions(statementMock); } } From e13fcd7ff37fec88222b9732b7c557826ab3ef5a Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 2 May 2019 11:05:42 +0200 Subject: [PATCH 12/22] #73 - hacking - changed method name according to review. --- .../r2dbc/function/DefaultDatabaseClient.java | 6 ++--- .../function/DefaultStatementFactory.java | 2 +- .../ParameterbindingPreparedOperation.java | 2 +- .../r2dbc/function/PreparedOperation.java | 2 +- .../function/StatementFactoryUnitTests.java | 24 +++++++++---------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index 8bb1263b..be7d0176 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -340,7 +340,7 @@ FetchSpec exchange(String sql, BiFunction mappingFun pop = new ParameterbindingPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex); } - Function> resultFunction = it -> Flux.from(pop.bind(it).execute()); + Function> resultFunction = it -> Flux.from(pop.createBoundStatement(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // sql, // @@ -883,7 +883,7 @@ private FetchSpec exchange(BiFunction mappingFunctio byName.forEach(it::bind); }); - Function> resultFunction = it -> Flux.from(operation.bind(it).execute()); + Function> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // operation.toQuery(), // @@ -994,7 +994,7 @@ private FetchSpec exchange(Object toInsert, BiFunction> resultFunction = it -> Flux.from(operation.bind(it).execute()); + Function> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // operation.toQuery(), // diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 3b137e1f..774686be 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -472,7 +472,7 @@ protected Statement bind(Statement to) { } @Override - public Statement bind(Connection connection) { + public Statement createBoundStatement(Connection connection) { // TODO add back logging // if (logger.isDebugEnabled()) { diff --git a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java index 613303df..21787fad 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java @@ -56,7 +56,7 @@ public BindableOperation getSource() { } @Override - public Statement bind(Connection connection) { + public Statement createBoundStatement(Connection connection) { Statement statement = connection.createStatement(operation.toQuery()); diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 50ff1477..8aa717a1 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -45,5 +45,5 @@ public interface PreparedOperation extends QueryOperation { * @param connection the {@link Connection} used for constructing a statement * @return the bound statement. */ - Statement bind(Connection connection); + Statement createBoundStatement(Connection connection); } diff --git a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java index 38e12004..a873e807 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java @@ -61,7 +61,7 @@ public void shouldToQuerySimpleSelectWithoutBindings() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo"); - select.bind(connectionMock); + select.createBoundStatement(connectionMock); verifyZeroInteractions(statementMock); } @@ -76,7 +76,7 @@ public void shouldToQuerySimpleSelectWithSimpleFilter() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1"); - select.bind(connectionMock); + select.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); @@ -93,7 +93,7 @@ public void shouldToQuerySimpleSelectWithMultipleFilters() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); - select.bind(connectionMock); + select.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); @@ -110,7 +110,7 @@ public void shouldToQuerySimpleSelectWithNullFilter() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IS NULL"); - select.bind(connectionMock); + select.createBoundStatement(connectionMock); verifyZeroInteractions(statementMock); } @@ -124,7 +124,7 @@ public void shouldToQuerySimpleSelectWithIterableFilter() { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IN ($1, $2)"); - select.bind(connectionMock); + select.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -147,7 +147,7 @@ public void shouldToQuerySimpleInsert() { assertThat(insert.getSource()).isInstanceOf(Insert.class); assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES ($1)"); - insert.bind(connectionMock); + insert.createBoundStatement(connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).returnGeneratedValues(any(String[].class)); verifyNoMoreInteractions(statementMock); @@ -170,7 +170,7 @@ public void shouldToQuerySimpleUpdate() { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.bind(connectionMock); + update.createBoundStatement(connectionMock); verify(statementMock).bind(0, "Foo"); verifyNoMoreInteractions(statementMock); } @@ -185,7 +185,7 @@ public void shouldToQueryNullUpdate() { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.bind(connectionMock); + update.createBoundStatement(connectionMock); verify(statementMock).bindNull(0, String.class); verifyNoMoreInteractions(statementMock); @@ -202,7 +202,7 @@ public void shouldToQueryUpdateWithFilter() { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1 WHERE foo.baz = $2"); - update.bind(connectionMock); + update.createBoundStatement(connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).bind(1, "Baz"); verifyNoMoreInteractions(statementMock); @@ -218,7 +218,7 @@ public void shouldToQuerySimpleDeleteWithSimpleFilter() { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1"); - delete.bind(connectionMock); + delete.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); } @@ -234,7 +234,7 @@ public void shouldToQuerySimpleDeleteWithMultipleFilters() { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); - delete.bind(connectionMock); + delete.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -250,7 +250,7 @@ public void shouldToQuerySimpleDeleteWithNullFilter() { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe IS NULL"); - delete.bind(connectionMock); + delete.createBoundStatement(connectionMock); verifyZeroInteractions(statementMock); } } From 9b3294333abe51c364914d70d64de51fea16c214 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 2 May 2019 13:48:19 +0200 Subject: [PATCH 13/22] #73 - hacking - extracted super class. --- .../function/DefaultStatementFactory.java | 94 ++++++++++++++----- .../ParameterbindingPreparedOperation.java | 11 +++ .../r2dbc/function/PreparedOperation.java | 8 +- 3 files changed, 87 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 774686be..363ff4d3 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -17,6 +17,7 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; +import kotlin.internal.LowPriorityInOverloadResolution; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -29,6 +30,7 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.r2dbc.dialect.BindMarker; @@ -362,6 +364,7 @@ private static Condition toCondition(BindMarkers bindMarkers, Column column, Set } } + /** * Value object holding value and {@code NULL} bindings. * @@ -412,13 +415,75 @@ void apply(Statement to) { } } + + static abstract class PreparedOperationSupport implements PreparedOperation { + + private Function sqlFilter = s -> s; + private Function bindingFilter = b -> b; + + + abstract protected String createBaseSql(); + + protected abstract Binding getBaseBinding(); + + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() + */ + @Override + public String toQuery() { + + return sqlFilter.apply(createBaseSql()); + } + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.function.PreparedOperation#bind(io.r2dbc.spi.Statement) + */ + protected Statement bind(Statement to) { + + bindingFilter.apply(getBaseBinding()).apply(to); + return to; + } + + + @Override + public Statement createBoundStatement(Connection connection) { + + // TODO add back logging +// if (logger.isDebugEnabled()) { +// logger.debug("Executing SQL statement [" + sql + "]"); +// } + + return bind(connection.createStatement(toQuery())); + } + + @Override + public void addSqlFilter(Function filter) { + + Assert.notNull(filter, "Filter must not be null."); + + sqlFilter = filter; + + } + + @Override + public void addBindingFilter(Function filter) { + + Assert.notNull(filter, "Filter must not be null."); + + bindingFilter = filter; + } + + } + + /** * Default implementation of {@link PreparedOperation}. * * @param */ @RequiredArgsConstructor - static class DefaultPreparedOperation implements PreparedOperation { + static class DefaultPreparedOperation extends PreparedOperationSupport { private final T source; private final RenderContext renderContext; @@ -433,13 +498,8 @@ public T getSource() { return this.source; } - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() - */ @Override - public String toQuery() { - + protected String createBaseSql() { SqlRenderer sqlRenderer = SqlRenderer.create(renderContext); if (this.source instanceof Select) { @@ -461,25 +521,9 @@ public String toQuery() { throw new IllegalStateException("Cannot render " + this.getSource()); } - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.PreparedOperation#bind(io.r2dbc.spi.Statement) - */ - protected Statement bind(Statement to) { - - binding.apply(to); - return to; - } - @Override - public Statement createBoundStatement(Connection connection) { - - // TODO add back logging -// if (logger.isDebugEnabled()) { -// logger.debug("Executing SQL statement [" + sql + "]"); -// } - - return bind(connection.createStatement(toQuery())); + protected Binding getBaseBinding() { + return binding; } } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java index 21787fad..c8dfa5dd 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java @@ -19,6 +19,7 @@ import io.r2dbc.spi.Statement; import java.util.Map; +import java.util.function.Function; import org.springframework.data.r2dbc.domain.SettableValue; @@ -66,6 +67,16 @@ public Statement createBoundStatement(Connection connection) { return statement; } + @Override + public void addSqlFilter(Function filter) { + + } + + @Override + public void addBindingFilter(Function filter) { + + } + @Override public String toQuery() { return operation.toQuery(); diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 8aa717a1..7b21de0a 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -18,6 +18,7 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -40,10 +41,15 @@ public interface PreparedOperation extends QueryOperation { T getSource(); /** - * Bind query parameters to a {@link Statement}. + * create a {@link Statement} from the generated SQL after applying the SQL filter and then applying the + * {@link org.springframework.data.r2dbc.function.DefaultStatementFactory.Binding} after filtering those as well. * * @param connection the {@link Connection} used for constructing a statement * @return the bound statement. */ Statement createBoundStatement(Connection connection); + + void addSqlFilter(Function filter); + + void addBindingFilter(Function filter); } From 2804726607cb6550a0c092b0e7ae866b32c34920 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 3 May 2019 15:00:36 +0200 Subject: [PATCH 14/22] #73 - hacking - separate Bindings class. --- .../data/r2dbc/function/Bindings.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/org/springframework/data/r2dbc/function/Bindings.java diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java new file mode 100644 index 00000000..d7e2ecd2 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 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.data.r2dbc.function; + +import io.r2dbc.spi.Statement; +import lombok.Value; + +import org.springframework.data.r2dbc.domain.SettableValue; + +import java.util.List; + +/** + * @author Jens Schauder + */ +public class Bindings { + + private final List bindings; + + public Bindings(List bindings) { + this.bindings = bindings; + } + + + @Value + static public abstract class SingleBinding { + + T identifier; + SettableValue value; + + public abstract void bindTo(Statement statement); + + public boolean isIndexed() { + return (identifier instanceof Number); + } + + public boolean isNamed() { + return !isIndexed(); + } + } + +} From dcdd96842cb411f512e64ec6cfb6349344182e39 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 3 May 2019 15:09:23 +0200 Subject: [PATCH 15/22] #73 - hacking - prepared various single bindings. --- .../data/r2dbc/function/Bindings.java | 97 +++++++++++++++++-- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java index d7e2ecd2..77b6b161 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -18,10 +18,10 @@ import io.r2dbc.spi.Statement; import lombok.Value; -import org.springframework.data.r2dbc.domain.SettableValue; - import java.util.List; +import org.springframework.data.r2dbc.domain.SettableValue; + /** * @author Jens Schauder */ @@ -35,20 +35,105 @@ public Bindings(List bindings) { @Value - static public abstract class SingleBinding { + public static abstract class SingleBinding { T identifier; SettableValue value; public abstract void bindTo(Statement statement); + public abstract boolean isIndexed(); + + public final boolean isNamed() { + return !isIndexed(); + } + } + + public static class NamedSingleBinding extends SingleBinding{ + + public NamedSingleBinding(T identifier, SettableValue value) { + super(identifier, value); + } + + @Override + public void bindTo(Statement statement) { + + if (getValue().isEmpty()) { + statement.bindNull(getIdentifier(), getValue().getType()); + } + + statement.bind(getIdentifier(), getValue()); + } + + @Override public boolean isIndexed() { - return (identifier instanceof Number); + return false; } + } - public boolean isNamed() { - return !isIndexed(); + public static class IndexedSingleBinding extends SingleBinding{ + + public IndexedSingleBinding(Integer identifier, SettableValue value) { + super(identifier, value); + } + + @Override + public void bindTo(Statement statement) { + + if (getValue().isEmpty()) { + statement.bindNull((int)getIdentifier(), getValue().getType()); + } + + statement.bind((int)getIdentifier(), getValue()); + } + @Override + public boolean isIndexed() { + return true; + } + } + + public static class NamedExpandedSingleBinding extends SingleBinding{ + + public NamedExpandedSingleBinding(T identifier, SettableValue value) { + super(identifier, value); + } + + @Override + public void bindTo(Statement statement) { + + if (getValue().isEmpty()) { + statement.bindNull(getIdentifier(), getValue().getType()); + } + + statement.bind(getIdentifier(), getValue()); + } + + @Override + public boolean isIndexed() { + return false; } } + public static class IndexedExpandedSingleBinding extends SingleBinding{ + + public IndexedExpandedSingleBinding(Integer identifier, SettableValue value) { + super(identifier, value); + } + + @Override + public void bindTo(Statement statement) { + + if (getValue().isEmpty()) { + statement.bindNull((int)getIdentifier(), getValue().getType()); + } + + statement.bind((int)getIdentifier(), getValue()); + } + @Override + public boolean isIndexed() { + return true; + } + } + + } From 01d114dc6b966f17890515dc975e649cc6897738 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 3 May 2019 15:18:32 +0200 Subject: [PATCH 16/22] #73 - hacking - proper expanded single binding. --- .../data/r2dbc/function/Bindings.java | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java index 77b6b161..555ef146 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -94,46 +94,28 @@ public boolean isIndexed() { public static class NamedExpandedSingleBinding extends SingleBinding{ - public NamedExpandedSingleBinding(T identifier, SettableValue value) { - super(identifier, value); - } - - @Override - public void bindTo(Statement statement) { - - if (getValue().isEmpty()) { - statement.bindNull(getIdentifier(), getValue().getType()); - } - - statement.bind(getIdentifier(), getValue()); - } - - @Override - public boolean isIndexed() { - return false; - } - } + private final BindableOperation operation; - public static class IndexedExpandedSingleBinding extends SingleBinding{ + public NamedExpandedSingleBinding(T identifier, SettableValue value, BindableOperation operation) { - public IndexedExpandedSingleBinding(Integer identifier, SettableValue value) { super(identifier, value); + + this.operation = operation; } @Override public void bindTo(Statement statement) { - if (getValue().isEmpty()) { - statement.bindNull((int)getIdentifier(), getValue().getType()); + if (getValue() != null) { + operation.bind(statement,getIdentifier(), getValue()); + } else { + operation.bindNull(statement, getIdentifier(), getValue().getType()); } - - statement.bind((int)getIdentifier(), getValue()); } + @Override public boolean isIndexed() { - return true; + return false; } } - - } From 7b57ff815d402447bd2b4eb9a7f9a02e1093f21b Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 3 May 2019 16:06:09 +0200 Subject: [PATCH 17/22] #73 - hacking - always nice when code compiles. --- .../data/r2dbc/function/Bindings.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java index 555ef146..06baa919 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -16,6 +16,7 @@ package org.springframework.data.r2dbc.function; import io.r2dbc.spi.Statement; +import lombok.RequiredArgsConstructor; import lombok.Value; import java.util.List; @@ -33,23 +34,22 @@ public Bindings(List bindings) { this.bindings = bindings; } + @RequiredArgsConstructor + public static abstract class SingleBinding { - @Value - public static abstract class SingleBinding { - - T identifier; - SettableValue value; + final T identifier; + final SettableValue value; public abstract void bindTo(Statement statement); - public abstract boolean isIndexed(); + public abstract boolean isIndexed(); public final boolean isNamed() { return !isIndexed(); } } - public static class NamedSingleBinding extends SingleBinding{ + public static class NamedSingleBinding extends SingleBinding { public NamedSingleBinding(T identifier, SettableValue value) { super(identifier, value); @@ -58,11 +58,11 @@ public NamedSingleBinding(T identifier, SettableValue value) { @Override public void bindTo(Statement statement) { - if (getValue().isEmpty()) { - statement.bindNull(getIdentifier(), getValue().getType()); + if (value.isEmpty()) { + statement.bindNull(identifier, value.getType()); } - statement.bind(getIdentifier(), getValue()); + statement.bind(identifier, value); } @Override @@ -71,7 +71,7 @@ public boolean isIndexed() { } } - public static class IndexedSingleBinding extends SingleBinding{ + public static class IndexedSingleBinding extends SingleBinding { public IndexedSingleBinding(Integer identifier, SettableValue value) { super(identifier, value); @@ -80,23 +80,24 @@ public IndexedSingleBinding(Integer identifier, SettableValue value) { @Override public void bindTo(Statement statement) { - if (getValue().isEmpty()) { - statement.bindNull((int)getIdentifier(), getValue().getType()); + if (value.isEmpty()) { + statement.bindNull((int) identifier, value.getType()); } - statement.bind((int)getIdentifier(), getValue()); + statement.bind((int) identifier, value); } + @Override public boolean isIndexed() { return true; } } - public static class NamedExpandedSingleBinding extends SingleBinding{ + public static class NamedExpandedSingleBinding extends SingleBinding { private final BindableOperation operation; - public NamedExpandedSingleBinding(T identifier, SettableValue value, BindableOperation operation) { + public NamedExpandedSingleBinding(String identifier, SettableValue value, BindableOperation operation) { super(identifier, value); @@ -106,10 +107,10 @@ public NamedExpandedSingleBinding(T identifier, SettableValue value, BindableOpe @Override public void bindTo(Statement statement) { - if (getValue() != null) { - operation.bind(statement,getIdentifier(), getValue()); + if (value != null) { + operation.bind(statement, identifier, value); } else { - operation.bindNull(statement, getIdentifier(), getValue().getType()); + operation.bindNull(statement, identifier, value.getType()); } } From 71b81a18647ab20be847094c6e816d335112ab95 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 3 May 2019 16:46:32 +0200 Subject: [PATCH 18/22] #73 - hacking - fixed an error. --- .../data/r2dbc/dialect/IndexedBindMarker.java | 10 ++- .../data/r2dbc/function/Bindings.java | 9 +- .../function/DefaultStatementFactory.java | 85 +++++++++++-------- .../ParameterbindingPreparedOperation.java | 2 +- .../r2dbc/function/PreparedOperation.java | 2 +- 5 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarker.java b/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarker.java index b7d84d65..c54da6dd 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarker.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarker.java @@ -20,11 +20,11 @@ /** * A single indexed bind marker. */ -class IndexedBindMarker implements BindMarker { +public class IndexedBindMarker implements BindMarker { private final String placeholder; - private int index; + private final int index; IndexedBindMarker(String placeholder, int index) { this.placeholder = placeholder; @@ -57,4 +57,10 @@ public void bind(Statement statement, Object value) { public void bindNull(Statement statement, Class valueType) { statement.bindNull(this.index, valueType); } + + + public int getIndex() { + return index; + } + } diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java index 06baa919..a0083c5f 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -17,7 +17,6 @@ import io.r2dbc.spi.Statement; import lombok.RequiredArgsConstructor; -import lombok.Value; import java.util.List; @@ -34,6 +33,10 @@ public Bindings(List bindings) { this.bindings = bindings; } + public void apply(Statement statement) { + bindings.forEach(sb -> sb.bindTo(statement)); + } + @RequiredArgsConstructor public static abstract class SingleBinding { @@ -82,9 +85,9 @@ public void bindTo(Statement statement) { if (value.isEmpty()) { statement.bindNull((int) identifier, value.getType()); + } else { + statement.bind((int) identifier, value); } - - statement.bind((int) identifier, value); } @Override diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 363ff4d3..251f193a 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -17,7 +17,6 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; -import kotlin.internal.LowPriorityInOverloadResolution; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -32,26 +31,14 @@ import java.util.function.Consumer; import java.util.function.Function; +import org.jetbrains.annotations.NotNull; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.r2dbc.dialect.BindMarker; import org.springframework.data.r2dbc.dialect.BindMarkers; import org.springframework.data.r2dbc.dialect.Dialect; +import org.springframework.data.r2dbc.dialect.IndexedBindMarker; import org.springframework.data.r2dbc.domain.SettableValue; -import org.springframework.data.relational.core.sql.AssignValue; -import org.springframework.data.relational.core.sql.Assignment; -import org.springframework.data.relational.core.sql.Column; -import org.springframework.data.relational.core.sql.Condition; -import org.springframework.data.relational.core.sql.Delete; -import org.springframework.data.relational.core.sql.DeleteBuilder; -import org.springframework.data.relational.core.sql.Expression; -import org.springframework.data.relational.core.sql.Insert; -import org.springframework.data.relational.core.sql.SQL; -import org.springframework.data.relational.core.sql.Select; -import org.springframework.data.relational.core.sql.SelectBuilder; -import org.springframework.data.relational.core.sql.StatementBuilder; -import org.springframework.data.relational.core.sql.Table; -import org.springframework.data.relational.core.sql.Update; -import org.springframework.data.relational.core.sql.UpdateBuilder; +import org.springframework.data.relational.core.sql.*; import org.springframework.data.relational.core.sql.render.RenderContext; import org.springframework.data.relational.core.sql.render.SqlRenderer; import org.springframework.lang.Nullable; @@ -104,10 +91,44 @@ public void bind(String identifier, SettableValue settable) { select = selectBuilder.build(); } - return new DefaultPreparedOperation<>(select, renderContext, binding); + return new DefaultPreparedOperation<>( // + select, // + renderContext, // + createBindings(binding) // + ); }); } + @NotNull + private static Bindings createBindings(Binding binding) { + + List singleBindings = new ArrayList<>(); + + binding.getNulls().forEach( // + (bindMarker, settableValue) -> { + + if (bindMarker instanceof IndexedBindMarker) { + singleBindings // + .add(new Bindings.IndexedSingleBinding( // + ((IndexedBindMarker) bindMarker).getIndex(), // + settableValue) // + ); + } + }); + + binding.getValues().forEach( // + (bindMarker, value) -> { + singleBindings // + .add(new Bindings.NamedSingleBinding<>( // + bindMarker.getPlaceholder(), // + value instanceof SettableValue ? (SettableValue) value + : SettableValue.from(value)) // + ); + }); + + return new Bindings(singleBindings); + } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.function.StatementFactory#insert(java.lang.String, java.util.Collection, java.util.function.Consumer) @@ -152,7 +173,7 @@ public void filterBy(String identifier, SettableValue settable) { Insert insert = StatementBuilder.insert().into(table).columns(table.columns(binderBuilder.bindings.keySet())) .values(expressions).build(); - return new DefaultPreparedOperation(insert, renderContext, binding) { + return new DefaultPreparedOperation(insert, renderContext, createBindings(binding)) { @Override public Statement bind(Statement to) { return super.bind(to).returnGeneratedValues(generatedKeysNames.toArray(new String[0])); @@ -206,7 +227,7 @@ public PreparedOperation update(String tableName, Consumer(update, renderContext, binding); + return new DefaultPreparedOperation<>(update, renderContext, createBindings(binding)); }); } @@ -244,7 +265,7 @@ public void bind(String identifier, SettableValue settable) { delete = deleteBuilder.build(); } - return new DefaultPreparedOperation<>(delete, renderContext, binding); + return new DefaultPreparedOperation<>(delete, renderContext, createBindings(binding)); }); } @@ -364,7 +385,6 @@ private static Condition toCondition(BindMarkers bindMarkers, Column column, Set } } - /** * Value object holding value and {@code NULL} bindings. * @@ -415,16 +435,14 @@ void apply(Statement to) { } } - static abstract class PreparedOperationSupport implements PreparedOperation { private Function sqlFilter = s -> s; - private Function bindingFilter = b -> b; - + private Function bindingFilter = b -> b; abstract protected String createBaseSql(); - protected abstract Binding getBaseBinding(); + protected abstract Bindings getBaseBinding(); /* * (non-Javadoc) @@ -435,6 +453,7 @@ public String toQuery() { return sqlFilter.apply(createBaseSql()); } + /* * (non-Javadoc) * @see org.springframework.data.r2dbc.function.PreparedOperation#bind(io.r2dbc.spi.Statement) @@ -445,14 +464,13 @@ protected Statement bind(Statement to) { return to; } - @Override public Statement createBoundStatement(Connection connection) { // TODO add back logging -// if (logger.isDebugEnabled()) { -// logger.debug("Executing SQL statement [" + sql + "]"); -// } + // if (logger.isDebugEnabled()) { + // logger.debug("Executing SQL statement [" + sql + "]"); + // } return bind(connection.createStatement(toQuery())); } @@ -467,7 +485,7 @@ public void addSqlFilter(Function filter) { } @Override - public void addBindingFilter(Function filter) { + public void addBindingFilter(Function filter) { Assert.notNull(filter, "Filter must not be null."); @@ -476,7 +494,6 @@ public void addBindingFilter(Function filter) { } - /** * Default implementation of {@link PreparedOperation}. * @@ -487,7 +504,7 @@ static class DefaultPreparedOperation extends PreparedOperationSupport { private final T source; private final RenderContext renderContext; - private final Binding binding; + private final Bindings bindings; /* * (non-Javadoc) @@ -522,8 +539,8 @@ protected String createBaseSql() { } @Override - protected Binding getBaseBinding() { - return binding; + protected Bindings getBaseBinding() { + return bindings; } } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java index c8dfa5dd..0274b5f8 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java @@ -73,7 +73,7 @@ public void addSqlFilter(Function filter) { } @Override - public void addBindingFilter(Function filter) { + public void addBindingFilter(Function filter) { } diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java index 7b21de0a..4730884f 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -51,5 +51,5 @@ public interface PreparedOperation extends QueryOperation { void addSqlFilter(Function filter); - void addBindingFilter(Function filter); + void addBindingFilter(Function filter); } From 7f9c42c11bd4e74df8b69bcf5d6777e9fa0822ec Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 3 May 2019 16:49:14 +0200 Subject: [PATCH 19/22] #73 - hacking - fixed another error. --- .../data/r2dbc/function/Bindings.java | 5 +++-- .../r2dbc/function/DefaultStatementFactory.java | 15 ++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java index a0083c5f..9c5d9518 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -63,9 +63,10 @@ public void bindTo(Statement statement) { if (value.isEmpty()) { statement.bindNull(identifier, value.getType()); + } else { + statement.bind(identifier, value.getValue()); } - statement.bind(identifier, value); } @Override @@ -86,7 +87,7 @@ public void bindTo(Statement statement) { if (value.isEmpty()) { statement.bindNull((int) identifier, value.getType()); } else { - statement.bind((int) identifier, value); + statement.bind((int) identifier, value.getValue()); } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index 251f193a..a051fe31 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -112,18 +112,19 @@ private static Bindings createBindings(Binding binding) { .add(new Bindings.IndexedSingleBinding( // ((IndexedBindMarker) bindMarker).getIndex(), // settableValue) // - ); + ); } }); binding.getValues().forEach( // (bindMarker, value) -> { - singleBindings // - .add(new Bindings.NamedSingleBinding<>( // - bindMarker.getPlaceholder(), // - value instanceof SettableValue ? (SettableValue) value - : SettableValue.from(value)) // - ); + if (bindMarker instanceof IndexedBindMarker) { + singleBindings // + .add(new Bindings.IndexedSingleBinding( // + ((IndexedBindMarker) bindMarker).getIndex(), // + value instanceof SettableValue ? (SettableValue) value : SettableValue.from(value)) // + ); + } }); return new Bindings(singleBindings); From 13690ea698b70c262de50df7436805eddf943f08 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 6 May 2019 10:01:30 +0200 Subject: [PATCH 20/22] #73 - hacking - added backup. --- .../OldParameterbindingPreparedOperation.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java diff --git a/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java new file mode 100644 index 00000000..1ea99635 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java @@ -0,0 +1,109 @@ +/* + * Copyright 2019 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.data.r2dbc.function; + +import io.r2dbc.spi.Connection; +import io.r2dbc.spi.Statement; + +import java.util.Map; +import java.util.function.Function; + +import org.springframework.data.r2dbc.domain.SettableValue; + +/** + * @author Jens Schauder + */ +public class OldParameterbindingPreparedOperation implements PreparedOperation { + + private final BindableOperation operation; + private final Map byName; + private final Map byIndex; + + private OldParameterbindingPreparedOperation(BindableOperation operation, Map byName, + Map byIndex) { + + this.operation = operation; + this.byName = byName; + this.byIndex = byIndex; + } + + OldParameterbindingPreparedOperation(String sql, NamedParameterExpander namedParameters, + ReactiveDataAccessStrategy dataAccessStrategy, Map byName, + Map byIndex) { + + this( // + namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), new MapBindParameterSource(byName)), // + byName, // + byIndex // + ); + } + + @Override + public BindableOperation getSource() { + return operation; + } + + @Override + public Statement createBoundStatement(Connection connection) { + + Statement statement = connection.createStatement(operation.toQuery()); + + bindByName(statement, byName); + bindByIndex(statement, byIndex); + + return statement; + } + + @Override + public void addSqlFilter(Function filter) { + + } + + @Override + public void addBindingFilter(Function filter) { + + } + + @Override + public String toQuery() { + return operation.toQuery(); + } + + // todo that is a weird assymmetry between bindByName and bindByIndex + private void bindByName(Statement statement, Map byName) { + + byName.forEach((name, o) -> { + + if (o.getValue() != null) { + operation.bind(statement,name, o.getValue()); + } else { + operation.bindNull(statement, name, o.getType()); + } + }); + } + + private static void bindByIndex(Statement statement, Map byIndex) { + + byIndex.forEach((i, o) -> { + + if (o.getValue() != null) { + statement.bind(i.intValue(), o.getValue()); + } else { + statement.bindNull(i.intValue(), o.getType()); + } + }); + } +} From fa55f0e738392a914bf232f4f2eebb3f5a092ce0 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 6 May 2019 10:08:06 +0200 Subject: [PATCH 21/22] #73 - hacking - moved bindings in constructor. --- .../function/DefaultStatementFactory.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java index a051fe31..d9fcc61e 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -440,10 +440,18 @@ static abstract class PreparedOperationSupport implements PreparedOperation sqlFilter = s -> s; private Function bindingFilter = b -> b; + private final Bindings bindings; + + protected PreparedOperationSupport(Bindings bindings) { + + this.bindings = bindings; + } abstract protected String createBaseSql(); - protected abstract Bindings getBaseBinding(); + Bindings getBaseBinding() { + return bindings; + }; /* * (non-Javadoc) @@ -500,12 +508,18 @@ public void addBindingFilter(Function filter) { * * @param */ - @RequiredArgsConstructor static class DefaultPreparedOperation extends PreparedOperationSupport { private final T source; private final RenderContext renderContext; - private final Bindings bindings; + + DefaultPreparedOperation(T source, RenderContext renderContext, Bindings bindings) { + + super(bindings); + + this.source = source; + this.renderContext = renderContext; + } /* * (non-Javadoc) @@ -518,6 +532,7 @@ public T getSource() { @Override protected String createBaseSql() { + SqlRenderer sqlRenderer = SqlRenderer.create(renderContext); if (this.source instanceof Select) { @@ -539,9 +554,5 @@ protected String createBaseSql() { throw new IllegalStateException("Cannot render " + this.getSource()); } - @Override - protected Bindings getBaseBinding() { - return bindings; - } } } From 2265c7fecfd319da1335c750d2e11a75e5305a58 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 6 May 2019 10:40:13 +0200 Subject: [PATCH 22/22] #73 - hacking - ExpandedBindings work as well. --- .../data/r2dbc/function/Bindings.java | 22 --------- .../r2dbc/function/DefaultDatabaseClient.java | 2 +- ...on.java => ExpandedPreparedOperation.java} | 48 ++++++++++++------- 3 files changed, 31 insertions(+), 41 deletions(-) rename src/main/java/org/springframework/data/r2dbc/function/{ParameterbindingPreparedOperation.java => ExpandedPreparedOperation.java} (63%) diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java index 9c5d9518..a882156d 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -52,28 +52,6 @@ public final boolean isNamed() { } } - public static class NamedSingleBinding extends SingleBinding { - - public NamedSingleBinding(T identifier, SettableValue value) { - super(identifier, value); - } - - @Override - public void bindTo(Statement statement) { - - if (value.isEmpty()) { - statement.bindNull(identifier, value.getType()); - } else { - statement.bind(identifier, value.getValue()); - } - - } - - @Override - public boolean isIndexed() { - return false; - } - } public static class IndexedSingleBinding extends SingleBinding { diff --git a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index be7d0176..2b5cda4a 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -337,7 +337,7 @@ FetchSpec exchange(String sql, BiFunction mappingFun if (sqlSupplier instanceof PreparedOperation) { pop = ((PreparedOperation) sqlSupplier); } else { - pop = new ParameterbindingPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex); + pop = new ExpandedPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex); } Function> resultFunction = it -> Flux.from(pop.createBoundStatement(it).execute()); diff --git a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java similarity index 63% rename from src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java rename to src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java index 0274b5f8..a4d0e2bb 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/ParameterbindingPreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java @@ -18,32 +18,49 @@ import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.function.Function; import org.springframework.data.r2dbc.domain.SettableValue; /** * @author Jens Schauder */ -public class ParameterbindingPreparedOperation implements PreparedOperation { +public class ExpandedPreparedOperation + extends DefaultStatementFactory.PreparedOperationSupport { private final BindableOperation operation; private final Map byName; private final Map byIndex; - private ParameterbindingPreparedOperation(BindableOperation operation, Map byName, - Map byIndex) { + private ExpandedPreparedOperation(BindableOperation operation, Map byName, + Map byIndex) { + + super(createBindings(operation, byName, byIndex)); this.operation = operation; this.byName = byName; this.byIndex = byIndex; } - ParameterbindingPreparedOperation(String sql, NamedParameterExpander namedParameters, - ReactiveDataAccessStrategy dataAccessStrategy, Map byName, + private static Bindings createBindings(BindableOperation operation, Map byName, Map byIndex) { + List bindings = new ArrayList<>(); + + byName.forEach( + (identifier, settableValue) -> bindings.add(new Bindings.NamedExpandedSingleBinding(identifier, settableValue, operation))); + + byIndex.forEach((identifier, settableValue) -> bindings.add(new Bindings.IndexedSingleBinding(identifier, settableValue))); + + return new Bindings(bindings); + } + + ExpandedPreparedOperation(String sql, NamedParameterExpander namedParameters, + ReactiveDataAccessStrategy dataAccessStrategy, Map byName, + Map byIndex) { + this( // namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), new MapBindParameterSource(byName)), // byName, // @@ -51,6 +68,11 @@ private ParameterbindingPreparedOperation(BindableOperation operation, Map filter) { - - } - - @Override - public void addBindingFilter(Function filter) { - - } - @Override public String toQuery() { return operation.toQuery(); } // todo that is a weird assymmetry between bindByName and bindByIndex - private void bindByName(Statement statement, Map byName) { + private void bindByName(Statement statement, Map byName) { byName.forEach((name, o) -> { if (o.getValue() != null) { - operation.bind(statement,name, o.getValue()); + operation.bind(statement, name, o.getValue()); } else { operation.bindNull(statement, name, o.getType()); }