#162 - Refine declaration of nullable values in DatabaseClient.

DatabaseClient.BindSpec.bind(…) (execute) and DatabaseClient.GenericInsertSpec.value(…) (insert) now consistently accept SettableValue for scalar and absent values.

This change allows us to provide a Kotlin extension leveraging reified generics to provide the type of a value even if it is null to construct an appropriate SettableValue for fluent API usage.
This commit is contained in:
Mark Paluch
2019-09-03 16:23:48 +02:00
parent bac5c343b7
commit 30c5e2f5e0
6 changed files with 229 additions and 68 deletions

View File

@@ -172,7 +172,7 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
databaseClient.insert().into("legoset")//
.value("id", 42055) //
.value("name", "SCHAUFELRADBAGGER") //
.nullValue("manual") //
.nullValue("manual", Integer.class) //
.fetch() //
.rowsUpdated() //
.as(StepVerifier::create) //
@@ -190,7 +190,7 @@ public abstract class AbstractDatabaseClientIntegrationTests extends R2dbcIntegr
databaseClient.insert().into("legoset")//
.value("id", 42055) //
.value("name", "SCHAUFELRADBAGGER") //
.nullValue("manual") //
.nullValue("manual", Integer.class) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();

View File

@@ -34,6 +34,7 @@ import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.SettableValue;
import org.springframework.data.r2dbc.support.R2dbcExceptionTranslator;
/**
@@ -118,6 +119,34 @@ public class DefaultDatabaseClientUnitTests {
verify(statement).bindNull("$1", String.class);
}
@Test // gh-162
public void executeShouldBindSettableValues() {
Statement statement = mock(Statement.class);
when(connection.createStatement("SELECT * FROM table WHERE key = $1")).thenReturn(statement);
when(statement.execute()).thenReturn(Mono.empty());
DefaultDatabaseClient databaseClient = (DefaultDatabaseClient) DatabaseClient.builder()
.connectionFactory(connectionFactory)
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)).build();
databaseClient.execute("SELECT * FROM table WHERE key = $1") //
.bind(0, SettableValue.empty(String.class)) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
verify(statement).bindNull(0, String.class);
databaseClient.execute("SELECT * FROM table WHERE key = $1") //
.bind("$1", SettableValue.empty(String.class)) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
verify(statement).bindNull("$1", String.class);
}
@Test // gh-128
public void executeShouldBindNamedNullValues() {
@@ -138,7 +167,7 @@ public class DefaultDatabaseClientUnitTests {
verify(statement).bindNull(0, String.class);
}
@Test // gh-128
@Test // gh-128, gh-162
public void executeShouldBindValues() {
Statement statement = mock(Statement.class);
@@ -150,7 +179,7 @@ public class DefaultDatabaseClientUnitTests {
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)).build();
databaseClient.execute("SELECT * FROM table WHERE key = $1") //
.bind(0, "foo") //
.bind(0, SettableValue.from("foo")) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
@@ -166,6 +195,52 @@ public class DefaultDatabaseClientUnitTests {
verify(statement).bind("$1", "foo");
}
@Test // gh-162
public void insertShouldAcceptNullValues() {
Statement statement = mock(Statement.class);
when(connection.createStatement("INSERT INTO foo (first, second) VALUES ($1, $2)")).thenReturn(statement);
when(statement.returnGeneratedValues()).thenReturn(statement);
when(statement.execute()).thenReturn(Mono.empty());
DefaultDatabaseClient databaseClient = (DefaultDatabaseClient) DatabaseClient.builder()
.connectionFactory(connectionFactory)
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)).build();
databaseClient.insert().into("foo") //
.value("first", "foo") //
.nullValue("second", Integer.class) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
verify(statement).bind(0, "foo");
verify(statement).bindNull(1, Integer.class);
}
@Test // gh-162
public void insertShouldAcceptSettableValue() {
Statement statement = mock(Statement.class);
when(connection.createStatement("INSERT INTO foo (first, second) VALUES ($1, $2)")).thenReturn(statement);
when(statement.returnGeneratedValues()).thenReturn(statement);
when(statement.execute()).thenReturn(Mono.empty());
DefaultDatabaseClient databaseClient = (DefaultDatabaseClient) DatabaseClient.builder()
.connectionFactory(connectionFactory)
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)).build();
databaseClient.insert().into("foo") //
.value("first", SettableValue.from("foo")) //
.value("second", SettableValue.empty(Integer.class)) //
.then() //
.as(StepVerifier::create) //
.verifyComplete();
verify(statement).bind(0, "foo");
verify(statement).bindNull(1, Integer.class);
}
@Test // gh-128
public void executeShouldBindNamedValuesByIndex() {

View File

@@ -21,6 +21,7 @@ import io.mockk.verify
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.springframework.data.r2dbc.mapping.SettableValue
import reactor.core.publisher.Mono
/**
@@ -32,6 +33,66 @@ import reactor.core.publisher.Mono
*/
class DatabaseClientExtensionsTests {
@Test // gh-162
fun bindByIndexShouldBindValue() {
val spec = mockk<DatabaseClient.GenericExecuteSpec>()
every { spec.bind(eq(0), any()) } returns spec
runBlocking {
spec.bind<String>(0, "foo")
}
verify {
spec.bind(0, SettableValue.fromOrEmpty("foo", String::class.java))
}
}
@Test // gh-162
fun bindByIndexShouldBindNull() {
val spec = mockk<DatabaseClient.GenericExecuteSpec>()
every { spec.bind(eq(0), any()) } returns spec
runBlocking {
spec.bind<String>(0, null)
}
verify {
spec.bind(0, SettableValue.empty(String::class.java))
}
}
@Test // gh-162
fun bindByNameShouldBindValue() {
val spec = mockk<DatabaseClient.GenericExecuteSpec>()
every { spec.bind(eq("field"), any()) } returns spec
runBlocking {
spec.bind<String>("field", "foo")
}
verify {
spec.bind("field", SettableValue.fromOrEmpty("foo", String::class.java))
}
}
@Test // gh-162
fun bindByNameShouldBindNull() {
val spec = mockk<DatabaseClient.GenericExecuteSpec>()
every { spec.bind(eq("field"), any()) } returns spec
runBlocking {
spec.bind<String>("field", null)
}
verify {
spec.bind("field", SettableValue.empty(String::class.java))
}
}
@Test // gh-63
fun genericExecuteSpecAwait() {
@@ -140,6 +201,36 @@ class DatabaseClientExtensionsTests {
}
}
@Test // gh-162
fun insertValueShouldBindValue() {
val spec = mockk<DatabaseClient.GenericInsertSpec<Any>>()
every { spec.value(eq("field"), any()) } returns spec
runBlocking {
spec.value<String>("field", "foo")
}
verify {
spec.value("field", SettableValue.fromOrEmpty("foo", String::class.java))
}
}
@Test // gh-162
fun insertValueShouldBindNull() {
val spec = mockk<DatabaseClient.GenericInsertSpec<Any>>()
every { spec.value(eq("field"), any()) } returns spec
runBlocking {
spec.value<String>("field", null)
}
verify {
spec.value("field", SettableValue.empty(String::class.java))
}
}
@Test // gh-122
fun selectFromSpecFrom() {