#368 - Use Spring R2DBC.
We now use Spring R2DBC DatabaseClient and utilities to implement Spring Data R2DBC functionality. Original pull request: #412.
This commit is contained in:
@@ -32,13 +32,13 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.repository.Query;
|
||||
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
import org.springframework.data.r2dbc.testing.H2TestSupport;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@@ -70,9 +70,8 @@ public class H2IntegrationTests {
|
||||
|
||||
jdbc.execute("INSERT INTO legoset (id, name, manual) VALUES(42055, 'SCHAUFELRADBAGGER', 12)");
|
||||
|
||||
databaseClient.execute("SELECT COUNT(*) FROM legoset") //
|
||||
.as(Long.class) //
|
||||
.fetch() //
|
||||
databaseClient.sql("SELECT COUNT(*) FROM legoset") //
|
||||
.map(it -> it.get(0, Long.class)) //
|
||||
.all() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(1L) //
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractR2dbcConfiguration}.
|
||||
|
||||
@@ -1,488 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 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.connectionfactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.IsolationLevel;
|
||||
import io.r2dbc.spi.R2dbcBadGrammarException;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.data.r2dbc.BadSqlGrammarException;
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.reactive.TransactionSynchronization;
|
||||
import org.springframework.transaction.reactive.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link R2dbcTransactionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class R2dbcTransactionManagerUnitTests {
|
||||
|
||||
ConnectionFactory connectionFactoryMock = mock(ConnectionFactory.class);
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
|
||||
private R2dbcTransactionManager tm;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
when(connectionFactoryMock.create()).thenReturn((Mono) Mono.just(connectionMock));
|
||||
when(connectionMock.beginTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock.close()).thenReturn(Mono.empty());
|
||||
tm = new R2dbcTransactionManager(connectionFactoryMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testSimpleTransaction() {
|
||||
|
||||
TestTransactionSynchronization sync = new TestTransactionSynchronization(
|
||||
TransactionSynchronization.STATUS_COMMITTED);
|
||||
AtomicInteger commits = new AtomicInteger();
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.fromRunnable(commits::incrementAndGet));
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).flatMap(it -> {
|
||||
|
||||
return TransactionSynchronizationManager.forCurrentTransaction()
|
||||
.doOnNext(synchronizationManager -> synchronizationManager.registerSynchronization(sync));
|
||||
|
||||
}) //
|
||||
.as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
assertThat(commits).hasValue(1);
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
|
||||
assertThat(sync.beforeCommitCalled).isTrue();
|
||||
assertThat(sync.afterCommitCalled).isTrue();
|
||||
assertThat(sync.beforeCompletionCalled).isTrue();
|
||||
assertThat(sync.afterCompletionCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-329
|
||||
public void testBeginFails() {
|
||||
|
||||
reset(connectionFactoryMock);
|
||||
when(connectionFactoryMock.create()).thenReturn(Mono.error(new R2dbcBadGrammarException("fail")));
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectErrorSatisfies(actual -> {
|
||||
|
||||
assertThat(actual).isInstanceOf(CannotCreateTransactionException.class)
|
||||
.hasCauseInstanceOf(BadSqlGrammarException.class);
|
||||
|
||||
}).verify();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void appliesIsolationLevel() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock.getTransactionIsolationLevel()).thenReturn(IsolationLevel.READ_COMMITTED);
|
||||
when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).setTransactionIsolationLevel(IsolationLevel.READ_COMMITTED);
|
||||
verify(connectionMock).setTransactionIsolationLevel(IsolationLevel.SERIALIZABLE);
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test // gh-184
|
||||
public void doesNotSetIsolationLevelIfMatch() {
|
||||
|
||||
when(connectionMock.getTransactionIsolationLevel()).thenReturn(IsolationLevel.READ_COMMITTED);
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock, never()).setTransactionIsolationLevel(any());
|
||||
verify(connectionMock).commitTransaction();
|
||||
}
|
||||
|
||||
@Test // gh-184
|
||||
public void doesNotSetAutoCommitDisabled() {
|
||||
|
||||
when(connectionMock.isAutoCommit()).thenReturn(false);
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock, never()).setAutoCommit(anyBoolean());
|
||||
verify(connectionMock).commitTransaction();
|
||||
}
|
||||
|
||||
@Test // gh-184
|
||||
public void restoresAutoCommit() {
|
||||
|
||||
when(connectionMock.isAutoCommit()).thenReturn(true);
|
||||
when(connectionMock.setAutoCommit(anyBoolean())).thenReturn(Mono.empty());
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).setAutoCommit(false);
|
||||
verify(connectionMock).setAutoCommit(true);
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void appliesReadOnly() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty());
|
||||
Statement statement = mock(Statement.class);
|
||||
when(connectionMock.createStatement(anyString())).thenReturn(statement);
|
||||
when(statement.execute()).thenReturn(Mono.empty());
|
||||
tm.setEnforceReadOnly(true);
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setReadOnly(true);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).createStatement("SET TRANSACTION READ ONLY");
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testCommitFails() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.defer(() -> {
|
||||
return Mono.error(new R2dbcBadGrammarException("Commit should fail"));
|
||||
}));
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock) //
|
||||
.doOnNext(it -> {
|
||||
it.createStatement("foo");
|
||||
}).then() //
|
||||
.as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyError(IllegalTransactionStateException.class);
|
||||
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).createStatement("foo");
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testRollback() {
|
||||
|
||||
AtomicInteger commits = new AtomicInteger();
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.fromRunnable(commits::incrementAndGet));
|
||||
|
||||
AtomicInteger rollbacks = new AtomicInteger();
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.fromRunnable(rollbacks::incrementAndGet));
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock).doOnNext(it -> {
|
||||
|
||||
throw new IllegalStateException();
|
||||
|
||||
}).as(operator::transactional) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyError(IllegalStateException.class);
|
||||
|
||||
assertThat(commits).hasValue(0);
|
||||
assertThat(rollbacks).hasValue(1);
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-329
|
||||
public void testRollbackFails() {
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.defer(() -> {
|
||||
return Mono.error(new R2dbcBadGrammarException("Commit should fail"));
|
||||
}), Mono.empty());
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
operator.execute(reactiveTransaction -> {
|
||||
|
||||
reactiveTransaction.setRollbackOnly();
|
||||
|
||||
return ConnectionFactoryUtils.getConnection(connectionFactoryMock) //
|
||||
.doOnNext(it -> {
|
||||
it.createStatement("foo");
|
||||
}).then();
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyError(IllegalTransactionStateException.class);
|
||||
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).createStatement("foo");
|
||||
verify(connectionMock, never()).commitTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testTransactionSetRollbackOnly() {
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
TestTransactionSynchronization sync = new TestTransactionSynchronization(
|
||||
TransactionSynchronization.STATUS_ROLLED_BACK);
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
operator.execute(tx -> {
|
||||
|
||||
tx.setRollbackOnly();
|
||||
assertThat(tx.isNewTransaction()).isTrue();
|
||||
|
||||
return TransactionSynchronizationManager.forCurrentTransaction().doOnNext(it -> {
|
||||
|
||||
assertThat(it.hasResource(connectionFactoryMock)).isTrue();
|
||||
it.registerSynchronization(sync);
|
||||
|
||||
}).then();
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
|
||||
assertThat(sync.beforeCommitCalled).isFalse();
|
||||
assertThat(sync.afterCommitCalled).isFalse();
|
||||
assertThat(sync.beforeCompletionCalled).isTrue();
|
||||
assertThat(sync.afterCompletionCalled).isTrue();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testPropagationNeverWithExistingTransaction() {
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
operator.execute(tx1 -> {
|
||||
|
||||
assertThat(tx1.isNewTransaction()).isTrue();
|
||||
|
||||
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
|
||||
return operator.execute(tx2 -> {
|
||||
|
||||
fail("Should have thrown IllegalTransactionStateException");
|
||||
return Mono.empty();
|
||||
});
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyError(IllegalTransactionStateException.class);
|
||||
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test // gh-107
|
||||
public void testPropagationSupportsAndRequiresNew() {
|
||||
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm, definition);
|
||||
|
||||
operator.execute(tx1 -> {
|
||||
|
||||
assertThat(tx1.isNewTransaction()).isFalse();
|
||||
|
||||
DefaultTransactionDefinition innerDef = new DefaultTransactionDefinition();
|
||||
innerDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
|
||||
TransactionalOperator inner = TransactionalOperator.create(tm, innerDef);
|
||||
|
||||
return inner.execute(tx2 -> {
|
||||
|
||||
assertThat(tx2.isNewTransaction()).isTrue();
|
||||
return Mono.empty();
|
||||
});
|
||||
}).as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock).commitTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
private static class TestTransactionSynchronization implements TransactionSynchronization {
|
||||
|
||||
private int status;
|
||||
|
||||
public boolean beforeCommitCalled;
|
||||
|
||||
public boolean beforeCompletionCalled;
|
||||
|
||||
public boolean afterCommitCalled;
|
||||
|
||||
public boolean afterCompletionCalled;
|
||||
|
||||
public Throwable afterCompletionException;
|
||||
|
||||
public TestTransactionSynchronization(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> suspend() {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> resume() {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> beforeCommit(boolean readOnly) {
|
||||
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
|
||||
fail("Should never be called");
|
||||
}
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertFalse(this.beforeCommitCalled);
|
||||
this.beforeCommitCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> beforeCompletion() {
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertFalse(this.beforeCompletionCalled);
|
||||
this.beforeCompletionCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> afterCommit() {
|
||||
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
|
||||
fail("Should never be called");
|
||||
}
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertFalse(this.afterCommitCalled);
|
||||
this.afterCommitCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> afterCompletion(int status) {
|
||||
try {
|
||||
return Mono.fromRunnable(() -> doAfterCompletion(status));
|
||||
} catch (Throwable ex) {
|
||||
this.afterCompletionException = ex;
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
protected void doAfterCompletion(int status) {
|
||||
assertFalse(this.afterCompletionCalled);
|
||||
this.afterCompletionCalled = true;
|
||||
assertTrue(status == this.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,23 +100,6 @@ public class SingleConnectionConnectionFactoryUnitTests {
|
||||
factory.destroy();
|
||||
}
|
||||
|
||||
@Test // gh-204
|
||||
public void releaseConnectionShouldNotCloseConnection() {
|
||||
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
ConnectionFactoryMetadata metadata = mock(ConnectionFactoryMetadata.class);
|
||||
|
||||
SingleConnectionConnectionFactory factory = new SingleConnectionConnectionFactory(connectionMock, metadata, false);
|
||||
|
||||
Connection connection = factory.create().block();
|
||||
|
||||
ConnectionFactoryUtils.releaseConnection(connection, factory) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock, never()).close();
|
||||
}
|
||||
|
||||
@Test // gh-204
|
||||
public void releaseConnectionShouldCloseUnrelatedConnection() {
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MappingR2dbcConverter}.
|
||||
@@ -72,12 +72,12 @@ public class MappingR2dbcConverterUnitTests {
|
||||
LocalDateTime localDateTime = LocalDateTime.now();
|
||||
converter.write(new Person("id", "Walter", "White", instant, localDateTime), row);
|
||||
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("id"), SettableValue.fromOrEmpty("id", String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("id"), Parameter.fromOrEmpty("id", String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("firstname"),
|
||||
SettableValue.fromOrEmpty("Walter", String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("lastname"), SettableValue.fromOrEmpty("White", String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("instant"), SettableValue.from(instant));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("local_date_time"), SettableValue.from(localDateTime));
|
||||
Parameter.fromOrEmpty("Walter", String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("lastname"), Parameter.fromOrEmpty("White", String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("instant"), Parameter.from(instant));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("local_date_time"), Parameter.from(localDateTime));
|
||||
}
|
||||
|
||||
@Test // gh-41
|
||||
@@ -117,7 +117,7 @@ public class MappingR2dbcConverterUnitTests {
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(withMap, row);
|
||||
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("nested"), SettableValue.from("map"));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("nested"), Parameter.from("map"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
@@ -138,7 +138,7 @@ public class MappingR2dbcConverterUnitTests {
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(withMap, row);
|
||||
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), SettableValue.from("Mint"));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), Parameter.from("Mint"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
@@ -148,7 +148,7 @@ public class MappingR2dbcConverterUnitTests {
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(withMap, row);
|
||||
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), SettableValue.fromOrEmpty(null, String.class));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("condition"), Parameter.fromOrEmpty(null, String.class));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
@@ -172,8 +172,8 @@ public class MappingR2dbcConverterUnitTests {
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(person, row);
|
||||
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("foo_column"), SettableValue.from("bar"))
|
||||
.containsEntry(SqlIdentifier.unquoted("entity"), SettableValue.from("nested_entity"));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("foo_column"), Parameter.from("bar"))
|
||||
.containsEntry(SqlIdentifier.unquoted("entity"), Parameter.from("nested_entity"));
|
||||
}
|
||||
|
||||
@Test // gh-59
|
||||
@@ -263,8 +263,8 @@ public class MappingR2dbcConverterUnitTests {
|
||||
public OutboundRow convert(CustomConversionPerson source) {
|
||||
|
||||
OutboundRow row = new OutboundRow();
|
||||
row.put("foo_column", SettableValue.from(source.foo));
|
||||
row.put("entity", SettableValue.from("nested_entity"));
|
||||
row.put("foo_column", Parameter.from(source.foo));
|
||||
row.put("entity", Parameter.from("nested_entity"));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Postgres-specific unit tests for {@link MappingR2dbcConverter}.
|
||||
@@ -69,7 +69,7 @@ public class PostgresMappingR2dbcConverterUnitTests {
|
||||
OutboundRow row = new OutboundRow();
|
||||
converter.write(person, row);
|
||||
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("json_value"), SettableValue.from(person.jsonValue));
|
||||
assertThat(row).containsEntry(SqlIdentifier.unquoted("json_value"), Parameter.from(person.jsonValue));
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -36,9 +36,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
|
||||
import org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager;
|
||||
import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.r2dbc.connection.R2dbcTransactionManager;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -146,7 +147,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
public void executeInsertInManagedTransaction() {
|
||||
|
||||
Flux<Integer> integerFlux = databaseClient //
|
||||
.execute(getInsertIntoLegosetStatement()) //
|
||||
.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
@@ -162,7 +163,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Test // gh-2
|
||||
public void executeInsertInAutoCommitTransaction() {
|
||||
|
||||
Flux<Integer> integerFlux = databaseClient.execute(getInsertIntoLegosetStatement()) //
|
||||
Flux<Integer> integerFlux = databaseClient.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
@@ -178,7 +179,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Test // gh-2
|
||||
public void shouldRollbackTransaction() {
|
||||
|
||||
Mono<Object> integerFlux = databaseClient.execute(getInsertIntoLegosetStatement()) //
|
||||
Mono<Object> integerFlux = databaseClient.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
@@ -196,7 +197,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Test // gh-2, gh-75, gh-107
|
||||
public void emitTransactionIds() {
|
||||
|
||||
Flux<Object> txId = databaseClient.execute(getCurrentTransactionIdStatement()) //
|
||||
Flux<Object> txId = databaseClient.sql(getCurrentTransactionIdStatement()) //
|
||||
.map((row, md) -> row.get(0)) //
|
||||
.all();
|
||||
|
||||
@@ -220,7 +221,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
TransactionalOperator transactionalOperator = TransactionalOperator
|
||||
.create(new R2dbcTransactionManager(connectionFactory), new DefaultTransactionDefinition());
|
||||
|
||||
Flux<Integer> integerFlux = databaseClient.execute(getInsertIntoLegosetStatement()) //
|
||||
Flux<Integer> integerFlux = databaseClient.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
@@ -301,7 +302,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Transactional
|
||||
public Flux<Object> emitTransactionIds(Mono<Void> prepareTransaction, String idStatement) {
|
||||
|
||||
Flux<Object> txId = databaseClient.execute(idStatement) //
|
||||
Flux<Object> txId = databaseClient.sql(idStatement) //
|
||||
.map((row, md) -> row.get(0)) //
|
||||
.all();
|
||||
|
||||
@@ -311,7 +312,7 @@ public abstract class AbstractTransactionalDatabaseClientIntegrationTests extend
|
||||
@Transactional
|
||||
public Flux<Integer> shouldRollbackTransactionUsingTransactionalOperator(String insertStatement) {
|
||||
|
||||
return databaseClient.execute(insertStatement) //
|
||||
return databaseClient.sql(insertStatement) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.ClassRule;
|
||||
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase;
|
||||
import org.springframework.data.r2dbc.testing.MySqlTestSupport;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Transactional integration tests for {@link DatabaseClient} against MySQL using Jasync MySQL.
|
||||
@@ -62,7 +63,7 @@ public class JasyncMySqlTransactionalDatabaseClientIntegrationTests
|
||||
* batches every now and then.
|
||||
* @see: https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-internal-data.html
|
||||
*/
|
||||
return client.execute(getInsertIntoLegosetStatement()) //
|
||||
return client.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.ClassRule;
|
||||
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase;
|
||||
import org.springframework.data.r2dbc.testing.MariaDbTestSupport;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Transactional integration tests for {@link DatabaseClient} against MariaDb.
|
||||
@@ -61,7 +62,7 @@ public class MariaDbTransactionalDatabaseClientIntegrationTests
|
||||
* And we need to delay emitting the result so that Mariadb has time to write the transaction id, which is done in
|
||||
* batches every now and then.
|
||||
*/
|
||||
return client.execute(getInsertIntoLegosetStatement()) //
|
||||
return client.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.ClassRule;
|
||||
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase;
|
||||
import org.springframework.data.r2dbc.testing.MySqlTestSupport;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Transactional integration tests for {@link DatabaseClient} against MySQL.
|
||||
@@ -62,7 +63,7 @@ public class MySqlTransactionalDatabaseClientIntegrationTests
|
||||
* batches every now and then.
|
||||
* @see: https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-internal-data.html
|
||||
*/
|
||||
return client.execute(getInsertIntoLegosetStatement()) //
|
||||
return client.sql(getInsertIntoLegosetStatement()) //
|
||||
.bind(0, 42055) //
|
||||
.bind(1, "SCHAUFELRADBAGGER") //
|
||||
.bindNull(2, Integer.class) //
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* {@link PostgresDialect} specific tests for {@link ReactiveDataAccessStrategy}.
|
||||
@@ -88,8 +89,8 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
|
||||
OutboundRow outboundRow = strategy.getOutboundRow(withArray);
|
||||
|
||||
assertThat(outboundRow) //
|
||||
.containsEntry(SqlIdentifier.unquoted("string_array"), SettableValue.from(new String[] { "hello", "world" }))
|
||||
.containsEntry(SqlIdentifier.unquoted("string_list"), SettableValue.from(new String[] { "hello", "world" }));
|
||||
.containsEntry(SqlIdentifier.unquoted("string_array"), Parameter.from(new String[] { "hello", "world" }))
|
||||
.containsEntry(SqlIdentifier.unquoted("string_list"), Parameter.from(new String[] { "hello", "world" }));
|
||||
}
|
||||
|
||||
@Test // gh-139
|
||||
@@ -104,7 +105,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
|
||||
OutboundRow outboundRow = strategy.getOutboundRow(withConversion);
|
||||
|
||||
assertThat(outboundRow) //
|
||||
.containsEntry(SqlIdentifier.unquoted("my_objects"), SettableValue.from("[one, two]"));
|
||||
.containsEntry(SqlIdentifier.unquoted("my_objects"), Parameter.from("[one, two]"));
|
||||
}
|
||||
|
||||
@Test // gh-139
|
||||
@@ -121,7 +122,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
|
||||
assertThat(outboundRow) //
|
||||
.containsKey(SqlIdentifier.unquoted("my_objects"));
|
||||
|
||||
SettableValue value = outboundRow.get("my_objects");
|
||||
Parameter value = outboundRow.get("my_objects");
|
||||
assertThat(value.isEmpty()).isTrue();
|
||||
assertThat(value.getType()).isEqualTo(String.class);
|
||||
}
|
||||
@@ -139,7 +140,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
|
||||
|
||||
assertThat(outboundRow).containsKey(SqlIdentifier.unquoted("enum_set"));
|
||||
|
||||
SettableValue value = outboundRow.get(SqlIdentifier.unquoted("enum_set"));
|
||||
Parameter value = outboundRow.get(SqlIdentifier.unquoted("enum_set"));
|
||||
assertThat(value.getValue()).isEqualTo(new String[] { "ONE", "TWO" });
|
||||
}
|
||||
|
||||
@@ -156,7 +157,7 @@ public class PostgresReactiveDataAccessStrategyTests extends ReactiveDataAccessS
|
||||
|
||||
assertThat(outboundRow).containsKey(SqlIdentifier.unquoted("enum_array"));
|
||||
|
||||
SettableValue value = outboundRow.get(SqlIdentifier.unquoted("enum_array"));
|
||||
Parameter value = outboundRow.get(SqlIdentifier.unquoted("enum_array"));
|
||||
assertThat(value.getValue()).isEqualTo(new String[] { "ONE", "TWO" });
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback;
|
||||
@@ -52,6 +51,8 @@ import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
@@ -61,7 +62,7 @@ import org.springframework.util.CollectionUtils;
|
||||
*/
|
||||
public class R2dbcEntityTemplateUnitTests {
|
||||
|
||||
DatabaseClient client;
|
||||
org.springframework.r2dbc.core.DatabaseClient client;
|
||||
R2dbcEntityTemplate entityTemplate;
|
||||
StatementRecorder recorder;
|
||||
|
||||
@@ -70,8 +71,8 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
|
||||
recorder = StatementRecorder.newInstance();
|
||||
client = DatabaseClient.builder().connectionFactory(recorder)
|
||||
.dataAccessStrategy(new DefaultReactiveDataAccessStrategy(PostgresDialect.INSTANCE)).build();
|
||||
entityTemplate = new R2dbcEntityTemplate(client);
|
||||
.bindMarkers(PostgresDialect.INSTANCE.getBindMarkersFactory()).build();
|
||||
entityTemplate = new R2dbcEntityTemplate(client, PostgresDialect.INSTANCE);
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
@@ -92,7 +93,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("SELECT"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("SELECT COUNT(person.id) FROM person WHERE person.THE_NAME = $1");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
@@ -113,7 +114,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("SELECT"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("SELECT person.id FROM person WHERE person.THE_NAME = $1 LIMIT 1");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
@@ -129,7 +130,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
|
||||
assertThat(statement.getSql())
|
||||
.isEqualTo("SELECT person.* FROM person WHERE person.THE_NAME = $1 ORDER BY THE_NAME ASC");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-215
|
||||
@@ -170,7 +171,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
|
||||
assertThat(statement.getSql())
|
||||
.isEqualTo("SELECT person.* FROM person WHERE person.THE_NAME = $1 ORDER BY THE_NAME ASC LIMIT 2");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
@@ -191,8 +192,8 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1 WHERE person.THE_NAME = $2");
|
||||
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("Heisenberg")).containsEntry(1,
|
||||
SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, Parameter.from("Heisenberg")).containsEntry(1,
|
||||
Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
@@ -212,7 +213,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("DELETE"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("DELETE FROM person WHERE person.THE_NAME = $1");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
@@ -229,7 +230,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("DELETE"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("DELETE FROM person WHERE person.id = $1");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-365
|
||||
@@ -249,8 +250,8 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("INSERT INTO versioned_person (id, version, name) VALUES ($1, $2, $3)");
|
||||
assertThat(statement.getBindings()).hasSize(3).containsEntry(0, SettableValue.from("id")).containsEntry(1,
|
||||
SettableValue.from(1L));
|
||||
assertThat(statement.getBindings()).hasSize(3).containsEntry(0, Parameter.from("id")).containsEntry(1,
|
||||
Parameter.from(1L));
|
||||
}
|
||||
|
||||
@Test // gh-215
|
||||
@@ -277,8 +278,8 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("INSERT INTO person (THE_NAME, description) VALUES ($1, $2)");
|
||||
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("before-convert"))
|
||||
.containsEntry(1, SettableValue.from("before-save"));
|
||||
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, Parameter.from("before-convert")).containsEntry(1,
|
||||
Parameter.from("before-save"));
|
||||
}
|
||||
|
||||
@Test // gh-365
|
||||
@@ -299,8 +300,8 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo(
|
||||
"UPDATE versioned_person SET version = $1, name = $2 WHERE versioned_person.id = $3 AND (versioned_person.version = $4)");
|
||||
assertThat(statement.getBindings()).hasSize(4).containsEntry(0, SettableValue.from(2L)).containsEntry(3,
|
||||
SettableValue.from(1L));
|
||||
assertThat(statement.getBindings()).hasSize(4).containsEntry(0, Parameter.from(2L)).containsEntry(3,
|
||||
Parameter.from(1L));
|
||||
}
|
||||
|
||||
@Test // gh-215
|
||||
@@ -332,8 +333,8 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1, description = $2 WHERE person.id = $3");
|
||||
assertThat(statement.getBindings()).hasSize(3).containsEntry(0, SettableValue.from("before-convert"))
|
||||
.containsEntry(1, SettableValue.from("before-save"));
|
||||
assertThat(statement.getBindings()).hasSize(3).containsEntry(0, Parameter.from("before-convert")).containsEntry(1,
|
||||
Parameter.from("before-save"));
|
||||
}
|
||||
|
||||
@ToString
|
||||
@@ -402,7 +403,7 @@ public class R2dbcEntityTemplateUnitTests {
|
||||
public Mono<Person> onBeforeSave(Person entity, OutboundRow outboundRow, SqlIdentifier table) {
|
||||
|
||||
capture(entity);
|
||||
outboundRow.put(SqlIdentifier.unquoted("description"), SettableValue.from("before-save"));
|
||||
outboundRow.put(SqlIdentifier.unquoted("description"), Parameter.from("before-save"));
|
||||
return Mono.just(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link R2dbcDialect}-aware {@link DefaultReactiveDataAccessStrategy} tests.
|
||||
@@ -205,7 +205,7 @@ public abstract class ReactiveDataAccessStrategyTestSupport {
|
||||
setter.accept(toSave, testValue);
|
||||
|
||||
assertThat(strategy.getOutboundRow(toSave)).containsEntry(SqlIdentifier.unquoted(fieldname),
|
||||
SettableValue.from(testValue));
|
||||
Parameter.from(testValue));
|
||||
|
||||
when(rowMock.get(fieldname)).thenReturn(testValue);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.r2dbc.testing.StatementRecorder;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Unit test for {@link ReactiveDeleteOperation}.
|
||||
@@ -104,7 +105,7 @@ public class ReactiveDeleteOperationUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("DELETE"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("DELETE FROM person WHERE person.THE_NAME = $1");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.r2dbc.testing.StatementRecorder;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Unit test for {@link ReactiveInsertOperation}.
|
||||
@@ -77,7 +78,7 @@ public class ReactiveInsertOperationUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("INSERT"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("INSERT INTO person (THE_NAME) VALUES ($1)");
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(1).containsEntry(0, Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
|
||||
@@ -27,10 +27,10 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.data.r2dbc.testing.StatementRecorder;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Unit test for {@link ReactiveUpdateOperation}.
|
||||
@@ -107,8 +107,8 @@ public class ReactiveUpdateOperationUnitTests {
|
||||
StatementRecorder.RecordedStatement statement = recorder.getCreatedStatement(s -> s.startsWith("UPDATE"));
|
||||
|
||||
assertThat(statement.getSql()).isEqualTo("UPDATE person SET THE_NAME = $1 WHERE person.THE_NAME = $2");
|
||||
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, SettableValue.from("Heisenberg")).containsEntry(1,
|
||||
SettableValue.from("Walter"));
|
||||
assertThat(statement.getBindings()).hasSize(2).containsEntry(0, Parameter.from("Heisenberg")).containsEntry(1,
|
||||
Parameter.from("Walter"));
|
||||
}
|
||||
|
||||
@Test // gh-220
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
|
||||
import org.springframework.data.r2dbc.testing.H2TestSupport;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
/**
|
||||
@@ -149,10 +150,10 @@ public class ConvertingR2dbcRepositoryIntegrationTests {
|
||||
OutboundRow outboundRow = new OutboundRow();
|
||||
|
||||
if (convertedEntity.getId() != null) {
|
||||
outboundRow.put("id", SettableValue.from(convertedEntity.getId()));
|
||||
outboundRow.put("id", Parameter.from(convertedEntity.getId()));
|
||||
}
|
||||
|
||||
outboundRow.put("name", SettableValue.from("prefixed: " + convertedEntity.getName()));
|
||||
outboundRow.put("name", Parameter.from("prefixed: " + convertedEntity.getName()));
|
||||
|
||||
return outboundRow;
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.dialect.MySqlDialect;
|
||||
import org.springframework.data.r2dbc.dialect.PostgresDialect;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.dialect.SqlServerDialect;
|
||||
import org.springframework.data.r2dbc.repository.config.mysql.MySqlPersonRepository;
|
||||
import org.springframework.data.r2dbc.repository.config.sqlserver.SqlServerPersonRepository;
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.dialect.DialectResolver;
|
||||
@@ -88,8 +88,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
R2dbcDialect dialect = DialectResolver.getDialect(connectionFactory);
|
||||
dataAccessStrategy = new DefaultReactiveDataAccessStrategy(dialect, r2dbcConverter);
|
||||
|
||||
databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory)
|
||||
.dataAccessStrategy(dataAccessStrategy).build();
|
||||
databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory).build();
|
||||
}
|
||||
|
||||
@Test // gh-282
|
||||
@@ -166,7 +165,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
assertThat(bindableQuery.get())
|
||||
.isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".date_of_birth BETWEEN $1 AND $2");
|
||||
|
||||
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
|
||||
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
|
||||
when(bindSpecMock.bind(anyInt(), any())).thenReturn(bindSpecMock);
|
||||
bindableQuery.bind(bindSpecMock);
|
||||
|
||||
@@ -325,7 +324,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
dataAccessStrategy);
|
||||
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" });
|
||||
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
|
||||
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
|
||||
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
|
||||
bindableQuery.bind(bindSpecMock);
|
||||
|
||||
verify(bindSpecMock, times(1)).bind(0, "Jo%");
|
||||
@@ -353,7 +352,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
dataAccessStrategy);
|
||||
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" });
|
||||
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
|
||||
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
|
||||
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
|
||||
bindableQuery.bind(bindSpecMock);
|
||||
|
||||
verify(bindSpecMock, times(1)).bind(0, "%hn");
|
||||
@@ -381,7 +380,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
dataAccessStrategy);
|
||||
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
|
||||
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
|
||||
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
|
||||
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
|
||||
bindableQuery.bind(bindSpecMock);
|
||||
|
||||
verify(bindSpecMock, times(1)).bind(0, "%oh%");
|
||||
@@ -409,7 +408,7 @@ public class PartTreeR2dbcQueryUnitTests {
|
||||
dataAccessStrategy);
|
||||
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" });
|
||||
BindableQuery bindableQuery = r2dbcQuery.createQuery(accessor);
|
||||
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
|
||||
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
|
||||
bindableQuery.bind(bindSpecMock);
|
||||
|
||||
verify(bindSpecMock, times(1)).bind(0, "%oh%");
|
||||
|
||||
@@ -23,11 +23,14 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PreparedOperationBindableQuery}.
|
||||
*
|
||||
* @author Roman Chigvintsev
|
||||
* @author Marl Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@Ignore
|
||||
@@ -35,11 +38,10 @@ public class PreparedOperationBindableQueryUnitTests {
|
||||
|
||||
@Mock PreparedOperation<?> preparedOperation;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Test // gh-282
|
||||
public void bindsQueryParameterValues() {
|
||||
|
||||
DatabaseClient.BindSpec bindSpecMock = mock(DatabaseClient.BindSpec.class);
|
||||
DatabaseClient.GenericExecuteSpec bindSpecMock = mock(DatabaseClient.GenericExecuteSpec.class);
|
||||
|
||||
PreparedOperationBindableQuery query = new PreparedOperationBindableQuery(preparedOperation);
|
||||
query.bind(bindSpecMock);
|
||||
|
||||
@@ -32,8 +32,8 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient.GenericExecuteSpec;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.DatabaseClient.GenericExecuteSpec;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
import org.springframework.data.r2dbc.repository.Query;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.testing.R2dbcIntegrationTestSupport;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
@@ -50,6 +49,7 @@ import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
|
||||
import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Abstract integration tests for {@link SimpleR2dbcRepository} to be ran against various databases.
|
||||
|
||||
@@ -27,12 +27,12 @@ import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
|
||||
import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Unit test for {@link R2dbcRepositoryFactory}.
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.r2dbc.mapping.SettableValue;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Recorder utility for R2DBC {@link Statement}s. Allows stubbing and introspection.
|
||||
@@ -255,7 +255,7 @@ public class StatementRecorder implements ConnectionFactory {
|
||||
|
||||
private final List<Result> results;
|
||||
|
||||
private final Map<Object, SettableValue> bindings = new LinkedHashMap<>();
|
||||
private final Map<Object, Parameter> bindings = new LinkedHashMap<>();
|
||||
|
||||
public RecordedStatement(String sql, Result result) {
|
||||
this(sql, Collections.singletonList(result));
|
||||
@@ -266,7 +266,7 @@ public class StatementRecorder implements ConnectionFactory {
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public Map<Object, SettableValue> getBindings() {
|
||||
public Map<Object, Parameter> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
@@ -281,25 +281,25 @@ public class StatementRecorder implements ConnectionFactory {
|
||||
|
||||
@Override
|
||||
public Statement bind(int index, Object o) {
|
||||
this.bindings.put(index, SettableValue.from(o));
|
||||
this.bindings.put(index, Parameter.from(o));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement bind(String identifier, Object o) {
|
||||
this.bindings.put(identifier, SettableValue.from(o));
|
||||
this.bindings.put(identifier, Parameter.from(o));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement bindNull(int index, Class<?> type) {
|
||||
this.bindings.put(index, SettableValue.empty(type));
|
||||
this.bindings.put(index, Parameter.empty(type));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement bindNull(String identifier, Class<?> type) {
|
||||
this.bindings.put(identifier, SettableValue.empty(type));
|
||||
this.bindings.put(identifier, Parameter.empty(type));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user