#73 - Introduce PreparedOperation.
We now encapsulate prepared operations from the StatementFactory within PreparedOperation that renders SQL and provides binding values.
StatementFactory supports SELECT/INSERT/UPDATE/DELETE statement creation considering Dialect-specific rendering.
StatementFactory replaces String-based statement methods in ReactiveDataAccessStrategy.
PreparedOperation<Update> operation = accessStrategy.getStatements().update(entity.getTableName(), binder -> {
binder.bind("name", "updated value");
binder.filterBy("id", SettableValue.from(42));
});
databaseClient.execute().sql(operation).then();
Original pull request: #82.
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
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.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultReactiveDataAccessStrategy}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class DefaultReactiveDataAccessStrategyUnitTests {
|
||||
|
||||
DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE);
|
||||
|
||||
@Test // gh-20
|
||||
public void shouldRenderInsertAndReturnGeneratedKeysQuery() {
|
||||
|
||||
BindableOperation operation = strategy.insertAndReturnGeneratedKeys("table",
|
||||
new HashSet<>(Arrays.asList("firstname", "lastname")));
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("INSERT INTO table (firstname, lastname) VALUES($1, $2)");
|
||||
}
|
||||
|
||||
@Test // gh-20
|
||||
public void shouldRenderUpdateByIdQuery() {
|
||||
|
||||
BindableOperation operation = strategy.updateById("table", new HashSet<>(Arrays.asList("firstname", "lastname")),
|
||||
"id");
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("UPDATE table SET firstname = $2, lastname = $3 WHERE id = $1");
|
||||
}
|
||||
|
||||
@Test // gh-20
|
||||
public void shouldRenderDeleteByIdQuery() {
|
||||
|
||||
BindableOperation operation = strategy.deleteById("table", "id");
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("DELETE FROM table WHERE id = $1");
|
||||
}
|
||||
|
||||
@Test // gh-20
|
||||
public void shouldRenderDeleteByIdInQuery() {
|
||||
|
||||
Statement statement = mock(Statement.class);
|
||||
BindIdOperation operation = strategy.deleteByIdIn("table", "id");
|
||||
|
||||
operation.bindId(statement, Collections.singleton("foo"));
|
||||
assertThat(operation.toQuery()).isEqualTo("DELETE FROM table WHERE id IN ($1)");
|
||||
|
||||
operation.bindId(statement, "bar");
|
||||
assertThat(operation.toQuery()).isEqualTo("DELETE FROM table WHERE id IN ($1, $2)");
|
||||
}
|
||||
|
||||
@Test // gh-22
|
||||
public void shouldUpdateArray() {
|
||||
|
||||
Map<String, SettableValue> columnsToUpdate = strategy
|
||||
.getOutboundRow(new WithCollectionTypes(new String[] { "one", "two" }, null));
|
||||
|
||||
Object stringArray = columnsToUpdate.get("string_array").getValue();
|
||||
|
||||
assertThat(stringArray).isInstanceOf(String[].class);
|
||||
assertThat((String[]) stringArray).hasSize(2).contains("one", "two");
|
||||
}
|
||||
|
||||
@Test // gh-22
|
||||
public void shouldConvertListToArray() {
|
||||
|
||||
Map<String, SettableValue> columnsToUpdate = strategy
|
||||
.getOutboundRow(new WithCollectionTypes(null, Arrays.asList("one", "two")));
|
||||
|
||||
Object stringArray = columnsToUpdate.get("string_collection").getValue();
|
||||
|
||||
assertThat(stringArray).isInstanceOf(String[].class);
|
||||
assertThat((String[]) stringArray).hasSize(2).contains("one", "two");
|
||||
}
|
||||
|
||||
static class WithCollectionTypes {
|
||||
|
||||
String[] stringArray;
|
||||
|
||||
List<String> stringCollection;
|
||||
|
||||
WithCollectionTypes(String[] stringArray, List<String> stringCollection) {
|
||||
|
||||
this.stringArray = stringArray;
|
||||
this.stringCollection = stringCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.r2dbc.spi.Statement;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.domain.SettableValue;
|
||||
import org.springframework.data.relational.core.dialect.RenderContextFactory;
|
||||
import org.springframework.data.relational.core.sql.Delete;
|
||||
import org.springframework.data.relational.core.sql.Insert;
|
||||
import org.springframework.data.relational.core.sql.Select;
|
||||
import org.springframework.data.relational.core.sql.Update;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link StatementFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class StatementFactoryUnitTests {
|
||||
|
||||
// See https://github.com/spring-projects/spring-data-r2dbc/issues/55
|
||||
DefaultStatementFactory statements = new DefaultStatementFactory(PostgresDialect.INSTANCE,
|
||||
new RenderContextFactory(org.springframework.data.relational.core.dialect.PostgresDialect.INSTANCE)
|
||||
.createRenderContext());
|
||||
|
||||
Statement statementMock = mock(Statement.class);
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleSelectWithoutBindings() {
|
||||
|
||||
PreparedOperation<Select> select = statements.select("foo", Arrays.asList("bar", "baz"), it -> {});
|
||||
|
||||
assertThat(select.getSource()).isInstanceOf(Select.class);
|
||||
assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo");
|
||||
|
||||
select.bind(statementMock);
|
||||
verifyZeroInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleSelectWithSimpleFilter() {
|
||||
|
||||
PreparedOperation<Select> select = statements.select("foo", Arrays.asList("bar", "baz"), it -> {
|
||||
it.filterBy("doe", SettableValue.from("John"));
|
||||
});
|
||||
|
||||
assertThat(select.getSource()).isInstanceOf(Select.class);
|
||||
assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1");
|
||||
|
||||
select.bind(statementMock);
|
||||
verify(statementMock).bind(0, "John");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleSelectWithMultipleFilters() {
|
||||
|
||||
PreparedOperation<Select> select = statements.select("foo", Arrays.asList("bar", "baz"), it -> {
|
||||
it.filterBy("doe", SettableValue.from("John"));
|
||||
it.filterBy("baz", SettableValue.from("Jake"));
|
||||
});
|
||||
|
||||
assertThat(select.getSource()).isInstanceOf(Select.class);
|
||||
assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe = $1 AND foo.baz = $2");
|
||||
|
||||
select.bind(statementMock);
|
||||
verify(statementMock).bind(0, "John");
|
||||
verify(statementMock).bind(1, "Jake");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleSelectWithNullFilter() {
|
||||
|
||||
PreparedOperation<Select> select = statements.select("foo", Arrays.asList("bar", "baz"), it -> {
|
||||
it.filterBy("doe", SettableValue.empty(String.class));
|
||||
});
|
||||
|
||||
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);
|
||||
verifyZeroInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleSelectWithIterableFilter() {
|
||||
|
||||
PreparedOperation<Select> select = statements.select("foo", Arrays.asList("bar", "baz"), it -> {
|
||||
it.filterBy("doe", SettableValue.from(Arrays.asList("John", "Jake")));
|
||||
});
|
||||
|
||||
assertThat(select.getSource()).isInstanceOf(Select.class);
|
||||
assertThat(select.toQuery()).isEqualTo("SELECT foo.bar, foo.baz FROM foo WHERE foo.doe IN ($1, $2)");
|
||||
|
||||
select.bind(statementMock);
|
||||
verify(statementMock).bind(0, "John");
|
||||
verify(statementMock).bind(1, "Jake");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailInsertToQueryingWithoutValueBindings() {
|
||||
|
||||
assertThatThrownBy(() -> statements.insert("foo", Collections.emptyList(), it -> {}))
|
||||
.isInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleInsert() {
|
||||
|
||||
PreparedOperation<Insert> insert = statements.insert("foo", Collections.emptyList(), it -> {
|
||||
it.bind("bar", SettableValue.from("Foo"));
|
||||
});
|
||||
|
||||
assertThat(insert.getSource()).isInstanceOf(Insert.class);
|
||||
assertThat(insert.toQuery()).isEqualTo("INSERT INTO foo (bar) VALUES ($1)");
|
||||
|
||||
insert.bind(statementMock);
|
||||
verify(statementMock).bind(0, "Foo");
|
||||
verify(statementMock).returnGeneratedValues(any(String[].class));
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailUpdateToQueryingWithoutValueBindings() {
|
||||
|
||||
assertThatThrownBy(() -> statements.update("foo", it -> it.filterBy("foo", SettableValue.empty(Object.class))))
|
||||
.isInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleUpdate() {
|
||||
|
||||
PreparedOperation<Update> update = statements.update("foo", it -> {
|
||||
it.bind("bar", SettableValue.from("Foo"));
|
||||
});
|
||||
|
||||
assertThat(update.getSource()).isInstanceOf(Update.class);
|
||||
assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1");
|
||||
|
||||
update.bind(statementMock);
|
||||
verify(statementMock).bind(0, "Foo");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQueryNullUpdate() {
|
||||
|
||||
PreparedOperation<Update> update = statements.update("foo", it -> {
|
||||
it.bind("bar", SettableValue.empty(String.class));
|
||||
});
|
||||
|
||||
assertThat(update.getSource()).isInstanceOf(Update.class);
|
||||
assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1");
|
||||
|
||||
update.bind(statementMock);
|
||||
verify(statementMock).bindNull(0, String.class);
|
||||
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQueryUpdateWithFilter() {
|
||||
|
||||
PreparedOperation<Update> update = statements.update("foo", it -> {
|
||||
it.bind("bar", SettableValue.from("Foo"));
|
||||
it.filterBy("baz", SettableValue.from("Baz"));
|
||||
});
|
||||
|
||||
assertThat(update.getSource()).isInstanceOf(Update.class);
|
||||
assertThat(update.toQuery()).isEqualTo("UPDATE foo SET bar = $1 WHERE foo.baz = $2");
|
||||
|
||||
update.bind(statementMock);
|
||||
verify(statementMock).bind(0, "Foo");
|
||||
verify(statementMock).bind(1, "Baz");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleDeleteWithSimpleFilter() {
|
||||
|
||||
PreparedOperation<Delete> delete = statements.delete("foo", it -> {
|
||||
it.filterBy("doe", SettableValue.from("John"));
|
||||
});
|
||||
|
||||
assertThat(delete.getSource()).isInstanceOf(Delete.class);
|
||||
assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1");
|
||||
|
||||
delete.bind(statementMock);
|
||||
verify(statementMock).bind(0, "John");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleDeleteWithMultipleFilters() {
|
||||
|
||||
PreparedOperation<Delete> delete = statements.delete("foo", it -> {
|
||||
it.filterBy("doe", SettableValue.from("John"));
|
||||
it.filterBy("baz", SettableValue.from("Jake"));
|
||||
});
|
||||
|
||||
assertThat(delete.getSource()).isInstanceOf(Delete.class);
|
||||
assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe = $1 AND foo.baz = $2");
|
||||
|
||||
delete.bind(statementMock);
|
||||
verify(statementMock).bind(0, "John");
|
||||
verify(statementMock).bind(1, "Jake");
|
||||
verifyNoMoreInteractions(statementMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldToQuerySimpleDeleteWithNullFilter() {
|
||||
|
||||
PreparedOperation<Delete> delete = statements.delete("foo", it -> {
|
||||
it.filterBy("doe", SettableValue.empty(String.class));
|
||||
});
|
||||
|
||||
assertThat(delete.getSource()).isInstanceOf(Delete.class);
|
||||
assertThat(delete.toQuery()).isEqualTo("DELETE FROM foo WHERE foo.doe IS NULL");
|
||||
|
||||
delete.bind(statementMock);
|
||||
verifyZeroInteractions(statementMock);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user