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 b7d84d6..c54da6d 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 @@ import io.r2dbc.spi.Statement; /** * 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 @@ class IndexedBindMarker implements BindMarker { 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 new file mode 100644 index 0000000..a882156 --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/Bindings.java @@ -0,0 +1,104 @@ +/* + * 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/DefaultDatabaseClient.java b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java index e837816..1cb32ac 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java +++ b/src/main/java/org/springframework/data/r2dbc/function/DefaultDatabaseClient.java @@ -331,40 +331,15 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { 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) { - 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(executeFunction.apply(it).execute()); + Function> resultFunction = it -> Flux.from(pop.createBoundStatement(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // sql, // @@ -907,20 +882,10 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { 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.createBoundStatement(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // - sql, // + operation.toQuery(), // resultFunction, // it -> resultFunction.apply(it).flatMap(Result::getRowsUpdated).next(), // mappingFunction); @@ -1028,21 +993,10 @@ class DefaultDatabaseClient implements DatabaseClient, ConnectionAccessor { }); }); - 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.createBoundStatement(it).execute()); return new DefaultSqlResult<>(DefaultDatabaseClient.this, // - sql, // + operation.toQuery(), // resultFunction, // it -> resultFunction // .apply(it) // 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 6028fd1..d9fcc61 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; @@ -28,27 +29,16 @@ 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.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; @@ -101,10 +91,45 @@ class DefaultStatementFactory implements StatementFactory { 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) -> { + 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) @@ -149,7 +174,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, binding) { + return new DefaultPreparedOperation(insert, renderContext, createBindings(binding)) { @Override public Statement bind(Statement to) { return super.bind(to).returnGeneratedValues(generatedKeysNames.toArray(new String[0])); @@ -203,7 +228,7 @@ class DefaultStatementFactory implements StatementFactory { update = updateBuilder.build(); } - return new DefaultPreparedOperation<>(update, renderContext, binding); + return new DefaultPreparedOperation<>(update, renderContext, createBindings(binding)); }); } @@ -241,7 +266,7 @@ class DefaultStatementFactory implements StatementFactory { delete = deleteBuilder.build(); } - return new DefaultPreparedOperation<>(delete, renderContext, binding); + return new DefaultPreparedOperation<>(delete, renderContext, createBindings(binding)); }); } @@ -411,17 +436,90 @@ class DefaultStatementFactory implements StatementFactory { } } + 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 */ - @RequiredArgsConstructor - static class DefaultPreparedOperation implements PreparedOperation { + static class DefaultPreparedOperation extends PreparedOperationSupport { private final T source; private final RenderContext renderContext; - private final Binding binding; + + DefaultPreparedOperation(T source, RenderContext renderContext, Bindings bindings) { + + super(bindings); + + this.source = source; + this.renderContext = renderContext; + } /* * (non-Javadoc) @@ -432,12 +530,8 @@ class DefaultStatementFactory implements StatementFactory { 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); @@ -460,15 +554,5 @@ class DefaultStatementFactory implements StatementFactory { throw new IllegalStateException("Cannot render " + this.getSource()); } - /* - * (non-Javadoc) - * @see org.springframework.data.r2dbc.function.PreparedOperation#bind(io.r2dbc.spi.Statement) - */ - @Override - public Statement bind(Statement to) { - - binding.apply(to); - return to; - } } } diff --git a/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java new file mode 100644 index 0000000..a4d0e2b --- /dev/null +++ b/src/main/java/org/springframework/data/r2dbc/function/ExpandedPreparedOperation.java @@ -0,0 +1,121 @@ +/* + * 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/OldParameterbindingPreparedOperation.java b/src/main/java/org/springframework/data/r2dbc/function/OldParameterbindingPreparedOperation.java new file mode 100644 index 0000000..1ea9963 --- /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()); + } + }); + } +} 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 865ab26..4730884 100644 --- a/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java +++ b/src/main/java/org/springframework/data/r2dbc/function/PreparedOperation.java @@ -15,8 +15,10 @@ */ package org.springframework.data.r2dbc.function; +import io.r2dbc.spi.Connection; import io.r2dbc.spi.Statement; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -39,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 to the target statement to bind parameters to. + * @param connection the {@link Connection} used for constructing a statement * @return the bound statement. */ - Statement bind(Statement to); + Statement createBoundStatement(Connection connection); + + void addSqlFilter(Function filter); + + void addBindingFilter(Function filter); } 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 b658745..a873e80 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 @@ package org.springframework.data.r2dbc.function; 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 class StatementFactoryUnitTests { assertThat(select.getSource()).isInstanceOf(Select.class); assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo"); - select.bind(statementMock); + select.createBoundStatement(connectionMock); + verifyZeroInteractions(statementMock); } @@ -69,7 +76,8 @@ 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.bind(statementMock); + select.createBoundStatement(connectionMock); + verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); } @@ -85,7 +93,8 @@ 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.bind(statementMock); + select.createBoundStatement(connectionMock); + verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -101,7 +110,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.bind(statementMock); + select.createBoundStatement(connectionMock); verifyZeroInteractions(statementMock); } @@ -115,7 +124,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.bind(statementMock); + select.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -138,7 +147,7 @@ public class StatementFactoryUnitTests { assertThat(insert.getSource()).isInstanceOf(Insert.class); assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES ($1)"); - insert.bind(statementMock); + insert.createBoundStatement(connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).returnGeneratedValues(any(String[].class)); verifyNoMoreInteractions(statementMock); @@ -161,7 +170,7 @@ public class StatementFactoryUnitTests { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.bind(statementMock); + update.createBoundStatement(connectionMock); verify(statementMock).bind(0, "Foo"); verifyNoMoreInteractions(statementMock); } @@ -176,7 +185,7 @@ public class StatementFactoryUnitTests { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1"); - update.bind(statementMock); + update.createBoundStatement(connectionMock); verify(statementMock).bindNull(0, String.class); verifyNoMoreInteractions(statementMock); @@ -193,7 +202,7 @@ public class StatementFactoryUnitTests { assertThat(update.getSource()).isInstanceOf(Update.class); assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1 WHERE foo.baz = $2"); - update.bind(statementMock); + update.createBoundStatement(connectionMock); verify(statementMock).bind(0, "Foo"); verify(statementMock).bind(1, "Baz"); verifyNoMoreInteractions(statementMock); @@ -209,7 +218,7 @@ public class StatementFactoryUnitTests { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1"); - delete.bind(statementMock); + delete.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verifyNoMoreInteractions(statementMock); } @@ -225,7 +234,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.bind(statementMock); + delete.createBoundStatement(connectionMock); verify(statementMock).bind(0, "John"); verify(statementMock).bind(1, "Jake"); verifyNoMoreInteractions(statementMock); @@ -241,7 +250,7 @@ public class StatementFactoryUnitTests { assertThat(delete.getSource()).isInstanceOf(Delete.class); assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe IS NULL"); - delete.bind(statementMock); + delete.createBoundStatement(connectionMock); verifyZeroInteractions(statementMock); } }