diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java index fa08a29..87b47ea 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java @@ -2,6 +2,8 @@ package org.springframework.data.r2dbc.dialect; import io.r2dbc.spi.Statement; +import org.springframework.data.r2dbc.domain.BindTarget; + /** * A bind marker represents a single bindable parameter within a query. Bind markers are dialect-specific and provide a * {@link #getPlaceholder() placeholder} that is used in the actual query. @@ -23,19 +25,19 @@ public interface BindMarker { /** * Bind the given {@code value} to the {@link Statement} using the underlying binding strategy. * - * @param statement the statement to bind the value to. + * @param bindTarget the target to bind the value to. * @param value the actual value. Must not be {@literal null}. Use {@link #bindNull(Statement, Class)} for * {@literal null} values. * @see Statement#bind */ - void bind(Statement statement, Object value); + void bind(BindTarget bindTarget, Object value); /** * Bind a {@literal null} value to the {@link Statement} using the underlying binding strategy. * - * @param statement the statement to bind the value to. + * @param bindTarget the target to bind the value to. * @param valueType value type, must not be {@literal null}. * @see Statement#bindNull */ - void bindNull(Statement statement, Class valueType); + void bindNull(BindTarget bindTarget, Class valueType); } 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 c54da6d..06f8ec6 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarker.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarker.java @@ -15,12 +15,12 @@ */ package org.springframework.data.r2dbc.dialect; -import io.r2dbc.spi.Statement; +import org.springframework.data.r2dbc.domain.BindTarget; /** * A single indexed bind marker. */ -public class IndexedBindMarker implements BindMarker { +class IndexedBindMarker implements BindMarker { private final String placeholder; @@ -42,20 +42,20 @@ public class IndexedBindMarker implements BindMarker { /* * (non-Javadoc) - * @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object) + * @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Object) */ @Override - public void bind(Statement statement, Object value) { - statement.bind(this.index, value); + public void bind(BindTarget target, Object value) { + target.bind(this.index, value); } /* * (non-Javadoc) - * @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class) + * @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Class) */ @Override - public void bindNull(Statement statement, Class valueType) { - statement.bindNull(this.index, valueType); + public void bindNull(BindTarget target, Class valueType) { + target.bindNull(this.index, valueType); } diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java b/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java index 80bf779..6f87466 100644 --- a/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java +++ b/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java @@ -1,10 +1,9 @@ package org.springframework.data.r2dbc.dialect; -import io.r2dbc.spi.Statement; - import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Function; +import org.springframework.data.r2dbc.domain.BindTarget; import org.springframework.util.Assert; /** @@ -98,20 +97,20 @@ class NamedBindMarkers implements BindMarkers { /* * (non-Javadoc) - * @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object) + * @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Object) */ @Override - public void bind(Statement statement, Object value) { - statement.bind(this.identifier, value); + public void bind(BindTarget target, Object value) { + target.bind(this.identifier, value); } /* * (non-Javadoc) - * @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class) + * @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(org.springframework.data.r2dbc.dialect.BindTarget, java.lang.Class) */ @Override - public void bindNull(Statement statement, Class valueType) { - statement.bindNull(this.identifier, valueType); + public void bindNull(BindTarget target, Class valueType) { + target.bindNull(this.identifier, valueType); } } } diff --git a/src/main/java/org/springframework/data/r2dbc/domain/BindTarget.java b/src/main/java/org/springframework/data/r2dbc/domain/BindTarget.java new file mode 100644 index 0000000..0215e7e --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/domain/BindTarget.java @@ -0,0 +1,59 @@ +/* + * 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.domain; + +/** + * Target to apply bindings to. + * + * @author Mark Paluch + * @see PreparedOperation + * @see io.r2dbc.spi.Statement#bind + * @see io.r2dbc.spi.Statement#bindNull + */ +public interface BindTarget { + + /** + * Bind a value. + * + * @param identifier the identifier to bind to. + * @param value the value to bind. + */ + void bind(Object identifier, Object value); + + /** + * Bind a value to an index. Indexes are zero-based. + * + * @param index the index to bind to. + * @param value the value to bind. + */ + void bind(int index, Object value); + + /** + * Bind a {@code null} value. + * + * @param identifier the identifier to bind to. + * @param type the type of {@literal null} value. + */ + void bindNull(Object identifier, Class type); + + /** + * Bind a {@code null} value. + * + * @param index the index to bind to. + * @param type the type of {@literal null} value. + */ + void bindNull(int index, Class type); +} diff --git a/src/main/java/org/springframework/data/r2dbc/domain/OutboundRow.java b/src/main/java/org/springframework/data/r2dbc/domain/OutboundRow.java index 0f32d89..159c80b 100644 --- a/src/main/java/org/springframework/data/r2dbc/domain/OutboundRow.java +++ b/src/main/java/org/springframework/data/r2dbc/domain/OutboundRow.java @@ -39,7 +39,7 @@ public class OutboundRow implements Map { * Creates an empty {@link OutboundRow} instance. */ public OutboundRow() { - rowAsMap = new LinkedHashMap<>(); + this.rowAsMap = new LinkedHashMap<>(); } /** @@ -51,7 +51,7 @@ public class OutboundRow implements Map { Assert.notNull(map, "Map must not be null"); - rowAsMap = new LinkedHashMap<>(map); + this.rowAsMap = new LinkedHashMap<>(map); } /** @@ -61,8 +61,8 @@ public class OutboundRow implements Map { * @param value value. */ public OutboundRow(String key, SettableValue value) { - rowAsMap = new LinkedHashMap<>(); - rowAsMap.put(key, value); + this.rowAsMap = new LinkedHashMap<>(); + this.rowAsMap.put(key, value); } /** @@ -78,7 +78,7 @@ public class OutboundRow implements Map { * @return this */ public OutboundRow append(String key, SettableValue value) { - rowAsMap.put(key, value); + this.rowAsMap.put(key, value); return this; } @@ -88,7 +88,7 @@ public class OutboundRow implements Map { */ @Override public int size() { - return rowAsMap.size(); + return this.rowAsMap.size(); } /* @@ -97,7 +97,7 @@ public class OutboundRow implements Map { */ @Override public boolean isEmpty() { - return rowAsMap.isEmpty(); + return this.rowAsMap.isEmpty(); } /* @@ -106,7 +106,7 @@ public class OutboundRow implements Map { */ @Override public boolean containsKey(Object key) { - return rowAsMap.containsKey(key); + return this.rowAsMap.containsKey(key); } /* @@ -115,7 +115,7 @@ public class OutboundRow implements Map { */ @Override public boolean containsValue(Object value) { - return rowAsMap.containsValue(value); + return this.rowAsMap.containsValue(value); } /* @@ -124,7 +124,7 @@ public class OutboundRow implements Map { */ @Override public SettableValue get(Object key) { - return rowAsMap.get(key); + return this.rowAsMap.get(key); } /* @@ -133,7 +133,7 @@ public class OutboundRow implements Map { */ @Override public SettableValue put(String key, SettableValue value) { - return rowAsMap.put(key, value); + return this.rowAsMap.put(key, value); } /* @@ -142,7 +142,7 @@ public class OutboundRow implements Map { */ @Override public SettableValue remove(Object key) { - return rowAsMap.remove(key); + return this.rowAsMap.remove(key); } /* @@ -151,7 +151,7 @@ public class OutboundRow implements Map { */ @Override public void putAll(Map m) { - rowAsMap.putAll(m); + this.rowAsMap.putAll(m); } /* @@ -160,7 +160,7 @@ public class OutboundRow implements Map { */ @Override public void clear() { - rowAsMap.clear(); + this.rowAsMap.clear(); } /* @@ -169,7 +169,7 @@ public class OutboundRow implements Map { */ @Override public Set keySet() { - return rowAsMap.keySet(); + return this.rowAsMap.keySet(); } /* @@ -178,7 +178,7 @@ public class OutboundRow implements Map { */ @Override public Collection values() { - return rowAsMap.values(); + return this.rowAsMap.values(); } /* @@ -187,7 +187,7 @@ public class OutboundRow implements Map { */ @Override public Set> entrySet() { - return rowAsMap.entrySet(); + return this.rowAsMap.entrySet(); } /* @@ -207,7 +207,7 @@ public class OutboundRow implements Map { OutboundRow row = (OutboundRow) o; - return rowAsMap.equals(row.rowAsMap); + return this.rowAsMap.equals(row.rowAsMap); } /* @@ -216,7 +216,7 @@ public class OutboundRow implements Map { */ @Override public int hashCode() { - return rowAsMap.hashCode(); + return this.rowAsMap.hashCode(); } /* @@ -225,11 +225,11 @@ public class OutboundRow implements Map { */ @Override public String toString() { - return "OutboundRow[" + rowAsMap + "]"; + return "OutboundRow[" + this.rowAsMap + "]"; } @Override public void forEach(BiConsumer action) { - rowAsMap.forEach(action); + this.rowAsMap.forEach(action); } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/domain/PreparedOperation.java similarity index 54% rename from src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java rename to src/main/java/org/springframework/data/r2dbc/domain/PreparedOperation.java index 4730884..5136580 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/domain/PreparedOperation.java @@ -13,25 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.r2dbc.function; +package org.springframework.data.r2dbc.domain; -import io.r2dbc.spi.Connection; -import io.r2dbc.spi.Statement; - -import java.util.function.Function; import java.util.function.Supplier; /** * Extension to {@link QueryOperation} for a prepared SQL query {@link Supplier} with bound parameters. Contains - * parameter bindings that can be {@link #bind(Statement)} bound to a {@link Statement}. + * parameter bindings that can be {@link #bindTo bound} bound to a {@link BindTarget}. *

- * Can be executed with {@link DatabaseClient}. + * Can be executed with {@link org.springframework.data.r2dbc.function.DatabaseClient}. *

* * @param underlying operation source. * @author Mark Paluch - * @see DatabaseClient - * @see DatabaseClient.SqlSpec#sql(Supplier) + * @see org.springframework.data.r2dbc.function.DatabaseClient + * @see org.springframework.data.r2dbc.function.DatabaseClient.SqlSpec#sql(Supplier) */ public interface PreparedOperation extends QueryOperation { @@ -41,15 +37,10 @@ public interface PreparedOperation extends QueryOperation { T getSource(); /** - * 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. + * Apply bindings to {@link BindTarget}. * - * @param connection the {@link Connection} used for constructing a statement - * @return the bound statement. + * @param target the target to apply bindings to. */ - Statement createBoundStatement(Connection connection); + void bindTo(BindTarget target); - void addSqlFilter(Function filter); - - void addBindingFilter(Function filter); } diff --git a/src/main/java/org/springframework/data/r2dbc/domain/QueryOperation.java b/src/main/java/org/springframework/data/r2dbc/domain/QueryOperation.java new file mode 100644 index 0000000..05d70c9 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/domain/QueryOperation.java @@ -0,0 +1,42 @@ +/* + * 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.domain; + +import java.util.function.Supplier; + +/** + * Interface declaring a query operation that can be represented with a query string. This interface is typically + * implemented by classes representing a SQL operation such as {@code SELECT}, {@code INSERT}, and such. + * + * @author Mark Paluch + * @see PreparedOperation + */ +@FunctionalInterface +public interface QueryOperation extends Supplier { + + /** + * Returns the string-representation of this operation to be used with {@link io.r2dbc.spi.Statement} creation. + * + * @return the operation as SQL string. + * @see io.r2dbc.spi.Connection#createStatement(String) + */ + String toQuery(); + + @Override + default String get() { + return toQuery(); + } +} diff --git a/src/main/java/org/springframework/data/r2dbc/domain/SettableValue.java b/src/main/java/org/springframework/data/r2dbc/domain/SettableValue.java index ebb5dca..36926bb 100644 --- a/src/main/java/org/springframework/data/r2dbc/domain/SettableValue.java +++ b/src/main/java/org/springframework/data/r2dbc/domain/SettableValue.java @@ -84,7 +84,7 @@ public class SettableValue { */ @Nullable public Object getValue() { - return value; + return this.value; } /** @@ -93,7 +93,7 @@ public class SettableValue { * @return the column value type */ public Class getType() { - return type; + return this.type; } /** @@ -102,7 +102,7 @@ public class SettableValue { * @return whether this {@link SettableValue} has a value. {@literal false} if {@link #getValue()} is {@literal null}. */ public boolean hasValue() { - return value != null; + return this.value != null; } /** @@ -111,7 +111,7 @@ public class SettableValue { * @return whether this {@link SettableValue} is empty. {@literal true} if {@link #getValue()} is {@literal null}. */ public boolean isEmpty() { - return value == null; + return this.value == null; } @Override @@ -121,20 +121,20 @@ public class SettableValue { if (!(o instanceof SettableValue)) return false; SettableValue value1 = (SettableValue) o; - return Objects.equals(value, value1.value) && Objects.equals(type, value1.type); + return Objects.equals(this.value, value1.value) && Objects.equals(this.type, value1.type); } @Override public int hashCode() { - return Objects.hash(value, type); + return Objects.hash(this.value, this.type); } @Override public String toString() { final StringBuffer sb = new StringBuffer(); sb.append("SettableValue"); - sb.append("[value=").append(value); - sb.append(", type=").append(type); + sb.append("[value=").append(this.value); + sb.append(", type=").append(this.type); sb.append(']'); return sb.toString(); } diff --git a/src/main/java/org/springframework/data/r2dbc/function/BindableOperation.java b/src/main/java/org/springframework/data/r2dbc/function/BindableOperation.java index 941b2cf..d37c44f 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/BindableOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/BindableOperation.java @@ -2,6 +2,9 @@ package org.springframework.data.r2dbc.function; import io.r2dbc.spi.Statement; +import org.springframework.data.r2dbc.domain.BindTarget; +import org.springframework.data.r2dbc.domain.PreparedOperation; +import org.springframework.data.r2dbc.domain.QueryOperation; import org.springframework.data.r2dbc.domain.SettableValue; /** @@ -11,46 +14,46 @@ import org.springframework.data.r2dbc.domain.SettableValue; * * @author Mark Paluch * @see Statement#bind - * @see Statement#bindNull + * @see Statement#bindNull TODO: Refactor to {@link PreparedOperation}. */ public interface BindableOperation extends QueryOperation { /** * Bind the given {@code value} to the {@link Statement} using the underlying binding strategy. * - * @param statement the statement to bind the value to. + * @param bindTarget the bindTarget to bind the value to. * @param identifier named identifier that is considered by the underlying binding strategy. * @param value the actual value. Must not be {@literal null}. Use {@link #bindNull(Statement, Class)} for * {@literal null} values. * @see Statement#bind */ - void bind(Statement statement, String identifier, Object value); + void bind(BindTarget bindTarget, String identifier, Object value); /** * Bind a {@literal null} value to the {@link Statement} using the underlying binding strategy. * - * @param statement the statement to bind the value to. + * @param bindTarget the bindTarget to bind the value to. * @param identifier named identifier that is considered by the underlying binding strategy. * @param valueType value type, must not be {@literal null}. * @see Statement#bindNull */ - void bindNull(Statement statement, String identifier, Class valueType); + void bindNull(BindTarget bindTarget, String identifier, Class valueType); /** * Bind a {@link SettableValue} to the {@link Statement} using the underlying binding strategy. Binds either the * {@link SettableValue#getValue()} or {@literal null}, depending on whether the value is {@literal null}. * - * @param statement the statement to bind the value to. + * @param bindTarget the bindTarget to bind the value to. * @param value the settable value * @see Statement#bind * @see Statement#bindNull */ - default void bind(Statement statement, String identifier, SettableValue value) { + default void bind(BindTarget bindTarget, String identifier, SettableValue value) { if (value.getValue() == null) { - bindNull(statement, identifier, value.getType()); + bindNull(bindTarget, identifier, value.getType()); } else { - bind(statement, identifier, value.getValue()); + bind(bindTarget, identifier, value.getValue()); } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java deleted file mode 100644 index a882156..0000000 --- a/src/main/java/org/springframework/data/r2dbc/function/Bindings.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.RequiredArgsConstructor; - -import java.util.List; - -import org.springframework.data.r2dbc.domain.SettableValue; - -/** - * @author Jens Schauder - */ -public class Bindings { - - private final List bindings; - - public Bindings(List bindings) { - this.bindings = bindings; - } - - public void apply(Statement statement) { - bindings.forEach(sb -> sb.bindTo(statement)); - } - - @RequiredArgsConstructor - public static abstract class SingleBinding { - - final T identifier; - final SettableValue value; - - public abstract void bindTo(Statement statement); - - public abstract boolean isIndexed(); - - public final 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 (value.isEmpty()) { - statement.bindNull((int) identifier, value.getType()); - } else { - statement.bind((int) identifier, value.getValue()); - } - } - - @Override - public boolean isIndexed() { - return true; - } - } - - public static class NamedExpandedSingleBinding extends SingleBinding { - - private final BindableOperation operation; - - public NamedExpandedSingleBinding(String identifier, SettableValue value, BindableOperation operation) { - - super(identifier, value); - - this.operation = operation; - } - - @Override - public void bindTo(Statement statement) { - - if (value != null) { - operation.bind(statement, identifier, value); - } else { - operation.bindNull(statement, identifier, value.getType()); - } - } - - @Override - public boolean isIndexed() { - return false; - } - } -} 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 df95db7..da2cf45 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DatabaseClient.java @@ -29,6 +29,7 @@ import org.reactivestreams.Publisher; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.r2dbc.domain.PreparedOperation; import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; /** 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 1cb32ac..ebf2107 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -52,7 +52,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.UncategorizedR2dbcException; +import org.springframework.data.r2dbc.domain.BindTarget; import org.springframework.data.r2dbc.domain.OutboundRow; +import org.springframework.data.r2dbc.domain.PreparedOperation; import org.springframework.data.r2dbc.domain.SettableValue; import org.springframework.data.r2dbc.function.connectionfactory.ConnectionProxy; import org.springframework.data.r2dbc.function.convert.ColumnMapRowMapper; @@ -60,6 +62,7 @@ import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator; import org.springframework.data.relational.core.sql.Insert; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Default implementation of {@link DatabaseClient}. @@ -322,24 +325,51 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { this.sqlSupplier = sqlSupplier; } - protected String getSql() { + FetchSpec exchange(Supplier sqlSupplier, BiFunction mappingFunction) { - String sql = sqlSupplier.get(); - Assert.state(sql != null, "SQL supplier returned null!"); - return sql; - } + String sql = getRequiredSql(sqlSupplier); - FetchSpec exchange(String sql, BiFunction mappingFunction) { + Function executeFunction = it -> { - PreparedOperation pop; + if (logger.isDebugEnabled()) { + logger.debug("Executing SQL statement [" + sql + "]"); + } - if (sqlSupplier instanceof PreparedOperation) { - pop = ((PreparedOperation) sqlSupplier); - } else { - pop = new ExpandedPreparedOperation(sql, namedParameters, dataAccessStrategy, byName, byIndex); - } + if (sqlSupplier instanceof PreparedOperation) { - Function> resultFunction = it -> Flux.from(pop.createBoundStatement(it).execute()); + Statement statement = it.createStatement(sql); + BindTarget bindTarget = new StatementWrapper(statement); + ((PreparedOperation) sqlSupplier).bindTo(bindTarget); + + return statement; + } + + BindableOperation operation = namedParameters.expand(sql, dataAccessStrategy.getBindMarkersFactory(), + new MapBindParameterSource(byName)); + + String expanded = operation.toQuery(); + if (logger.isTraceEnabled()) { + logger.trace("Expanded SQL [" + expanded + "]"); + } + + Statement statement = it.createStatement(expanded); + BindTarget bindTarget = new StatementWrapper(statement); + + byName.forEach((name, o) -> { + + if (o.getValue() != null) { + operation.bind(bindTarget, name, o.getValue()); + } else { + operation.bindNull(bindTarget, name, o.getType()); + } + }); + + bindByIndex(statement, byIndex); + + return statement; + }; + + Function> resultFunction = it -> Flux.from(executeFunction.apply(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // sql, // @@ -395,7 +425,7 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } private void assertNotPreparedOperation() { - if (sqlSupplier instanceof PreparedOperation) { + if (this.sqlSupplier instanceof PreparedOperation) { throw new InvalidDataAccessApiUsageException("Cannot add bindings to a PreparedOperation"); } } @@ -440,12 +470,12 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { Assert.notNull(mappingFunction, "Mapping function must not be null!"); - return exchange(getSql(), mappingFunction); + return exchange(this.sqlSupplier, mappingFunction); } @Override public FetchSpec> fetch() { - return exchange(getSql(), ColumnMapRowMapper.INSTANCE); + return exchange(this.sqlSupplier, ColumnMapRowMapper.INSTANCE); } @Override @@ -525,12 +555,12 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { Assert.notNull(mappingFunction, "Mapping function must not be null!"); - return exchange(getSql(), mappingFunction); + return exchange(this.sqlSupplier, mappingFunction); } @Override public FetchSpec fetch() { - return exchange(getSql(), mappingFunction); + return exchange(this.sqlSupplier, mappingFunction); } @Override @@ -882,10 +912,18 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { byName.forEach(it::bind); }); - Function> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute()); + String sql = getRequiredSql(operation); + + Function> resultFunction = it -> { + + Statement statement = it.createStatement(sql); + operation.bindTo(new StatementWrapper(statement)); + + return Flux.from(statement.execute()); + }; return new DefaultSqlResult<>(DefaultDatabaseClient.this, // - operation.toQuery(), // + sql, // resultFunction, // it -> resultFunction.apply(it).flatMap(Result::getRowsUpdated).next(), // mappingFunction); @@ -993,10 +1031,18 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { }); }); - Function> resultFunction = it -> Flux.from(operation.createBoundStatement(it).execute()); + String sql = getRequiredSql(operation); + Function> resultFunction = it -> { + + Statement statement = it.createStatement(sql); + operation.bindTo(new StatementWrapper(statement)); + statement.returnGeneratedValues(); + + return Flux.from(statement.execute()); + }; return new DefaultSqlResult<>(DefaultDatabaseClient.this, // - operation.toQuery(), // + sql, // resultFunction, // it -> resultFunction // .apply(it) // @@ -1045,6 +1091,13 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { } } + private static String getRequiredSql(Supplier sqlSupplier) { + + String sql = sqlSupplier.get(); + Assert.state(StringUtils.hasText(sql), "SQL returned by SQL supplier must not be empty!"); + return sql; + } + /** * Invocation handler that suppresses close calls on R2DBC Connections. Also prepares returned Statement * (Prepared/CallbackStatement) objects. @@ -1118,4 +1171,31 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { }); } } + + @RequiredArgsConstructor + static class StatementWrapper implements BindTarget { + + final Statement statement; + + @Override + public void bind(Object identifier, Object value) { + this.statement.bind(identifier, value); + } + + @Override + public void bind(int index, Object value) { + this.statement.bind(index, value); + } + + @Override + public void bindNull(Object identifier, Class type) { + this.statement.bindNull(identifier, type); + } + + @Override + public void bindNull(int index, Class type) { + this.statement.bindNull(index, type); + } + } + } 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 d9fcc61..980a6ce 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultStatementFactory.java @@ -15,7 +15,6 @@ */ package org.springframework.data.r2dbc.function; -import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -29,16 +28,29 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; 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.BindTarget; +import org.springframework.data.r2dbc.domain.PreparedOperation; import org.springframework.data.r2dbc.domain.SettableValue; -import org.springframework.data.relational.core.sql.*; +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; @@ -94,42 +106,11 @@ class DefaultStatementFactory implements StatementFactory { return new DefaultPreparedOperation<>( // select, // renderContext, // - createBindings(binding) // + 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) -> { - if (bindMarker instanceof IndexedBindMarker) { - singleBindings // - .add(new Bindings.IndexedSingleBinding( // - ((IndexedBindMarker) bindMarker).getIndex(), // - 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) @@ -174,12 +155,7 @@ class DefaultStatementFactory implements StatementFactory { Insert insert = StatementBuilder.insert().into(table).columns(table.columns(binderBuilder.bindings.keySet())) .values(expressions).build(); - return new DefaultPreparedOperation(insert, renderContext, createBindings(binding)) { - @Override - public Statement bind(Statement to) { - return super.bind(to).returnGeneratedValues(generatedKeysNames.toArray(new String[0])); - } - }; + return new DefaultPreparedOperation(insert, renderContext, binding); }); } @@ -228,7 +204,7 @@ class DefaultStatementFactory implements StatementFactory { update = updateBuilder.build(); } - return new DefaultPreparedOperation<>(update, renderContext, createBindings(binding)); + return new DefaultPreparedOperation<>(update, renderContext, binding); }); } @@ -266,7 +242,7 @@ class DefaultStatementFactory implements StatementFactory { delete = deleteBuilder.build(); } - return new DefaultPreparedOperation<>(delete, renderContext, createBindings(binding)); + return new DefaultPreparedOperation<>(delete, renderContext, binding); }); } @@ -429,97 +405,24 @@ class DefaultStatementFactory implements StatementFactory { * * @param to */ - void apply(Statement to) { + void apply(BindTarget to) { values.forEach((marker, value) -> marker.bind(to, value)); nulls.forEach((marker, value) -> marker.bindNull(to, value.getType())); } } - static abstract class PreparedOperationSupport implements PreparedOperation { - - private Function sqlFilter = s -> s; - private Function bindingFilter = b -> b; - private final Bindings bindings; - - protected PreparedOperationSupport(Bindings bindings) { - - this.bindings = bindings; - } - - abstract protected String createBaseSql(); - - Bindings getBaseBinding() { - return bindings; - }; - - /* - * (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 */ - static class DefaultPreparedOperation extends PreparedOperationSupport { + @RequiredArgsConstructor + static class DefaultPreparedOperation implements PreparedOperation { private final T source; private final RenderContext renderContext; - - DefaultPreparedOperation(T source, RenderContext renderContext, Bindings bindings) { - - super(bindings); - - this.source = source; - this.renderContext = renderContext; - } + private final Binding binding; /* * (non-Javadoc) @@ -530,8 +433,12 @@ class DefaultStatementFactory implements StatementFactory { return this.source; } + /* + * (non-Javadoc) + * @see org.springframework.data.r2dbc.function.QueryOperation#toQuery() + */ @Override - protected String createBaseSql() { + public String toQuery() { SqlRenderer sqlRenderer = SqlRenderer.create(renderContext); @@ -554,5 +461,9 @@ class DefaultStatementFactory implements StatementFactory { throw new IllegalStateException("Cannot render " + this.getSource()); } + @Override + public void bindTo(BindTarget target) { + binding.apply(target); + } } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java deleted file mode 100644 index a4d0e2b..0000000 --- a/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.data.r2dbc.domain.SettableValue; - -/** - * @author Jens Schauder - */ -public class ExpandedPreparedOperation - extends DefaultStatementFactory.PreparedOperationSupport { - - private final BindableOperation operation; - private final Map byName; - private final Map byIndex; - - private ExpandedPreparedOperation(BindableOperation operation, Map byName, - Map byIndex) { - - super(createBindings(operation, byName, byIndex)); - - this.operation = operation; - this.byName = byName; - this.byIndex = byIndex; - } - - 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, // - byIndex // - ); - } - - @Override - protected String createBaseSql() { - return toQuery(); - } - - @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 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()); - } - }); - } -} diff --git a/src/main/java/org/springframework/data/r2dbc/function/NamedParameterExpander.java b/src/main/java/org/springframework/data/r2dbc/function/NamedParameterExpander.java index 8beeb50..a913c62 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/NamedParameterExpander.java +++ b/src/main/java/org/springframework/data/r2dbc/function/NamedParameterExpander.java @@ -15,14 +15,13 @@ */ package org.springframework.data.r2dbc.function; -import io.r2dbc.spi.Statement; - import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.data.r2dbc.dialect.BindMarkersFactory; +import org.springframework.data.r2dbc.domain.BindTarget; /** * SQL translation support allowing the use of named parameters rather than native placeholders. @@ -147,13 +146,13 @@ public class NamedParameterExpander { return new BindableOperation() { @Override - public void bind(Statement statement, String identifier, Object value) { - statement.bind(identifier, value); + public void bind(BindTarget target, String identifier, Object value) { + target.bind(identifier, value); } @Override - public void bindNull(Statement statement, String identifier, Class valueType) { - statement.bindNull(identifier, valueType); + public void bindNull(BindTarget target, String identifier, Class valueType) { + target.bindNull(identifier, valueType); } @Override diff --git a/src/main/java/org/springframework/data/r2dbc/function/NamedParameterUtils.java b/src/main/java/org/springframework/data/r2dbc/function/NamedParameterUtils.java index 2ad6160..b2efac0 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/NamedParameterUtils.java +++ b/src/main/java/org/springframework/data/r2dbc/function/NamedParameterUtils.java @@ -15,7 +15,6 @@ */ package org.springframework.data.r2dbc.function; -import io.r2dbc.spi.Statement; import lombok.Value; import java.util.ArrayList; @@ -31,6 +30,7 @@ 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.BindMarkersFactory; +import org.springframework.data.r2dbc.domain.BindTarget; import org.springframework.util.Assert; /** @@ -391,22 +391,22 @@ abstract class NamedParameterUtils { /* * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bind(io.r2dbc.spi.Statement, java.lang.String, java.lang.Object) + * @see org.springframework.data.r2dbc.function.BindableOperation#bind(BindTarget, java.lang.String, java.lang.Object) */ @Override @SuppressWarnings("unchecked") - public void bind(Statement statement, String identifier, Object value) { + public void bind(BindTarget target, String identifier, Object value) { List bindMarkers = getBindMarkers(identifier); if (bindMarkers == null) { - statement.bind(identifier, value); + target.bind(identifier, value); return; } if (bindMarkers.size() == 1) { - bindMarkers.get(0).bind(statement, value); + bindMarkers.get(0).bind(target, value); } else { Assert.isInstanceOf(Collection.class, value, @@ -424,36 +424,36 @@ abstract class NamedParameterUtils { if (valueToBind instanceof Object[]) { Object[] objects = (Object[]) valueToBind; for (Object object : objects) { - bind(statement, markers, object); + bind(target, markers, object); } } else { - bind(statement, markers, valueToBind); + bind(target, markers, valueToBind); } } } } - private void bind(Statement statement, Iterator markers, Object valueToBind) { + private void bind(BindTarget target, Iterator markers, Object valueToBind) { Assert.isTrue(markers.hasNext(), () -> String.format( "No bind marker for value [%s] in SQL [%s]. Check that the query was expanded using the same arguments.", valueToBind, toQuery())); - markers.next().bind(statement, valueToBind); + markers.next().bind(target, valueToBind); } /* * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(io.r2dbc.spi.Statement, java.lang.String, java.lang.Class) + * @see org.springframework.data.r2dbc.function.BindableOperation#bindNull(BindTarget, java.lang.String, java.lang.Class) */ @Override - public void bindNull(Statement statement, String identifier, Class valueType) { + public void bindNull(BindTarget target, String identifier, Class valueType) { List bindMarkers = getBindMarkers(identifier); if (bindMarkers.size() == 1) { - bindMarkers.get(0).bindNull(statement, valueType); + bindMarkers.get(0).bindNull(target, valueType); return; } diff --git a/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java deleted file mode 100644 index 1ea9963..0000000 --- a/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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()); - } - }); - } -} diff --git a/src/main/java/org/springframework/data/r2dbc/function/QueryOperation.java b/src/main/java/org/springframework/data/r2dbc/function/QueryOperation.java deleted file mode 100644 index b26923e..0000000 --- a/src/main/java/org/springframework/data/r2dbc/function/QueryOperation.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.springframework.data.r2dbc.function; - -import java.util.function.Supplier; - -/** - * Interface declaring a query operation that can be represented with a query string. This interface is typically - * implemented by classes representing a SQL operation such as {@code SELECT}, {@code INSERT}, and such. - * - * @author Mark Paluch - */ -@FunctionalInterface -public interface QueryOperation extends Supplier { - - /** - * Returns the string-representation of this operation to be used with {@link io.r2dbc.spi.Statement} creation. - * - * @return the operation as SQL string. - * @see io.r2dbc.spi.Connection#createStatement(String) - */ - String toQuery(); - - @Override - default String get() { - return toQuery(); - } -} 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 21380ed..20849af 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java +++ b/src/main/java/org/springframework/data/r2dbc/function/StatementFactory.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.function.Consumer; import org.springframework.data.r2dbc.dialect.Dialect; +import org.springframework.data.r2dbc.domain.PreparedOperation; import org.springframework.data.r2dbc.domain.SettableValue; import org.springframework.data.relational.core.sql.Delete; import org.springframework.data.relational.core.sql.Insert; 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 cda23d9..347d7c6 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 @@ -27,9 +27,9 @@ import java.util.Set; import org.reactivestreams.Publisher; +import org.springframework.data.r2dbc.domain.PreparedOperation; import org.springframework.data.r2dbc.domain.SettableValue; import org.springframework.data.r2dbc.function.DatabaseClient; -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; diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/AnonymousBindMarkersUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/AnonymousBindMarkersUnitTests.java index 663af75..55a2628 100644 --- a/src/test/java/org/springframework/data/r2dbc/dialect/AnonymousBindMarkersUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/dialect/AnonymousBindMarkersUnitTests.java @@ -18,10 +18,10 @@ package org.springframework.data.r2dbc.dialect; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import io.r2dbc.spi.Statement; - import org.junit.Test; +import org.springframework.data.r2dbc.domain.BindTarget; + /** * Unit tests for {@link AnonymousBindMarkers}. * @@ -44,17 +44,17 @@ public class AnonymousBindMarkersUnitTests { @Test // gh-75 public void shouldBindByIndex() { - Statement statement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindMarkers bindMarkers = BindMarkersFactory.anonymous("?").create(); BindMarker first = bindMarkers.next(); BindMarker second = bindMarkers.next(); - second.bind(statement, "foo"); - first.bindNull(statement, Object.class); + second.bind(bindTarget, "foo"); + first.bindNull(bindTarget, Object.class); - verify(statement).bindNull(0, Object.class); - verify(statement).bind(1, "foo"); + verify(bindTarget).bindNull(0, Object.class); + verify(bindTarget).bind(1, "foo"); } } diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java index 94e7f5d..eafbd64 100644 --- a/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java @@ -3,10 +3,10 @@ package org.springframework.data.r2dbc.dialect; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import io.r2dbc.spi.Statement; - import org.junit.Test; +import org.springframework.data.r2dbc.domain.BindTarget; + /** * Unit tests for {@link IndexedBindMarkers}. * @@ -29,20 +29,20 @@ public class IndexedBindMarkersUnitTests { @Test // gh-15 public void shouldCreateNewBindMarkersWithOffset() { - Statement statement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 1).create(); BindMarker first = bindMarkers.next(); - first.bind(statement, "foo"); + first.bind(bindTarget, "foo"); BindMarker second = bindMarkers.next(); - second.bind(statement, "bar"); + second.bind(bindTarget, "bar"); assertThat(first.getPlaceholder()).isEqualTo("$1"); assertThat(second.getPlaceholder()).isEqualTo("$2"); - verify(statement).bind(0, "foo"); - verify(statement).bind(1, "bar"); + verify(bindTarget).bind(0, "foo"); + verify(bindTarget).bind(1, "bar"); } @Test // gh-15 @@ -65,28 +65,28 @@ public class IndexedBindMarkersUnitTests { @Test // gh-15 public void bindValueShouldBindByIndex() { - Statement statement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create(); - bindMarkers.next().bind(statement, "foo"); - bindMarkers.next().bind(statement, "bar"); + bindMarkers.next().bind(bindTarget, "foo"); + bindMarkers.next().bind(bindTarget, "bar"); - verify(statement).bind(0, "foo"); - verify(statement).bind(1, "bar"); + verify(bindTarget).bind(0, "foo"); + verify(bindTarget).bind(1, "bar"); } @Test // gh-15 public void bindNullShouldBindByIndex() { - Statement statement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create(); bindMarkers.next(); // ignore - bindMarkers.next().bindNull(statement, Integer.class); + bindMarkers.next().bindNull(bindTarget, Integer.class); - verify(statement).bindNull(1, Integer.class); + verify(bindTarget).bindNull(1, Integer.class); } } diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java index 5febb2e..e92aa8b 100644 --- a/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java @@ -3,10 +3,10 @@ package org.springframework.data.r2dbc.dialect; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import io.r2dbc.spi.Statement; - import org.junit.Test; +import org.springframework.data.r2dbc.domain.BindTarget; + /** * Unit tests for {@link NamedBindMarkers}. * @@ -85,27 +85,27 @@ public class NamedBindMarkersUnitTests { @Test // gh-15 public void bindValueShouldBindByName() { - Statement statement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create(); - bindMarkers.next().bind(statement, "foo"); - bindMarkers.next().bind(statement, "bar"); + bindMarkers.next().bind(bindTarget, "foo"); + bindMarkers.next().bind(bindTarget, "bar"); - verify(statement).bind("p0", "foo"); - verify(statement).bind("p1", "bar"); + verify(bindTarget).bind("p0", "foo"); + verify(bindTarget).bind("p1", "bar"); } @Test // gh-15 public void bindNullShouldBindByName() { - Statement statement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create(); bindMarkers.next(); // ignore - bindMarkers.next().bindNull(statement, Integer.class); + bindMarkers.next().bindNull(bindTarget, Integer.class); - verify(statement).bindNull("p1", Integer.class); + verify(bindTarget).bindNull("p1", Integer.class); } } diff --git a/src/test/java/org/springframework/data/r2dbc/function/NamedParameterUtilsUnitTests.java b/src/test/java/org/springframework/data/r2dbc/function/NamedParameterUtilsUnitTests.java index ed87827..351f969 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/NamedParameterUtilsUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/NamedParameterUtilsUnitTests.java @@ -18,15 +18,15 @@ package org.springframework.data.r2dbc.function; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import io.r2dbc.spi.Statement; - import java.util.Arrays; import java.util.HashMap; import org.junit.Test; + import org.springframework.data.r2dbc.dialect.BindMarkersFactory; import org.springframework.data.r2dbc.dialect.PostgresDialect; import org.springframework.data.r2dbc.dialect.SqlServerDialect; +import org.springframework.data.r2dbc.domain.BindTarget; /** * Unit tests for {@link NamedParameterUtils}. @@ -94,15 +94,15 @@ public class NamedParameterUtilsUnitTests { namedParams.addValue("a", Arrays.asList(new Object[] { "Walter", "Heisenberg" }, new Object[] { "Walt Jr.", "Flynn" })); - Statement mockStatement = mock(Statement.class); + BindTarget bindTarget = mock(BindTarget.class); BindableOperation operation = NamedParameterUtils.substituteNamedParameters("xxx :a", BIND_MARKERS, namedParams); - operation.bind(mockStatement, "a", namedParams.getValue("a")); + operation.bind(bindTarget, "a", namedParams.getValue("a")); - verify(mockStatement).bind(0, "Walter"); - verify(mockStatement).bind(1, "Heisenberg"); - verify(mockStatement).bind(2, "Walt Jr."); - verify(mockStatement).bind(3, "Flynn"); + verify(bindTarget).bind(0, "Walter"); + verify(bindTarget).bind(1, "Heisenberg"); + verify(bindTarget).bind(2, "Walt Jr."); + verify(bindTarget).bind(3, "Flynn"); } @Test // gh-23 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 a873e80..fd22ac5 100644 --- a/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/r2dbc/function/StatementFactoryUnitTests.java @@ -24,9 +24,11 @@ import io.r2dbc.spi.Statement; import java.util.Arrays; import java.util.Collections; +import org.junit.Before; import org.junit.Test; import org.springframework.data.r2dbc.dialect.PostgresDialect; +import org.springframework.data.r2dbc.domain.PreparedOperation; import org.springframework.data.r2dbc.domain.SettableValue; import org.springframework.data.relational.core.dialect.RenderContextFactory; import org.springframework.data.relational.core.sql.Delete; @@ -49,7 +51,8 @@ public class StatementFactoryUnitTests { Statement statementMock = mock(Statement.class); Connection connectionMock = mock(Connection.class); - { + @Before + public void before() { when(connectionMock.createStatement(anyString())).thenReturn(statementMock); } @@ -61,7 +64,7 @@ public class StatementFactoryUnitTests { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo"); - select.createBoundStatement(connectionMock); + createBoundStatement(select, connectionMock); verifyZeroInteractions(statementMock); } @@ -76,7 +79,7 @@ public class StatementFactoryUnitTests { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1"); - select.createBoundStatement(connectionMock); + createBoundStatement(select, connectionMock); verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); @@ -93,7 +96,7 @@ public class StatementFactoryUnitTests { 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.createBoundStatement(connectionMock); + createBoundStatement(select, connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); @@ -110,7 +113,7 @@ public class StatementFactoryUnitTests { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IS NULL"); - select.createBoundStatement(connectionMock); + createBoundStatement(select, connectionMock); verifyZeroInteractions(statementMock); } @@ -124,7 +127,7 @@ public class StatementFactoryUnitTests { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IN ($1, $2)"); - select.createBoundStatement(connectionMock); + createBoundStatement(select, connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -147,9 +150,8 @@ public class StatementFactoryUnitTests { assertThat(insert.getSource()).isInstanceOf(Insert.class); assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES ($1)"); - insert.createBoundStatement(connectionMock); + createBoundStatement(insert, connectionMock); verify(statementMock).bind(0, "Foo"); - verify(statementMock).returnGeneratedValues(any(String[].class)); verifyNoMoreInteractions(statementMock); } @@ -170,7 +172,7 @@ public class StatementFactoryUnitTests { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.createBoundStatement(connectionMock); + createBoundStatement(update, connectionMock); verify(statementMock).bind(0, "Foo"); verifyNoMoreInteractions(statementMock); } @@ -185,7 +187,7 @@ public class StatementFactoryUnitTests { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.createBoundStatement(connectionMock); + createBoundStatement(update, connectionMock); verify(statementMock).bindNull(0, String.class); verifyNoMoreInteractions(statementMock); @@ -202,7 +204,7 @@ public class StatementFactoryUnitTests { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1 WHERE foo.baz = $2"); - update.createBoundStatement(connectionMock); + createBoundStatement(update, connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).bind(1, "Baz"); verifyNoMoreInteractions(statementMock); @@ -218,7 +220,7 @@ public class StatementFactoryUnitTests { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1"); - delete.createBoundStatement(connectionMock); + createBoundStatement(delete, connectionMock); verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); } @@ -234,7 +236,7 @@ public class StatementFactoryUnitTests { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1 AND foo.baz = $2"); - delete.createBoundStatement(connectionMock); + createBoundStatement(delete, connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -250,7 +252,13 @@ public class StatementFactoryUnitTests { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe IS NULL"); - delete.createBoundStatement(connectionMock); + createBoundStatement(delete, connectionMock); verifyZeroInteractions(statementMock); } + + void createBoundStatement(PreparedOperation operation, Connection connection) { + + Statement statement = connection.createStatement(operation.toQuery()); + operation.bindTo(new DefaultDatabaseClient.StatementWrapper(statement)); + } }