Add support for R2DBC
This commit introduces support for R2DBC ("Reactive Relational Database
Connectivity") with custom ConnectionFactory implementations, a
functional DatabaseClient for SQL execution, transaction management, a
bind marker abstraction database initialization utilities, and
exception translation.
Closes gh-25065
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.r2dbc.connection;
|
||||
|
||||
import io.r2dbc.spi.R2dbcBadGrammarException;
|
||||
import io.r2dbc.spi.R2dbcDataIntegrityViolationException;
|
||||
import io.r2dbc.spi.R2dbcException;
|
||||
import io.r2dbc.spi.R2dbcNonTransientResourceException;
|
||||
import io.r2dbc.spi.R2dbcPermissionDeniedException;
|
||||
import io.r2dbc.spi.R2dbcRollbackException;
|
||||
import io.r2dbc.spi.R2dbcTimeoutException;
|
||||
import io.r2dbc.spi.R2dbcTransientResourceException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.dao.DataAccessResourceFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.PermissionDeniedDataAccessException;
|
||||
import org.springframework.dao.QueryTimeoutException;
|
||||
import org.springframework.dao.TransientDataAccessResourceException;
|
||||
import org.springframework.r2dbc.BadSqlGrammarException;
|
||||
import org.springframework.r2dbc.UncategorizedR2dbcException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ConnectionFactoryUtils}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ConnectionFactoryUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
public void shouldTranslateTransientResourceException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcTransientResourceException(""));
|
||||
assertThat(exception).isInstanceOf(TransientDataAccessResourceException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslateRollbackException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcRollbackException());
|
||||
assertThat(exception).isInstanceOf(ConcurrencyFailureException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslateTimeoutException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcTimeoutException());
|
||||
assertThat(exception).isInstanceOf(QueryTimeoutException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotTranslateUnknownExceptions() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new MyTransientExceptions());
|
||||
assertThat(exception).isInstanceOf(UncategorizedR2dbcException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslateNonTransientResourceException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcNonTransientResourceException());
|
||||
assertThat(exception).isInstanceOf(DataAccessResourceFailureException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslateIntegrityViolationException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcDataIntegrityViolationException());
|
||||
assertThat(exception).isInstanceOf(DataIntegrityViolationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslatePermissionDeniedException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcPermissionDeniedException());
|
||||
assertThat(exception).isInstanceOf(PermissionDeniedDataAccessException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslateBadSqlGrammarException() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("", "",
|
||||
new R2dbcBadGrammarException());
|
||||
assertThat(exception).isInstanceOf(BadSqlGrammarException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageGeneration() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK",
|
||||
"SOME-SQL", new R2dbcTransientResourceException("MESSAGE"));
|
||||
assertThat(exception).isInstanceOf(
|
||||
TransientDataAccessResourceException.class).hasMessage(
|
||||
"TASK; SQL [SOME-SQL]; MESSAGE; nested exception is io.r2dbc.spi.R2dbcTransientResourceException: MESSAGE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageGenerationNullSQL() {
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK", null,
|
||||
new R2dbcTransientResourceException("MESSAGE"));
|
||||
assertThat(exception).isInstanceOf(
|
||||
TransientDataAccessResourceException.class).hasMessage(
|
||||
"TASK; MESSAGE; nested exception is io.r2dbc.spi.R2dbcTransientResourceException: MESSAGE");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void messageGenerationNullMessage() {
|
||||
|
||||
Exception exception = ConnectionFactoryUtils.convertR2dbcException("TASK",
|
||||
"SOME-SQL", new R2dbcTransientResourceException());
|
||||
assertThat(exception).isInstanceOf(
|
||||
TransientDataAccessResourceException.class).hasMessage(
|
||||
"TASK; SQL [SOME-SQL]; null; nested exception is io.r2dbc.spi.R2dbcTransientResourceException");
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class MyTransientExceptions extends R2dbcException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DelegatingConnectionFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class DelegatingConnectionFactoryUnitTests {
|
||||
|
||||
ConnectionFactory delegate = mock(ConnectionFactory.class);
|
||||
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
|
||||
DelegatingConnectionFactory connectionFactory = new ExampleConnectionFactory(
|
||||
delegate);
|
||||
|
||||
@Test
|
||||
public void shouldDelegateGetConnection() {
|
||||
|
||||
Mono<Connection> connectionMono = Mono.just(connectionMock);
|
||||
when(delegate.create()).thenReturn((Mono) connectionMono);
|
||||
|
||||
assertThat(connectionFactory.create()).isSameAs(connectionMono);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDelegateUnwrapWithoutImplementing() {
|
||||
assertThat(connectionFactory.unwrap()).isSameAs(delegate);
|
||||
}
|
||||
|
||||
static class ExampleConnectionFactory extends DelegatingConnectionFactory {
|
||||
|
||||
ExampleConnectionFactory(ConnectionFactory targetConnectionFactory) {
|
||||
super(targetConnectionFactory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.never;
|
||||
import static org.mockito.BDDMockito.reset;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link R2dbcTransactionManager}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class R2dbcTransactionManagerUnitTests {
|
||||
|
||||
ConnectionFactory connectionFactoryMock = mock(ConnectionFactory.class);
|
||||
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
|
||||
private R2dbcTransactionManager tm;
|
||||
|
||||
@BeforeEach
|
||||
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
|
||||
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(connection -> 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
|
||||
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(
|
||||
R2dbcBadGrammarException.class))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
public void testCommitFails() {
|
||||
when(connectionMock.commitTransaction()).thenReturn(Mono.defer(() -> Mono.error(new R2dbcBadGrammarException("Commit should fail"))));
|
||||
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock)
|
||||
.doOnNext(connection -> connection.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
|
||||
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(connection -> {
|
||||
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
|
||||
public void testRollbackFails() {
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.defer(() -> Mono.error(new R2dbcBadGrammarException("Commit should fail"))), Mono.empty());
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
operator.execute(reactiveTransaction -> {
|
||||
|
||||
reactiveTransaction.setRollbackOnly();
|
||||
|
||||
return ConnectionFactoryUtils.getConnection(connectionFactoryMock)
|
||||
.doOnNext(connection -> connection.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
|
||||
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(
|
||||
synchronizationManager -> {
|
||||
assertThat(synchronizationManager.hasResource(connectionFactoryMock)).isTrue();
|
||||
synchronizationManager.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
|
||||
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
|
||||
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(() -> {
|
||||
assertThat(this.beforeCommitCalled).isFalse();
|
||||
this.beforeCommitCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> beforeCompletion() {
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertThat(this.beforeCompletionCalled).isFalse();
|
||||
this.beforeCompletionCalled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> afterCommit() {
|
||||
if (this.status != TransactionSynchronization.STATUS_COMMITTED) {
|
||||
fail("Should never be called");
|
||||
}
|
||||
return Mono.fromRunnable(() -> {
|
||||
assertThat(this.afterCommitCalled).isFalse();
|
||||
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) {
|
||||
assertThat(this.afterCompletionCalled).isFalse();
|
||||
this.afterCompletionCalled = true;
|
||||
assertThat(status).isEqualTo(this.status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection;
|
||||
|
||||
import io.r2dbc.h2.H2Connection;
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactoryMetadata;
|
||||
import io.r2dbc.spi.IsolationLevel;
|
||||
import io.r2dbc.spi.R2dbcNonTransientResourceException;
|
||||
import io.r2dbc.spi.Wrapped;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.never;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SingleConnectionFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SingleConnectionFactoryUnitTests {
|
||||
|
||||
@Test
|
||||
public void shouldAllocateSameConnection() {
|
||||
SingleConnectionFactory factory = new SingleConnectionFactory(
|
||||
"r2dbc:h2:mem:///foo", false);
|
||||
|
||||
Mono<? extends Connection> cf1 = factory.create();
|
||||
Mono<? extends Connection> cf2 = factory.create();
|
||||
|
||||
Connection c1 = cf1.block();
|
||||
Connection c2 = cf2.block();
|
||||
|
||||
assertThat(c1).isSameAs(c2);
|
||||
factory.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyAutoCommit() {
|
||||
SingleConnectionFactory factory = new SingleConnectionFactory(
|
||||
"r2dbc:h2:mem:///foo", false);
|
||||
factory.setAutoCommit(false);
|
||||
|
||||
factory.create().as(StepVerifier::create)
|
||||
.consumeNextWith(actual -> assertThat(actual.isAutoCommit()).isFalse())
|
||||
.verifyComplete();
|
||||
|
||||
factory.setAutoCommit(true);
|
||||
|
||||
factory.create().as(StepVerifier::create)
|
||||
.consumeNextWith(actual -> assertThat(actual.isAutoCommit()).isTrue())
|
||||
.verifyComplete();
|
||||
|
||||
factory.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSuppressClose() {
|
||||
SingleConnectionFactory factory = new SingleConnectionFactory(
|
||||
"r2dbc:h2:mem:///foo", true);
|
||||
|
||||
Connection connection = factory.create().block();
|
||||
|
||||
StepVerifier.create(connection.close()).verifyComplete();
|
||||
assertThat(connection).isInstanceOf(Wrapped.class);
|
||||
assertThat(((Wrapped) connection).unwrap()).isInstanceOf(H2Connection.class);
|
||||
|
||||
StepVerifier.create(
|
||||
connection.setTransactionIsolationLevel(IsolationLevel.READ_COMMITTED))
|
||||
.verifyComplete();
|
||||
factory.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotSuppressClose() {
|
||||
SingleConnectionFactory factory = new SingleConnectionFactory(
|
||||
"r2dbc:h2:mem:///foo", false);
|
||||
|
||||
Connection connection = factory.create().block();
|
||||
|
||||
StepVerifier.create(connection.close()).verifyComplete();
|
||||
|
||||
StepVerifier.create(connection.setTransactionIsolationLevel(
|
||||
IsolationLevel.READ_COMMITTED)).verifyError(
|
||||
R2dbcNonTransientResourceException.class);
|
||||
factory.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseConnectionShouldNotCloseConnection() {
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
ConnectionFactoryMetadata metadata = mock(ConnectionFactoryMetadata.class);
|
||||
|
||||
SingleConnectionFactory factory = new SingleConnectionFactory(
|
||||
connectionMock, metadata, true);
|
||||
|
||||
Connection connection = factory.create().block();
|
||||
|
||||
ConnectionFactoryUtils.releaseConnection(connection, factory)
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
verify(connectionMock, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void releaseConnectionShouldCloseUnrelatedConnection() {
|
||||
Connection connectionMock = mock(Connection.class);
|
||||
Connection otherConnection = mock(Connection.class);
|
||||
ConnectionFactoryMetadata metadata = mock(ConnectionFactoryMetadata.class);
|
||||
when(otherConnection.close()).thenReturn(Mono.empty());
|
||||
|
||||
SingleConnectionFactory factory = new SingleConnectionFactory(
|
||||
connectionMock, metadata, false);
|
||||
|
||||
factory.create().as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
ConnectionFactoryUtils.releaseConnection(otherConnection, factory)
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
verify(otherConnection).close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Wrapped;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.times;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.verifyNoInteractions;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TransactionAwareConnectionFactoryProxy}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class TransactionAwareConnectionFactoryProxyUnitTests {
|
||||
|
||||
ConnectionFactory connectionFactoryMock = mock(ConnectionFactory.class);
|
||||
|
||||
Connection connectionMock1 = mock(Connection.class);
|
||||
|
||||
Connection connectionMock2 = mock(Connection.class);
|
||||
|
||||
Connection connectionMock3 = mock(Connection.class);
|
||||
|
||||
R2dbcTransactionManager tm;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
when(connectionFactoryMock.create()).thenReturn((Mono) Mono.just(connectionMock1),
|
||||
(Mono) Mono.just(connectionMock2), (Mono) Mono.just(connectionMock3));
|
||||
tm = new R2dbcTransactionManager(connectionFactoryMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createShouldWrapConnection() {
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(connection -> assertThat(connection).isInstanceOf(Wrapped.class))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwrapShouldReturnTargetConnection() {
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.map(Wrapped.class::cast).as(StepVerifier::create)
|
||||
.consumeNextWith(wrapped -> assertThat(wrapped.unwrap()).isEqualTo(connectionMock1))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unwrapShouldReturnTargetConnectionEvenWhenClosed() {
|
||||
when(connectionMock1.close()).thenReturn(Mono.empty());
|
||||
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.map(Connection.class::cast).flatMap(
|
||||
connection -> Mono.from(connection.close()).then(Mono.just(connection))).as(
|
||||
StepVerifier::create)
|
||||
.consumeNextWith(wrapped -> assertThat(((Wrapped<?>) wrapped).unwrap()).isEqualTo(connectionMock1))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTargetConnectionShouldReturnTargetConnection() {
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.map(Wrapped.class::cast).as(StepVerifier::create)
|
||||
.consumeNextWith(wrapped -> assertThat(wrapped.unwrap()).isEqualTo(connectionMock1))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMetadataShouldThrowsErrorEvenWhenClosed() {
|
||||
when(connectionMock1.close()).thenReturn(Mono.empty());
|
||||
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.map(Connection.class::cast).flatMap(
|
||||
connection -> Mono.from(connection.close())
|
||||
.then(Mono.just(connection))).as(StepVerifier::create)
|
||||
.consumeNextWith(connection -> assertThatIllegalStateException().isThrownBy(
|
||||
connection::getMetadata)).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hashCodeShouldReturnProxyHash() {
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.map(Connection.class::cast).as(StepVerifier::create)
|
||||
.consumeNextWith(connection -> assertThat(connection.hashCode()).isEqualTo(
|
||||
System.identityHashCode(connection))).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsShouldCompareCorrectly() {
|
||||
new TransactionAwareConnectionFactoryProxy(connectionFactoryMock).create()
|
||||
.map(Connection.class::cast).as(StepVerifier::create)
|
||||
.consumeNextWith(connection -> {
|
||||
assertThat(connection.equals(connection)).isTrue();
|
||||
assertThat(connection.equals(connectionMock1)).isFalse();
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitBoundConnection() {
|
||||
when(connectionMock1.beginTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock1.commitTransaction()).thenReturn(Mono.empty());
|
||||
when(connectionMock1.close()).thenReturn(Mono.empty());
|
||||
|
||||
TransactionalOperator rxtx = TransactionalOperator.create(tm);
|
||||
AtomicReference<Connection> transactionalConnection = new AtomicReference<>();
|
||||
|
||||
TransactionAwareConnectionFactoryProxy proxyCf = new TransactionAwareConnectionFactoryProxy(
|
||||
connectionFactoryMock);
|
||||
|
||||
ConnectionFactoryUtils.getConnection(connectionFactoryMock)
|
||||
.doOnNext(transactionalConnection::set).flatMap(connection -> proxyCf.create()
|
||||
.doOnNext(wrappedConnection -> assertThat(((Wrapped<?>) wrappedConnection).unwrap()).isSameAs(connection)))
|
||||
.as(rxtx::transactional)
|
||||
.flatMapMany(Connection::close)
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
|
||||
verifyNoInteractions(connectionMock2);
|
||||
verifyNoInteractions(connectionMock3);
|
||||
verify(connectionFactoryMock, times(1)).create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.init;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.io.ClassRelativeResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract test support for {@link DatabasePopulator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public abstract class AbstractDatabaseInitializationTests {
|
||||
|
||||
ClassRelativeResourceLoader resourceLoader = new ClassRelativeResourceLoader(
|
||||
getClass());
|
||||
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
|
||||
|
||||
@Test
|
||||
public void scriptWithSingleLineCommentsAndFailedDrop() {
|
||||
databasePopulator.addScript(resource("db-schema-failed-drop-comments.sql"));
|
||||
databasePopulator.addScript(resource("db-test-data.sql"));
|
||||
databasePopulator.setIgnoreFailedDrops(true);
|
||||
|
||||
runPopulator();
|
||||
|
||||
assertUsersDatabaseCreated("Heisenberg");
|
||||
}
|
||||
|
||||
private void runPopulator() {
|
||||
databasePopulator.populate(getConnectionFactory()) //
|
||||
.as(StepVerifier::create) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptWithStandardEscapedLiteral() {
|
||||
databasePopulator.addScript(defaultSchema());
|
||||
databasePopulator.addScript(resource("db-test-data-escaped-literal.sql"));
|
||||
|
||||
runPopulator();
|
||||
|
||||
assertUsersDatabaseCreated("'Heisenberg'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptWithMySqlEscapedLiteral() {
|
||||
databasePopulator.addScript(defaultSchema());
|
||||
databasePopulator.addScript(resource("db-test-data-mysql-escaped-literal.sql"));
|
||||
|
||||
runPopulator();
|
||||
|
||||
assertUsersDatabaseCreated("\\$Heisenberg\\$");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptWithMultipleStatements() {
|
||||
databasePopulator.addScript(defaultSchema());
|
||||
databasePopulator.addScript(resource("db-test-data-multiple.sql"));
|
||||
|
||||
runPopulator();
|
||||
|
||||
assertUsersDatabaseCreated("Heisenberg", "Jesse");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scriptWithMultipleStatementsAndLongSeparator() {
|
||||
databasePopulator.addScript(defaultSchema());
|
||||
databasePopulator.addScript(resource("db-test-data-endings.sql"));
|
||||
databasePopulator.setSeparator("@@");
|
||||
|
||||
runPopulator();
|
||||
|
||||
assertUsersDatabaseCreated("Heisenberg", "Jesse");
|
||||
}
|
||||
|
||||
abstract ConnectionFactory getConnectionFactory();
|
||||
|
||||
Resource resource(String path) {
|
||||
return resourceLoader.getResource(path);
|
||||
}
|
||||
|
||||
Resource defaultSchema() {
|
||||
return resource("db-schema.sql");
|
||||
}
|
||||
|
||||
Resource usersSchema() {
|
||||
return resource("users-schema.sql");
|
||||
}
|
||||
|
||||
void assertUsersDatabaseCreated(String... lastNames) {
|
||||
assertUsersDatabaseCreated(getConnectionFactory(), lastNames);
|
||||
}
|
||||
|
||||
void assertUsersDatabaseCreated(ConnectionFactory connectionFactory,
|
||||
String... lastNames) {
|
||||
|
||||
DatabaseClient client = DatabaseClient.create(connectionFactory);
|
||||
|
||||
for (String lastName : lastNames) {
|
||||
|
||||
client.sql("select count(0) from users where last_name = :name") //
|
||||
.bind("name", lastName) //
|
||||
.map((row, metadata) -> row.get(0)) //
|
||||
.first() //
|
||||
.map(number -> ((Number) number).intValue()) //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(1).as(
|
||||
"Did not find user with last name [" + lastName + "].") //
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.init;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.times;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CompositeDatabasePopulator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CompositeDatabasePopulatorTests {
|
||||
|
||||
Connection mockedConnection = mock(Connection.class);
|
||||
|
||||
DatabasePopulator mockedDatabasePopulator1 = mock(DatabasePopulator.class);
|
||||
|
||||
DatabasePopulator mockedDatabasePopulator2 = mock(DatabasePopulator.class);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
when(mockedDatabasePopulator1.populate(mockedConnection)).thenReturn(
|
||||
Mono.empty());
|
||||
when(mockedDatabasePopulator2.populate(mockedConnection)).thenReturn(
|
||||
Mono.empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addPopulators() {
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||
populator.addPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2);
|
||||
|
||||
populator.populate(mockedConnection).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
|
||||
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPopulatorsWithMultiple() {
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||
populator.setPopulators(mockedDatabasePopulator1, mockedDatabasePopulator2); // multiple
|
||||
|
||||
populator.populate(mockedConnection).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
|
||||
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPopulatorsForOverride() {
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
|
||||
populator.setPopulators(mockedDatabasePopulator1);
|
||||
populator.setPopulators(mockedDatabasePopulator2); // override
|
||||
|
||||
populator.populate(mockedConnection).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(mockedDatabasePopulator1, times(0)).populate(mockedConnection);
|
||||
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructWithVarargs() {
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator(
|
||||
mockedDatabasePopulator1, mockedDatabasePopulator2);
|
||||
|
||||
populator.populate(mockedConnection).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
|
||||
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructWithCollection() {
|
||||
Set<DatabasePopulator> populators = new LinkedHashSet<>();
|
||||
populators.add(mockedDatabasePopulator1);
|
||||
populators.add(mockedDatabasePopulator2);
|
||||
|
||||
CompositeDatabasePopulator populator = new CompositeDatabasePopulator(populators);
|
||||
populator.populate(mockedConnection).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(mockedDatabasePopulator1, times(1)).populate(mockedConnection);
|
||||
verify(mockedDatabasePopulator2, times(1)).populate(mockedConnection);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.init;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import io.r2dbc.spi.test.MockConnection;
|
||||
import io.r2dbc.spi.test.MockConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ConnectionFactoryInitializer}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ConnectionFactoryInitializerUnitTests {
|
||||
|
||||
AtomicBoolean called = new AtomicBoolean();
|
||||
|
||||
DatabasePopulator populator = mock(DatabasePopulator.class);
|
||||
|
||||
MockConnection connection = MockConnection.builder().build();
|
||||
|
||||
MockConnectionFactory connectionFactory = MockConnectionFactory.builder().connection(
|
||||
connection).build();
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldInitializeConnectionFactory() {
|
||||
when(populator.populate(connectionFactory)).thenReturn(
|
||||
Mono.<Void> empty().doOnSubscribe(subscription -> called.set(true)));
|
||||
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
initializer.setDatabasePopulator(populator);
|
||||
|
||||
initializer.afterPropertiesSet();
|
||||
|
||||
assertThat(called).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCleanConnectionFactory() {
|
||||
when(populator.populate(connectionFactory)).thenReturn(
|
||||
Mono.<Void> empty().doOnSubscribe(subscription -> called.set(true)));
|
||||
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
initializer.setDatabaseCleaner(populator);
|
||||
|
||||
initializer.afterPropertiesSet();
|
||||
initializer.destroy();
|
||||
|
||||
assertThat(called).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.init;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactories;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DatabasePopulator} using H2.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class H2DatabasePopulatorIntegrationTests
|
||||
extends AbstractDatabaseInitializationTests {
|
||||
|
||||
UUID databaseName = UUID.randomUUID();
|
||||
|
||||
ConnectionFactory connectionFactory = ConnectionFactories.get("r2dbc:h2:mem:///"
|
||||
+ databaseName + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
|
||||
|
||||
|
||||
@Override
|
||||
ConnectionFactory getConnectionFactory() {
|
||||
return this.connectionFactory;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRunScript() {
|
||||
|
||||
databasePopulator.addScript(usersSchema());
|
||||
databasePopulator.addScript(resource("db-test-data-h2.sql"));
|
||||
// Set statement separator to double newline so that ";" is not
|
||||
// considered a statement separator within the source code of the
|
||||
// aliased function 'REVERSE'.
|
||||
databasePopulator.setSeparator("\n\n");
|
||||
|
||||
databasePopulator.populate(connectionFactory).as(
|
||||
StepVerifier::create).verifyComplete();
|
||||
|
||||
assertUsersDatabaseCreated(connectionFactory, "White");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.init;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResourceDatabasePopulator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ResourceDatabasePopulatorUnitTests {
|
||||
|
||||
private static final Resource script1 = mock(Resource.class);
|
||||
|
||||
private static final Resource script2 = mock(Resource.class);
|
||||
|
||||
private static final Resource script3 = mock(Resource.class);
|
||||
|
||||
|
||||
@Test
|
||||
public void constructWithNullResource() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new ResourceDatabasePopulator((Resource) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructWithNullResourceArray() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> new ResourceDatabasePopulator((Resource[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructWithResource() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(
|
||||
script1);
|
||||
assertThat(databasePopulator.scripts).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructWithMultipleResources() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(
|
||||
script1, script2);
|
||||
assertThat(databasePopulator.scripts).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructWithMultipleResourcesAndThenAddScript() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator(
|
||||
script1, script2);
|
||||
assertThat(databasePopulator.scripts).hasSize(2);
|
||||
|
||||
databasePopulator.addScript(script3);
|
||||
assertThat(databasePopulator.scripts).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScriptsWithNullResource() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> databasePopulator.addScripts((Resource) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addScriptsWithNullResourceArray() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> databasePopulator.addScripts((Resource[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setScriptsWithNullResource() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> databasePopulator.setScripts((Resource) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setScriptsWithNullResourceArray() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> databasePopulator.setScripts((Resource[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setScriptsAndThenAddScript() {
|
||||
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
|
||||
assertThat(databasePopulator.scripts).isEmpty();
|
||||
|
||||
databasePopulator.setScripts(script1, script2);
|
||||
assertThat(databasePopulator.scripts).hasSize(2);
|
||||
|
||||
databasePopulator.addScript(script3);
|
||||
assertThat(databasePopulator.scripts).hasSize(3);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.init;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.util.Strings;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.core.io.support.EncodedResource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ScriptUtils}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
* @author Phillip Webb
|
||||
* @author Chris Baldwin
|
||||
* @author Nicolas Debeissat
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ScriptUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
public void splitSqlScriptDelimitedWithSemicolon() {
|
||||
String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||
String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||
String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)";
|
||||
String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
|
||||
String script = Strings.join(rawStatement1, rawStatement2, rawStatement3).with(
|
||||
";");
|
||||
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, ";", statements);
|
||||
|
||||
assertThat(statements).hasSize(3).containsSequence(cleanedStatement1,
|
||||
cleanedStatement2, cleanedStatement3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitSqlScriptDelimitedWithNewLine() {
|
||||
String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
|
||||
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
|
||||
String script = Strings.join(statement1, statement2, statement3).with("\n");
|
||||
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, "\n", statements);
|
||||
|
||||
assertThat(statements).hasSize(3).containsSequence(statement1, statement2,
|
||||
statement3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitSqlScriptDelimitedWithNewLineButDefaultDelimiterSpecified() {
|
||||
String statement1 = "do something";
|
||||
String statement2 = "do something else";
|
||||
|
||||
char delim = '\n';
|
||||
String script = statement1 + delim + statement2 + delim;
|
||||
|
||||
List<String> statements = new ArrayList<>();
|
||||
|
||||
ScriptUtils.splitSqlScript(script, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR,
|
||||
statements);
|
||||
|
||||
assertThat(statements).hasSize(1).contains(script.replace('\n', ' '));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitScriptWithSingleQuotesNestedInsideDoubleQuotes() {
|
||||
String statement1 = "select '1' as \"Dogbert's owner's\" from dual";
|
||||
String statement2 = "select '2' as \"Dilbert's\" from dual";
|
||||
|
||||
char delim = ';';
|
||||
String script = statement1 + delim + statement2 + delim;
|
||||
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, ';', statements);
|
||||
|
||||
assertThat(statements).hasSize(2).containsSequence(statement1, statement2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAndSplitScriptWithMultipleNewlinesAsSeparator() {
|
||||
String script = readScript("db-test-data-multi-newline.sql");
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, "\n\n", statements);
|
||||
|
||||
String statement1 = "insert into users (last_name) values ('Walter')";
|
||||
String statement2 = "insert into users (last_name) values ('Jesse')";
|
||||
|
||||
assertThat(statements.size()).as("wrong number of statements").isEqualTo(2);
|
||||
assertThat(statements.get(0)).as("statement 1 not split correctly").isEqualTo(
|
||||
statement1);
|
||||
assertThat(statements.get(1)).as("statement 2 not split correctly").isEqualTo(
|
||||
statement2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAndSplitScriptContainingComments() {
|
||||
String script = readScript("test-data-with-comments.sql");
|
||||
splitScriptContainingComments(script);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAndSplitScriptContainingCommentsWithWindowsLineEnding() {
|
||||
String script = readScript("test-data-with-comments.sql").replaceAll("\n",
|
||||
"\r\n");
|
||||
splitScriptContainingComments(script);
|
||||
}
|
||||
|
||||
private void splitScriptContainingComments(String script) {
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, ';', statements);
|
||||
|
||||
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
|
||||
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
|
||||
String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
|
||||
|
||||
assertThat(statements).hasSize(4).containsSequence(statement1, statement2,
|
||||
statement3, statement4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAndSplitScriptContainingCommentsWithLeadingTabs() {
|
||||
String script = readScript("test-data-with-comments-and-leading-tabs.sql");
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, ';', statements);
|
||||
|
||||
String statement1 = "insert into customer (id, name) values (1, 'Walter White')";
|
||||
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1)";
|
||||
String statement3 = "insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1)";
|
||||
|
||||
assertThat(statements).hasSize(3).containsSequence(statement1, statement2,
|
||||
statement3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAndSplitScriptContainingMultiLineComments() {
|
||||
String script = readScript("test-data-with-multi-line-comments.sql");
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, ';', statements);
|
||||
|
||||
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Walter', 'White')";
|
||||
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Jesse' , 'Pinkman' )";
|
||||
|
||||
assertThat(statements).hasSize(2).containsSequence(statement1, statement2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAndSplitScriptContainingMultiLineNestedComments() {
|
||||
String script = readScript("test-data-with-multi-line-nested-comments.sql");
|
||||
List<String> statements = new ArrayList<>();
|
||||
ScriptUtils.splitSqlScript(script, ';', statements);
|
||||
|
||||
String statement1 = "INSERT INTO users(first_name, last_name) VALUES('Walter', 'White')";
|
||||
String statement2 = "INSERT INTO users(first_name, last_name) VALUES( 'Jesse' , 'Pinkman' )";
|
||||
|
||||
assertThat(statements).hasSize(2).containsSequence(statement1, statement2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsDelimiters() {
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n select ';'",
|
||||
";")).isFalse();
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1; select 2",
|
||||
";")).isTrue();
|
||||
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';",
|
||||
"\n")).isFalse();
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2",
|
||||
"\n")).isTrue();
|
||||
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n select 2",
|
||||
"\n\n")).isFalse();
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters("select 1\n\n select 2",
|
||||
"\n\n")).isTrue();
|
||||
|
||||
// MySQL style escapes '\\'
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters(
|
||||
"insert into users(first_name, last_name)\nvalues('a\\\\', 'b;')",
|
||||
";")).isFalse();
|
||||
assertThat(ScriptUtils.containsSqlScriptDelimiters(
|
||||
"insert into users(first_name, last_name)\nvalues('Charles', 'd\\'Artagnan'); select 1;",
|
||||
";")).isTrue();
|
||||
}
|
||||
|
||||
private String readScript(String path) {
|
||||
EncodedResource resource = new EncodedResource(
|
||||
new ClassPathResource(path, getClass()));
|
||||
return ScriptUtils.readScript(resource, new DefaultDataBufferFactory()).block();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.lookup;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractRoutingConnectionFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class AbstractRoutingConnectionFactoryUnitTests {
|
||||
|
||||
private static final String ROUTING_KEY = "routingKey";
|
||||
|
||||
@Mock
|
||||
ConnectionFactory defaultConnectionFactory;
|
||||
|
||||
@Mock
|
||||
ConnectionFactory routedConnectionFactory;
|
||||
|
||||
DummyRoutingConnectionFactory connectionFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
connectionFactory = new DummyRoutingConnectionFactory();
|
||||
connectionFactory.setDefaultTargetConnectionFactory(defaultConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDetermineRoutedFactory() {
|
||||
|
||||
connectionFactory.setTargetConnectionFactories(
|
||||
singletonMap("key", routedConnectionFactory));
|
||||
connectionFactory.setConnectionFactoryLookup(new MapConnectionFactoryLookup());
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.subscriberContext(Context.of(ROUTING_KEY, "key"))
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(routedConnectionFactory)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFallbackToDefaultConnectionFactory() {
|
||||
connectionFactory.setTargetConnectionFactories(
|
||||
singletonMap("key", routedConnectionFactory));
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(defaultConnectionFactory)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializationShouldFailUnsupportedLookupKey() {
|
||||
connectionFactory.setTargetConnectionFactories(singletonMap("key", new Object()));
|
||||
|
||||
assertThatThrownBy(() -> connectionFactory.afterPropertiesSet()).isInstanceOf(
|
||||
IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializationShouldFailUnresolvableKey() {
|
||||
connectionFactory.setTargetConnectionFactories(singletonMap("key", "value"));
|
||||
connectionFactory.setConnectionFactoryLookup(new MapConnectionFactoryLookup());
|
||||
|
||||
assertThatThrownBy(() -> connectionFactory.afterPropertiesSet())
|
||||
.isInstanceOf(ConnectionFactoryLookupFailureException.class)
|
||||
.hasMessageContaining(
|
||||
"No ConnectionFactory with name 'value' registered");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unresolvableConnectionFactoryRetrievalShouldFail() {
|
||||
connectionFactory.setLenientFallback(false);
|
||||
connectionFactory.setConnectionFactoryLookup(new MapConnectionFactoryLookup());
|
||||
connectionFactory.setTargetConnectionFactories(
|
||||
singletonMap("key", routedConnectionFactory));
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.subscriberContext(Context.of(ROUTING_KEY, "unknown"))
|
||||
.as(StepVerifier::create)
|
||||
.verifyError(IllegalStateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectionFactoryRetrievalWithUnknownLookupKeyShouldReturnDefaultConnectionFactory() {
|
||||
connectionFactory.setTargetConnectionFactories(
|
||||
singletonMap("key", routedConnectionFactory));
|
||||
connectionFactory.setDefaultTargetConnectionFactory(defaultConnectionFactory);
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.subscriberContext(Context.of(ROUTING_KEY, "unknown"))
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(defaultConnectionFactory)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectionFactoryRetrievalWithoutLookupKeyShouldReturnDefaultConnectionFactory() {
|
||||
connectionFactory.setTargetConnectionFactories(
|
||||
singletonMap("key", routedConnectionFactory));
|
||||
connectionFactory.setDefaultTargetConnectionFactory(defaultConnectionFactory);
|
||||
connectionFactory.setLenientFallback(false);
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(defaultConnectionFactory)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLookupFromMap() {
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup("lookup-key",
|
||||
routedConnectionFactory);
|
||||
|
||||
connectionFactory.setConnectionFactoryLookup(lookup);
|
||||
connectionFactory.setTargetConnectionFactories(
|
||||
singletonMap("my-key", "lookup-key"));
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.subscriberContext(Context.of(ROUTING_KEY, "my-key"))
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(routedConnectionFactory)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAllowModificationsAfterInitialization() {
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup();
|
||||
|
||||
connectionFactory.setConnectionFactoryLookup(lookup);
|
||||
connectionFactory.setTargetConnectionFactories(lookup.getConnectionFactories());
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.subscriberContext(Context.of(ROUTING_KEY, "lookup-key"))
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(defaultConnectionFactory)
|
||||
.verifyComplete();
|
||||
|
||||
lookup.addConnectionFactory("lookup-key", routedConnectionFactory);
|
||||
connectionFactory.afterPropertiesSet();
|
||||
|
||||
connectionFactory.determineTargetConnectionFactory()
|
||||
.subscriberContext(Context.of(ROUTING_KEY, "lookup-key"))
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(routedConnectionFactory)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
static class DummyRoutingConnectionFactory extends AbstractRoutingConnectionFactory {
|
||||
|
||||
@Override
|
||||
protected Mono<Object> determineCurrentLookupKey() {
|
||||
return Mono.subscriberContext().filter(context -> context.hasKey(ROUTING_KEY))
|
||||
.map(context -> context.get(ROUTING_KEY));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.lookup;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link BeanFactoryConnectionFactoryLookup}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class BeanFactoryConnectionFactoryLookupUnitTests {
|
||||
|
||||
private static final String CONNECTION_FACTORY_BEAN_NAME = "connectionFactory";
|
||||
|
||||
@Mock
|
||||
BeanFactory beanFactory;
|
||||
|
||||
@Test
|
||||
public void shouldLookupConnectionFactory() {
|
||||
DummyConnectionFactory expectedConnectionFactory = new DummyConnectionFactory();
|
||||
when(beanFactory.getBean(CONNECTION_FACTORY_BEAN_NAME,
|
||||
ConnectionFactory.class)).thenReturn(expectedConnectionFactory);
|
||||
|
||||
BeanFactoryConnectionFactoryLookup lookup = new BeanFactoryConnectionFactoryLookup();
|
||||
lookup.setBeanFactory(beanFactory);
|
||||
|
||||
ConnectionFactory connectionFactory = lookup.getConnectionFactory(
|
||||
CONNECTION_FACTORY_BEAN_NAME);
|
||||
|
||||
assertThat(connectionFactory).isNotNull();
|
||||
assertThat(connectionFactory).isSameAs(expectedConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLookupWhereBeanFactoryYieldsNonConnectionFactoryType() {
|
||||
BeanFactory beanFactory = mock(BeanFactory.class);
|
||||
|
||||
when(beanFactory.getBean(CONNECTION_FACTORY_BEAN_NAME,
|
||||
ConnectionFactory.class)).thenThrow(
|
||||
new BeanNotOfRequiredTypeException(CONNECTION_FACTORY_BEAN_NAME,
|
||||
ConnectionFactory.class, String.class));
|
||||
|
||||
BeanFactoryConnectionFactoryLookup lookup = new BeanFactoryConnectionFactoryLookup(
|
||||
beanFactory);
|
||||
|
||||
assertThatExceptionOfType(
|
||||
ConnectionFactoryLookupFailureException.class).isThrownBy(
|
||||
() -> lookup.getConnectionFactory(CONNECTION_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLookupWhereBeanFactoryHasNotBeenSupplied() {
|
||||
BeanFactoryConnectionFactoryLookup lookup = new BeanFactoryConnectionFactoryLookup();
|
||||
|
||||
assertThatThrownBy(() -> lookup.getConnectionFactory(
|
||||
CONNECTION_FACTORY_BEAN_NAME)).isInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.lookup;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactoryMetadata;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
/**
|
||||
* Stub, do-nothing {@link ConnectionFactory} implementation.
|
||||
* <p>
|
||||
* All methods throw {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DummyConnectionFactory implements ConnectionFactory {
|
||||
|
||||
@Override
|
||||
public Publisher<? extends Connection> create() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionFactoryMetadata getMetadata() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.connection.lookup;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MapConnectionFactoryLookup}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class MapConnectionFactoryLookupUnitTests {
|
||||
|
||||
private static final String CONNECTION_FACTORY_NAME = "connectionFactory";
|
||||
|
||||
@Test
|
||||
public void getConnectionFactorysReturnsUnmodifiableMap() {
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup();
|
||||
Map<String, ConnectionFactory> connectionFactories = lookup.getConnectionFactories();
|
||||
|
||||
assertThatThrownBy(() -> connectionFactories.put("",
|
||||
new DummyConnectionFactory())).isInstanceOf(
|
||||
UnsupportedOperationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldLookupConnectionFactory() {
|
||||
Map<String, ConnectionFactory> connectionFactories = new HashMap<>();
|
||||
DummyConnectionFactory expectedConnectionFactory = new DummyConnectionFactory();
|
||||
|
||||
connectionFactories.put(CONNECTION_FACTORY_NAME, expectedConnectionFactory);
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup();
|
||||
|
||||
lookup.setConnectionFactories(connectionFactories);
|
||||
|
||||
ConnectionFactory connectionFactory = lookup.getConnectionFactory(
|
||||
CONNECTION_FACTORY_NAME);
|
||||
|
||||
assertThat(connectionFactory).isNotNull().isSameAs(expectedConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addingConnectionFactoryPermitsOverride() {
|
||||
Map<String, ConnectionFactory> connectionFactories = new HashMap<>();
|
||||
DummyConnectionFactory overriddenConnectionFactory = new DummyConnectionFactory();
|
||||
DummyConnectionFactory expectedConnectionFactory = new DummyConnectionFactory();
|
||||
connectionFactories.put(CONNECTION_FACTORY_NAME, overriddenConnectionFactory);
|
||||
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup();
|
||||
|
||||
lookup.setConnectionFactories(connectionFactories);
|
||||
lookup.addConnectionFactory(CONNECTION_FACTORY_NAME, expectedConnectionFactory);
|
||||
|
||||
ConnectionFactory connectionFactory = lookup.getConnectionFactory(
|
||||
CONNECTION_FACTORY_NAME);
|
||||
|
||||
assertThat(connectionFactory).isNotNull().isSameAs(expectedConnectionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void getConnectionFactoryWhereSuppliedMapHasNonConnectionFactoryTypeUnderSpecifiedKey() {
|
||||
Map connectionFactories = new HashMap<>();
|
||||
connectionFactories.put(CONNECTION_FACTORY_NAME, new Object());
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup(
|
||||
connectionFactories);
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> lookup.getConnectionFactory(CONNECTION_FACTORY_NAME)).isInstanceOf(
|
||||
ClassCastException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getConnectionFactoryWhereSuppliedMapHasNoEntryForSpecifiedKey() {
|
||||
MapConnectionFactoryLookup lookup = new MapConnectionFactoryLookup();
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> lookup.getConnectionFactory(CONNECTION_FACTORY_NAME)).isInstanceOf(
|
||||
ConnectionFactoryLookupFailureException.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Result;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DatabaseClient}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Mingyuan Wu
|
||||
*/
|
||||
public abstract class AbstractDatabaseClientIntegrationTests {
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
connectionFactory = createConnectionFactory();
|
||||
|
||||
Mono.from(connectionFactory.create())
|
||||
.flatMapMany(connection -> Flux.from(connection.createStatement("DROP TABLE legoset").execute())
|
||||
.flatMap(Result::getRowsUpdated)
|
||||
.onErrorResume(e -> Mono.empty())
|
||||
.thenMany(connection.createStatement(getCreateTableStatement()).execute())
|
||||
.flatMap(Result::getRowsUpdated).thenMany(connection.close())).as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ConnectionFactory} to be used in this test.
|
||||
*
|
||||
* @return the {@link ConnectionFactory} to be used in this test
|
||||
*/
|
||||
protected abstract ConnectionFactory createConnectionFactory();
|
||||
|
||||
/**
|
||||
* Return the the CREATE TABLE statement for table {@code legoset} with the following
|
||||
* three columns:
|
||||
* <ul>
|
||||
* <li>id integer (primary key), not null</li>
|
||||
* <li>name varchar(255), nullable</li>
|
||||
* <li>manual integer, nullable</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the CREATE TABLE statement for table {@code legoset} with three columns.
|
||||
*/
|
||||
protected abstract String getCreateTableStatement();
|
||||
|
||||
@Test
|
||||
public void executeInsert() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
databaseClient.sql("INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)")
|
||||
.bind("id", 42055)
|
||||
.bind("name", "SCHAUFELRADBAGGER")
|
||||
.bindNull("manual", Integer.class)
|
||||
.fetch().rowsUpdated()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(1)
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql("SELECT id FROM legoset")
|
||||
.map(row -> row.get("id"))
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> {
|
||||
assertThat(actual).isInstanceOf(Number.class);
|
||||
assertThat(((Number) actual).intValue()).isEqualTo(42055);
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldTranslateDuplicateKeyException() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
executeInsert();
|
||||
|
||||
databaseClient.sql(
|
||||
"INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)")
|
||||
.bind("id", 42055)
|
||||
.bind("name", "SCHAUFELRADBAGGER")
|
||||
.bindNull("manual", Integer.class)
|
||||
.fetch().rowsUpdated()
|
||||
.as(StepVerifier::create)
|
||||
.expectErrorSatisfies(exception -> assertThat(exception)
|
||||
.isInstanceOf(DataIntegrityViolationException.class)
|
||||
.hasMessageContaining("execute; SQL [INSERT INTO legoset"))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeDeferred() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
databaseClient.sql(() -> "INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)")
|
||||
.bind("id", 42055)
|
||||
.bind("name", "SCHAUFELRADBAGGER")
|
||||
.bindNull("manual", Integer.class)
|
||||
.fetch().rowsUpdated()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(1)
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient.sql("SELECT id FROM legoset")
|
||||
.map(row -> row.get("id")).first()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitGeneratedKey() {
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
databaseClient.sql(
|
||||
"INSERT INTO legoset ( name, manual) VALUES(:name, :manual)")
|
||||
.bind("name","SCHAUFELRADBAGGER")
|
||||
.bindNull("manual", Integer.class)
|
||||
.filter(statement -> statement.returnGeneratedValues("id"))
|
||||
.map(row -> (Number) row.get("id"))
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Result;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.r2dbc.connection.R2dbcTransactionManager;
|
||||
import org.springframework.transaction.ReactiveTransactionManager;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Abstract base class for transactional integration tests for {@link DatabaseClient}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public abstract class AbstractTransactionalDatabaseClientIntegrationTests {
|
||||
|
||||
private ConnectionFactory connectionFactory;
|
||||
|
||||
AnnotationConfigApplicationContext context;
|
||||
|
||||
DatabaseClient databaseClient;
|
||||
R2dbcTransactionManager transactionManager;
|
||||
TransactionalOperator rxtx;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
|
||||
connectionFactory = createConnectionFactory();
|
||||
|
||||
context = new AnnotationConfigApplicationContext();
|
||||
context.getBeanFactory().registerResolvableDependency(ConnectionFactory.class, connectionFactory);
|
||||
context.register(Config.class);
|
||||
context.refresh();
|
||||
|
||||
|
||||
Mono.from(connectionFactory.create())
|
||||
.flatMapMany(connection -> Flux.from(connection.createStatement("DROP TABLE legoset").execute())
|
||||
.flatMap(Result::getRowsUpdated)
|
||||
.onErrorResume(e -> Mono.empty())
|
||||
.thenMany(connection.createStatement(getCreateTableStatement()).execute())
|
||||
.flatMap(Result::getRowsUpdated).thenMany(connection.close())).as(StepVerifier::create).verifyComplete();
|
||||
|
||||
databaseClient = DatabaseClient.create(connectionFactory);
|
||||
transactionManager = new R2dbcTransactionManager(connectionFactory);
|
||||
rxtx = TransactionalOperator.create(transactionManager);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
context.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ConnectionFactory} to be used in this test.
|
||||
* @return the {@link ConnectionFactory} to be used in this test.
|
||||
*/
|
||||
protected abstract ConnectionFactory createConnectionFactory();
|
||||
|
||||
/**
|
||||
* Return the the CREATE TABLE statement for table {@code legoset} with the following three columns:
|
||||
* <ul>
|
||||
* <li>id integer (primary key), not null</li>
|
||||
* <li>name varchar(255), nullable</li>
|
||||
* <li>manual integer, nullable</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the CREATE TABLE statement for table {@code legoset} with three columns.
|
||||
*/
|
||||
protected abstract String getCreateTableStatement();
|
||||
|
||||
/**
|
||||
* Get a parameterized {@code INSERT INTO legoset} statement setting id, name, and manual values.
|
||||
*/
|
||||
protected String getInsertIntoLegosetStatement() {
|
||||
return "INSERT INTO legoset (id, name, manual) VALUES(:id, :name, :manual)";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeInsertInTransaction() {
|
||||
Flux<Integer> integerFlux = databaseClient
|
||||
.sql(getInsertIntoLegosetStatement())
|
||||
.bind(0, 42055)
|
||||
.bind(1, "SCHAUFELRADBAGGER")
|
||||
.bindNull(2, Integer.class)
|
||||
.fetch().rowsUpdated().flux().as(rxtx::transactional);
|
||||
|
||||
integerFlux.as(StepVerifier::create)
|
||||
.expectNext(1)
|
||||
.verifyComplete();
|
||||
|
||||
databaseClient
|
||||
.sql("SELECT id FROM legoset")
|
||||
.fetch()
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(actual -> assertThat(actual).hasEntrySatisfying("id", numberOf(42055)))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRollbackTransaction() {
|
||||
|
||||
Mono<Object> integerFlux = databaseClient.sql(getInsertIntoLegosetStatement())
|
||||
.bind(0, 42055)
|
||||
.bind(1, "SCHAUFELRADBAGGER")
|
||||
.bindNull(2, Integer.class)
|
||||
.fetch().rowsUpdated()
|
||||
.then(Mono.error(new IllegalStateException("failed")))
|
||||
.as(rxtx::transactional);
|
||||
|
||||
integerFlux.as(StepVerifier::create)
|
||||
.expectError(IllegalStateException.class)
|
||||
.verify();
|
||||
|
||||
databaseClient
|
||||
.sql("SELECT id FROM legoset")
|
||||
.fetch()
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRollbackTransactionUsingTransactionalOperator() {
|
||||
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
TransactionalOperator transactionalOperator = TransactionalOperator
|
||||
.create(new R2dbcTransactionManager(connectionFactory), new DefaultTransactionDefinition());
|
||||
|
||||
Flux<Integer> integerFlux = databaseClient.sql(getInsertIntoLegosetStatement())
|
||||
.bind(0, 42055)
|
||||
.bind(1, "SCHAUFELRADBAGGER")
|
||||
.bindNull(2, Integer.class)
|
||||
.fetch().rowsUpdated()
|
||||
.thenMany(Mono.fromSupplier(() -> {
|
||||
throw new IllegalStateException("failed");
|
||||
}));
|
||||
|
||||
integerFlux.as(transactionalOperator::transactional)
|
||||
.as(StepVerifier::create)
|
||||
.expectError(IllegalStateException.class)
|
||||
.verify();
|
||||
|
||||
databaseClient
|
||||
.sql("SELECT id FROM legoset")
|
||||
.fetch()
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private Condition<? super Object> numberOf(int expected) {
|
||||
return new Condition<>(object -> object instanceof Number &&
|
||||
((Number) object).intValue() == expected, "Number %d", expected);
|
||||
}
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Config {
|
||||
|
||||
@Autowired GenericApplicationContext context;
|
||||
|
||||
@Bean
|
||||
ReactiveTransactionManager txMgr(ConnectionFactory connectionFactory) {
|
||||
return new R2dbcTransactionManager(connectionFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
|
||||
return TransactionalOperator.create(transactionManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Result;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import io.r2dbc.spi.test.MockColumnMetadata;
|
||||
import io.r2dbc.spi.test.MockResult;
|
||||
import io.r2dbc.spi.test.MockRow;
|
||||
import io.r2dbc.spi.test.MockRowMetadata;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.doReturn;
|
||||
import static org.mockito.BDDMockito.inOrder;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.times;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.BDDMockito.verifyNoInteractions;
|
||||
import static org.mockito.BDDMockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.BDDMockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultDatabaseClient}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Ferdinand Jacobs
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class DefaultDatabaseClientUnitTests {
|
||||
|
||||
@Mock
|
||||
Connection connection;
|
||||
|
||||
private DatabaseClient.Builder databaseClientBuilder;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
|
||||
|
||||
when(connectionFactory.create()).thenReturn((Publisher) Mono.just(connection));
|
||||
when(connection.close()).thenReturn(Mono.empty());
|
||||
|
||||
databaseClientBuilder = DatabaseClient.builder().connectionFactory(
|
||||
connectionFactory).bindMarkers(BindMarkersFactory.indexed("$", 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCloseConnectionOnlyOnce() {
|
||||
DefaultDatabaseClient databaseClient = (DefaultDatabaseClient) databaseClientBuilder.build();
|
||||
|
||||
Flux<Object> flux = databaseClient.inConnectionMany(connection -> Flux.empty());
|
||||
|
||||
flux.subscribe(new CoreSubscriber<Object>() {
|
||||
|
||||
Subscription subscription;
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
s.request(1);
|
||||
subscription = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(Object o) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
subscription.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
verify(connection, times(1)).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeShouldBindNullValues() {
|
||||
Statement statement = mockStatementFor("SELECT * FROM table WHERE key = $1");
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.namedParameters(false).build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = $1").bindNull(0,
|
||||
String.class).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bindNull(0, String.class);
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = $1").bindNull("$1",
|
||||
String.class).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bindNull("$1", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeShouldBindSettableValues() {
|
||||
Statement statement = mockStatementFor("SELECT * FROM table WHERE key = $1");
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.namedParameters(false).build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = $1").bind(0,
|
||||
Parameter.empty(String.class)).then().as(
|
||||
StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bindNull(0, String.class);
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = $1").bind("$1",
|
||||
Parameter.empty(String.class)).then().as(
|
||||
StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bindNull("$1", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeShouldBindNamedNullValues() {
|
||||
|
||||
Statement statement = mockStatementFor("SELECT * FROM table WHERE key = $1");
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = :key").bindNull("key",
|
||||
String.class).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bindNull(0, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeShouldBindNamedValuesFromIndexes() {
|
||||
Statement statement = mockStatementFor(
|
||||
"SELECT id, name, manual FROM legoset WHERE name IN ($1, $2, $3)");
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql(
|
||||
"SELECT id, name, manual FROM legoset WHERE name IN (:name)").bind(0,
|
||||
Arrays.asList("unknown", "dunno", "other")).then().as(
|
||||
StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bind(0, "unknown");
|
||||
verify(statement).bind(1, "dunno");
|
||||
verify(statement).bind(2, "other");
|
||||
verify(statement).execute();
|
||||
verifyNoMoreInteractions(statement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeShouldBindValues() {
|
||||
Statement statement = mockStatementFor("SELECT * FROM table WHERE key = $1");
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = $1").bind(0,
|
||||
Parameter.from("foo")).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bind(0, "foo");
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = $1").bind("$1",
|
||||
"foo").then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bind("$1", "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeShouldBindNamedValuesByIndex() {
|
||||
|
||||
Statement statement = mockStatementFor("SELECT * FROM table WHERE key = $1");
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM table WHERE key = :key").bind("key",
|
||||
"foo").then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
verify(statement).bind(0, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rowsUpdatedShouldEmitSingleValue() {
|
||||
|
||||
Result result = mock(Result.class);
|
||||
when(result.getRowsUpdated()).thenReturn(Mono.empty(), Mono.just(2),
|
||||
Flux.just(1, 2, 3));
|
||||
mockStatementFor("DROP TABLE tab;", result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("DROP TABLE tab;").fetch().rowsUpdated().as(
|
||||
StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
databaseClient.sql("DROP TABLE tab;").fetch().rowsUpdated().as(
|
||||
StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
databaseClient.sql("DROP TABLE tab;").fetch().rowsUpdated().as(
|
||||
StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectShouldEmitFirstValue() {
|
||||
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(
|
||||
MockColumnMetadata.builder().name("name").build()).build();
|
||||
|
||||
MockResult.Builder resultBuilder = MockResult.builder().rowMetadata(metadata);
|
||||
MockResult result = resultBuilder.row(MockRow.builder().identified(0, Object.class, "Walter").build())
|
||||
.row(MockRow.builder().identified(0, Object.class, "White").build()).build();
|
||||
|
||||
mockStatementFor("SELECT * FROM person", result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM person").map(row -> row.get(0))
|
||||
.first()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext("Walter")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectShouldEmitAllValues() {
|
||||
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(
|
||||
MockColumnMetadata.builder().name("name").build()).build();
|
||||
|
||||
MockResult.Builder resultBuilder = MockResult.builder().rowMetadata(metadata);
|
||||
MockResult result = resultBuilder.row(MockRow.builder().identified(0, Object.class, "Walter").build())
|
||||
.row(MockRow.builder().identified(0, Object.class, "White").build()).build();
|
||||
|
||||
mockStatementFor("SELECT * FROM person", result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM person").map(row -> row.get(0))
|
||||
.all()
|
||||
.as(StepVerifier::create)
|
||||
.expectNext("Walter")
|
||||
.expectNext("White")
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectOneShouldFailWithException() {
|
||||
|
||||
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(
|
||||
MockColumnMetadata.builder().name("name").build()).build();
|
||||
|
||||
MockResult.Builder resultBuilder = MockResult.builder().rowMetadata(metadata);
|
||||
MockResult result = resultBuilder.row(MockRow.builder().identified(0, Object.class, "Walter").build())
|
||||
.row(MockRow.builder().identified(0, Object.class, "White").build()).build();
|
||||
|
||||
mockStatementFor("SELECT * FROM person", result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT * FROM person").map(row -> row.get(0))
|
||||
.one()
|
||||
.as(StepVerifier::create)
|
||||
.verifyError(IncorrectResultSizeDataAccessException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyExecuteFunction() {
|
||||
|
||||
Statement statement = mockStatement();
|
||||
MockResult result = mockSingleColumnResult(
|
||||
MockRow.builder().identified(0, Object.class, "Walter"));
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.executeFunction(
|
||||
stmnt -> Mono.just(result)).build();
|
||||
|
||||
databaseClient.sql("SELECT").fetch().all().as(
|
||||
StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
verifyNoInteractions(statement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyPreparedOperation() {
|
||||
|
||||
MockResult result = mockSingleColumnResult(
|
||||
MockRow.builder().identified(0, Object.class, "Walter"));
|
||||
Statement statement = mockStatementFor("SELECT * FROM person", result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql(new PreparedOperation<String>() {
|
||||
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return "SELECT * FROM person";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSource() {
|
||||
return "SELECT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(BindTarget target) {
|
||||
target.bind("index", "value");
|
||||
}
|
||||
}).fetch().all().as(
|
||||
StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
verify(statement).bind("index", "value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplyStatementFilterFunctions() {
|
||||
|
||||
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(
|
||||
MockColumnMetadata.builder().name("name").build()).build();
|
||||
MockResult result = MockResult.builder().rowMetadata(metadata).build();
|
||||
|
||||
Statement statement = mockStatement(result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT").filter(
|
||||
(s, next) -> next.execute(s.returnGeneratedValues("foo"))).filter(
|
||||
(s, next) -> next.execute(
|
||||
s.returnGeneratedValues("bar"))).fetch().all().as(
|
||||
StepVerifier::create).verifyComplete();
|
||||
|
||||
InOrder inOrder = inOrder(statement);
|
||||
inOrder.verify(statement).returnGeneratedValues("foo");
|
||||
inOrder.verify(statement).returnGeneratedValues("bar");
|
||||
inOrder.verify(statement).execute();
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldApplySimpleStatementFilterFunctions() {
|
||||
|
||||
MockResult result = mockSingleColumnEmptyResult();
|
||||
|
||||
Statement statement = mockStatement(result);
|
||||
|
||||
DatabaseClient databaseClient = databaseClientBuilder.build();
|
||||
|
||||
databaseClient.sql("SELECT").filter(
|
||||
s -> s.returnGeneratedValues("foo")).filter(
|
||||
s -> s.returnGeneratedValues("bar")).fetch().all().as(
|
||||
StepVerifier::create).verifyComplete();
|
||||
|
||||
InOrder inOrder = inOrder(statement);
|
||||
inOrder.verify(statement).returnGeneratedValues("foo");
|
||||
inOrder.verify(statement).returnGeneratedValues("bar");
|
||||
inOrder.verify(statement).execute();
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
private Statement mockStatement() {
|
||||
return mockStatementFor(null, null);
|
||||
}
|
||||
|
||||
private Statement mockStatement(Result result) {
|
||||
return mockStatementFor(null, result);
|
||||
}
|
||||
|
||||
private Statement mockStatementFor(String sql) {
|
||||
return mockStatementFor(sql, null);
|
||||
}
|
||||
|
||||
private Statement mockStatementFor(@Nullable String sql, @Nullable Result result) {
|
||||
|
||||
Statement statement = mock(Statement.class);
|
||||
when(connection.createStatement(sql == null ? anyString() : eq(sql))).thenReturn(
|
||||
statement);
|
||||
when(statement.returnGeneratedValues(anyString())).thenReturn(statement);
|
||||
when(statement.returnGeneratedValues()).thenReturn(statement);
|
||||
|
||||
doReturn(result == null ? Mono.empty() : Flux.just(result)).when(
|
||||
statement).execute();
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
private MockResult mockSingleColumnEmptyResult() {
|
||||
return mockSingleColumnResult(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks a {@link Result} with a single column "name" and a single row if a non null
|
||||
* row is provided.
|
||||
*/
|
||||
private MockResult mockSingleColumnResult(@Nullable MockRow.Builder row) {
|
||||
|
||||
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(
|
||||
MockColumnMetadata.builder().name("name").build()).build();
|
||||
|
||||
MockResult.Builder resultBuilder = MockResult.builder().rowMetadata(metadata);
|
||||
if (row != null) {
|
||||
resultBuilder = resultBuilder.row(row.build());
|
||||
}
|
||||
return resultBuilder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.h2.H2ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DatabaseClient} against H2.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class H2DatabaseClientIntegrationTests
|
||||
extends AbstractDatabaseClientIntegrationTests {
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id serial CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " version integer NULL,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ ");";
|
||||
|
||||
@Override
|
||||
protected ConnectionFactory createConnectionFactory() {
|
||||
return H2ConnectionFactory.inMemory("r2dbc-test");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCreateTableStatement() {
|
||||
return CREATE_TABLE_LEGOSET;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.h2.H2ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link DatabaseClient} against H2.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class H2TransactionalDatabaseClientIntegrationTests
|
||||
extends AbstractTransactionalDatabaseClientIntegrationTests {
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id integer CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " version integer NULL,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
+ " manual integer NULL\n" //
|
||||
+ ");";
|
||||
|
||||
@Override
|
||||
protected ConnectionFactory createConnectionFactory() {
|
||||
return H2ConnectionFactory.inMemory("r2dbc-transactional");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getCreateTableStatement() {
|
||||
return CREATE_TABLE_LEGOSET;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link NamedParameterUtils}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class NamedParameterUtilsUnitTests {
|
||||
|
||||
private final BindMarkersFactory BIND_MARKERS = BindMarkersFactory.indexed("$", 1);
|
||||
|
||||
@Test
|
||||
public void shouldParseSql() {
|
||||
String sql = "xxx :a yyyy :b :c :a zzzzz";
|
||||
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
assertThat(psql.getParameterNames()).containsExactly("a", "b", "c", "a");
|
||||
assertThat(psql.getTotalParameterCount()).isEqualTo(4);
|
||||
assertThat(psql.getNamedParameterCount()).isEqualTo(3);
|
||||
|
||||
String sql2 = "xxx &a yyyy ? zzzzz";
|
||||
ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2);
|
||||
assertThat(psql2.getParameterNames()).containsExactly("a");
|
||||
assertThat(psql2.getTotalParameterCount()).isEqualTo(1);
|
||||
assertThat(psql2.getNamedParameterCount()).isEqualTo(1);
|
||||
|
||||
String sql3 = "xxx &ä+:ö" + '\t' + ":ü%10 yyyy ? zzzzz";
|
||||
ParsedSql psql3 = NamedParameterUtils.parseSqlStatement(sql3);
|
||||
assertThat(psql3.getParameterNames()).containsExactly("ä", "ö", "ü");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void substituteNamedParameters() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a", "a").addValue("b", "b").addValue("c", "c");
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a :b :c", BIND_MARKERS, namedParams);
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("xxx $1 $2 $3");
|
||||
|
||||
PreparedOperation<?> operation2 = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a :b :c", BindMarkersFactory.named("@", "P", 8), namedParams);
|
||||
|
||||
assertThat(operation2.toQuery()).isEqualTo("xxx @P0a @P1b @P2c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void substituteObjectArray() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a", Arrays.asList(new Object[] { "Walter", "Heisenberg" },
|
||||
new Object[] { "Walt Jr.", "Flynn" }));
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a", BIND_MARKERS, namedParams);
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo("xxx ($1, $2), ($3, $4)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBindObjectArray() {
|
||||
MapBindParameterSource namedParams = new MapBindParameterSource(new HashMap<>());
|
||||
namedParams.addValue("a", Arrays.asList(new Object[] { "Walter", "Heisenberg" },
|
||||
new Object[] { "Walt Jr.", "Flynn" }));
|
||||
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
"xxx :a", BIND_MARKERS, namedParams);
|
||||
operation.bindTo(bindTarget);
|
||||
|
||||
verify(bindTarget).bind(0, "Walter");
|
||||
verify(bindTarget).bind(1, "Heisenberg");
|
||||
verify(bindTarget).bind(2, "Walt Jr.");
|
||||
verify(bindTarget).bind(3, "Flynn");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlContainingComments() {
|
||||
String sql1 = "/*+ HINT */ xxx /* comment ? */ :a yyyy :b :c :a zzzzz -- :xx XX\n";
|
||||
|
||||
ParsedSql psql1 = NamedParameterUtils.parseSqlStatement(sql1);
|
||||
assertThat(expand(psql1)).isEqualTo(
|
||||
"/*+ HINT */ xxx /* comment ? */ $1 yyyy $2 $3 $1 zzzzz -- :xx XX\n");
|
||||
|
||||
MapBindParameterSource paramMap = new MapBindParameterSource(new HashMap<>());
|
||||
paramMap.addValue("a", "a");
|
||||
paramMap.addValue("b", "b");
|
||||
paramMap.addValue("c", "c");
|
||||
|
||||
String sql2 = "/*+ HINT */ xxx /* comment ? */ :a yyyy :b :c :a zzzzz -- :xx XX";
|
||||
ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2);
|
||||
assertThat(expand(psql2)).isEqualTo(
|
||||
"/*+ HINT */ xxx /* comment ? */ $1 yyyy $2 $3 $1 zzzzz -- :xx XX");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithPostgresCasting() {
|
||||
String expectedSql = "select 'first name' from artists where id = $1 and birth_date=$2::timestamp";
|
||||
String sql = "select 'first name' from artists where id = :id and birth_date=:birthDate::timestamp";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
PreparedOperation<?> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
parsedSql, BIND_MARKERS, new MapBindParameterSource());
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithPostgresContainedOperator() {
|
||||
String expectedSql = "select 'first name' from artists where info->'stat'->'albums' = ?? $1 and '[\"1\",\"2\",\"3\"]'::jsonb ?? '4'";
|
||||
String sql = "select 'first name' from artists where info->'stat'->'albums' = ?? :album and '[\"1\",\"2\",\"3\"]'::jsonb ?? '4'";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(parsedSql.getTotalParameterCount()).isEqualTo(1);
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithPostgresAnyArrayStringsExistsOperator() {
|
||||
String expectedSql = "select '[\"3\", \"11\"]'::jsonb ?| '{1,3,11,12,17}'::text[]";
|
||||
String sql = "select '[\"3\", \"11\"]'::jsonb ?| '{1,3,11,12,17}'::text[]";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(parsedSql.getTotalParameterCount()).isEqualTo(0);
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithPostgresAllArrayStringsExistsOperator() {
|
||||
String expectedSql = "select '[\"3\", \"11\"]'::jsonb ?& '{1,3,11,12,17}'::text[] AND $1 = 'Back in Black'";
|
||||
String sql = "select '[\"3\", \"11\"]'::jsonb ?& '{1,3,11,12,17}'::text[] AND :album = 'Back in Black'";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
assertThat(parsedSql.getTotalParameterCount()).isEqualTo(1);
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithEscapedColon() {
|
||||
String expectedSql = "select '0\\:0' as a, foo from bar where baz < DATE($1 23:59:59) and baz = $2";
|
||||
String sql = "select '0\\:0' as a, foo from bar where baz < DATE(:p1 23\\:59\\:59) and baz = :p2";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(parsedSql.getParameterNames()).containsExactly("p1", "p2");
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithBracketDelimitedParameterNames() {
|
||||
String expectedSql = "select foo from bar where baz = b$1$2z";
|
||||
String sql = "select foo from bar where baz = b:{p1}:{p2}z";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
assertThat(parsedSql.getParameterNames()).containsExactly("p1", "p2");
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithEmptyBracketsOrBracketsInQuotes() {
|
||||
String expectedSql = "select foo from bar where baz = b:{}z";
|
||||
String sql = "select foo from bar where baz = b:{}z";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(parsedSql.getParameterNames()).isEmpty();
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
|
||||
String expectedSql2 = "select foo from bar where baz = 'b:{p1}z'";
|
||||
String sql2 = "select foo from bar where baz = 'b:{p1}z'";
|
||||
|
||||
ParsedSql parsedSql2 = NamedParameterUtils.parseSqlStatement(sql2);
|
||||
assertThat(parsedSql2.getParameterNames()).isEmpty();
|
||||
assertThat(expand(parsedSql2)).isEqualTo(expectedSql2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithSingleLetterInBrackets() {
|
||||
String expectedSql = "select foo from bar where baz = b$1z";
|
||||
String sql = "select foo from bar where baz = b:{p}z";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
assertThat(parsedSql.getParameterNames()).containsExactly("p");
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithLogicalAnd() {
|
||||
String expectedSql = "xxx & yyyy";
|
||||
|
||||
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(expectedSql);
|
||||
|
||||
assertThat(expand(parsedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void substituteNamedParametersWithLogicalAnd() {
|
||||
|
||||
String expectedSql = "xxx & yyyy";
|
||||
|
||||
assertThat(expand(expectedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void variableAssignmentOperator() {
|
||||
String expectedSql = "x := 1";
|
||||
|
||||
assertThat(expand(expectedSql)).isEqualTo(expectedSql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithQuotedSingleQuote() {
|
||||
String sql = "SELECT ':foo'':doo', :xxx FROM DUAL";
|
||||
|
||||
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(psql.getTotalParameterCount()).isEqualTo(1);
|
||||
assertThat(psql.getParameterNames()).containsExactly("xxx");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithQuotesAndCommentBefore() {
|
||||
String sql = "SELECT /*:doo*/':foo', :xxx FROM DUAL";
|
||||
|
||||
ParsedSql psql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(psql.getTotalParameterCount()).isEqualTo(1);
|
||||
assertThat(psql.getParameterNames()).containsExactly("xxx");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSqlStatementWithQuotesAndCommentAfter() {
|
||||
String sql2 = "SELECT ':foo'/*:doo*/, :xxx FROM DUAL";
|
||||
|
||||
ParsedSql psql2 = NamedParameterUtils.parseSqlStatement(sql2);
|
||||
|
||||
assertThat(psql2.getTotalParameterCount()).isEqualTo(1);
|
||||
assertThat(psql2.getParameterNames()).containsExactly("xxx");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAllowParsingMultipleUseOfParameter() {
|
||||
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
ParsedSql parsed = NamedParameterUtils.parseSqlStatement(sql);
|
||||
|
||||
assertThat(parsed.getTotalParameterCount()).isEqualTo(2);
|
||||
assertThat(parsed.getNamedParameterCount()).isEqualTo(1);
|
||||
assertThat(parsed.getParameterNames()).containsExactly("id", "id");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleEqualParameterReferencesBindsValueOnce() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameter.from("foo"))));
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = $0 or lastname = $0");
|
||||
|
||||
operation.bindTo(new BindTarget() {
|
||||
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
assertThat(index).isEqualTo(0);
|
||||
assertThat(value).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleEqualCollectionParameterReferencesBindsValueOnce() {
|
||||
String sql = "SELECT * FROM person where name IN (:ids) or lastname IN (:ids)";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
MultiValueMap<Integer, Object> bindings = new LinkedMultiValueMap<>();
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(Collections.singletonMap("ids",
|
||||
Parameter.from(Arrays.asList("foo", "bar", "baz")))));
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name IN ($0, $1, $2) or lastname IN ($0, $1, $2)");
|
||||
|
||||
operation.bindTo(new BindTarget() {
|
||||
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
assertThat(index).isIn(0, 1, 2);
|
||||
assertThat(value).isIn("foo", "bar", "baz");
|
||||
|
||||
bindings.add(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(bindings).containsEntry(0, Collections.singletonList("foo")) //
|
||||
.containsEntry(1, Collections.singletonList("bar")) //
|
||||
.containsEntry(2, Collections.singletonList("baz"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleEqualParameterReferencesForAnonymousMarkersBindsValueMultipleTimes() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.anonymous("?");
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameter.from("foo"))));
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = ? or lastname = ?");
|
||||
|
||||
Map<Integer, Object> bindValues = new LinkedHashMap<>();
|
||||
|
||||
operation.bindTo(new BindTarget() {
|
||||
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
bindValues.put(index, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(bindValues).hasSize(2).containsEntry(0, "foo").containsEntry(1, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleEqualParameterReferencesBindsNullOnce() {
|
||||
String sql = "SELECT * FROM person where name = :id or lastname = :id";
|
||||
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
PreparedOperation<String> operation = NamedParameterUtils.substituteNamedParameters(
|
||||
sql, factory, new MapBindParameterSource(
|
||||
Collections.singletonMap("id", Parameter.empty(String.class))));
|
||||
|
||||
assertThat(operation.toQuery()).isEqualTo(
|
||||
"SELECT * FROM person where name = $0 or lastname = $0");
|
||||
|
||||
operation.bindTo(new BindTarget() {
|
||||
|
||||
@Override
|
||||
public void bind(String identifier, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(int index, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(String identifier, Class<?> type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindNull(int index, Class<?> type) {
|
||||
assertThat(index).isEqualTo(0);
|
||||
assertThat(type).isEqualTo(String.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String expand(ParsedSql sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
|
||||
new MapBindParameterSource()).toQuery();
|
||||
}
|
||||
|
||||
private String expand(String sql) {
|
||||
return NamedParameterUtils.substituteNamedParameters(sql, BIND_MARKERS,
|
||||
new MapBindParameterSource()).toQuery();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core.binding;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnonymousBindMarkers}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class AnonymousBindMarkersUnitTests {
|
||||
|
||||
@Test
|
||||
public void shouldCreateNewBindMarkers() {
|
||||
BindMarkersFactory factory = BindMarkersFactory.anonymous("?");
|
||||
|
||||
BindMarkers bindMarkers1 = factory.create();
|
||||
BindMarkers bindMarkers2 = factory.create();
|
||||
|
||||
assertThat(bindMarkers1.next().getPlaceholder()).isEqualTo("?");
|
||||
assertThat(bindMarkers2.next().getPlaceholder()).isEqualTo("?");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBindByIndex() {
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.anonymous("?").create();
|
||||
|
||||
BindMarker first = bindMarkers.next();
|
||||
BindMarker second = bindMarkers.next();
|
||||
|
||||
second.bind(bindTarget, "foo");
|
||||
first.bindNull(bindTarget, Object.class);
|
||||
|
||||
verify(bindTarget).bindNull(0, Object.class);
|
||||
verify(bindTarget).bind(1, "foo");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core.binding;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Bindings}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class BindingsUnitTests {
|
||||
|
||||
BindMarkersFactory markersFactory = BindMarkersFactory.indexed("$", 1);
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
@Test
|
||||
void shouldCreateBindings() {
|
||||
MutableBindings bindings = new MutableBindings(markersFactory.create());
|
||||
|
||||
bindings.bind(bindings.nextMarker(), "foo");
|
||||
bindings.bindNull(bindings.nextMarker(), String.class);
|
||||
|
||||
assertThat(bindings).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyValueBinding() {
|
||||
MutableBindings bindings = new MutableBindings(markersFactory.create());
|
||||
|
||||
bindings.bind(bindings.nextMarker(), "foo");
|
||||
bindings.apply(bindTarget);
|
||||
|
||||
verify(bindTarget).bind(0, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplySimpleValueBinding() {
|
||||
MutableBindings bindings = new MutableBindings(markersFactory.create());
|
||||
|
||||
BindMarker marker = bindings.bind("foo");
|
||||
bindings.apply(bindTarget);
|
||||
|
||||
assertThat(marker.getPlaceholder()).isEqualTo("$1");
|
||||
verify(bindTarget).bind(0, "foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyNullBinding() {
|
||||
MutableBindings bindings = new MutableBindings(markersFactory.create());
|
||||
|
||||
bindings.bindNull(bindings.nextMarker(), String.class);
|
||||
|
||||
bindings.apply(bindTarget);
|
||||
|
||||
verify(bindTarget).bindNull(0, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplySimpleNullBinding() {
|
||||
MutableBindings bindings = new MutableBindings(markersFactory.create());
|
||||
|
||||
BindMarker marker = bindings.bindNull(String.class);
|
||||
bindings.apply(bindTarget);
|
||||
|
||||
assertThat(marker.getPlaceholder()).isEqualTo("$1");
|
||||
verify(bindTarget).bindNull(0, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConsumeBindings() {
|
||||
MutableBindings bindings = new MutableBindings(markersFactory.create());
|
||||
|
||||
bindings.bind(bindings.nextMarker(), "foo");
|
||||
bindings.bindNull(bindings.nextMarker(), String.class);
|
||||
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
bindings.forEach(binding -> {
|
||||
|
||||
if (binding.hasValue()) {
|
||||
counter.incrementAndGet();
|
||||
assertThat(binding.getValue()).isEqualTo("foo");
|
||||
assertThat(binding.getBindMarker().getPlaceholder()).isEqualTo("$1");
|
||||
}
|
||||
|
||||
if (binding.isNull()) {
|
||||
counter.incrementAndGet();
|
||||
|
||||
assertThat(((Bindings.NullBinding) binding).getValueType()).isEqualTo(String.class);
|
||||
assertThat(binding.getBindMarker().getPlaceholder()).isEqualTo("$2");
|
||||
}
|
||||
});
|
||||
|
||||
assertThat(counter).hasValue(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMergeBindings() {
|
||||
BindMarkers markers = markersFactory.create();
|
||||
|
||||
BindMarker shared = markers.next();
|
||||
BindMarker leftMarker = markers.next();
|
||||
List<Bindings.Binding> left = new ArrayList<>();
|
||||
left.add(new Bindings.NullBinding(shared, String.class));
|
||||
left.add(new Bindings.ValueBinding(leftMarker, "left"));
|
||||
|
||||
BindMarker rightMarker = markers.next();
|
||||
List<Bindings.Binding> right = new ArrayList<>();
|
||||
left.add(new Bindings.ValueBinding(shared, "override"));
|
||||
left.add(new Bindings.ValueBinding(rightMarker, "right"));
|
||||
|
||||
Bindings merged = Bindings.merge(new Bindings(left), new Bindings(right));
|
||||
|
||||
assertThat(merged).hasSize(3);
|
||||
|
||||
merged.apply(bindTarget);
|
||||
verify(bindTarget).bind(0, "override");
|
||||
verify(bindTarget).bind(1, "left");
|
||||
verify(bindTarget).bind(2, "right");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core.binding;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link IndexedBindMarkers}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class IndexedBindMarkersUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldCreateNewBindMarkers() {
|
||||
BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
|
||||
|
||||
BindMarkers bindMarkers1 = factory.create();
|
||||
BindMarkers bindMarkers2 = factory.create();
|
||||
|
||||
assertThat(bindMarkers1.next().getPlaceholder()).isEqualTo("$0");
|
||||
assertThat(bindMarkers2.next().getPlaceholder()).isEqualTo("$0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateNewBindMarkersWithOffset() {
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 1).create();
|
||||
|
||||
BindMarker first = bindMarkers.next();
|
||||
first.bind(bindTarget, "foo");
|
||||
|
||||
BindMarker second = bindMarkers.next();
|
||||
second.bind(bindTarget, "bar");
|
||||
|
||||
assertThat(first.getPlaceholder()).isEqualTo("$1");
|
||||
assertThat(second.getPlaceholder()).isEqualTo("$2");
|
||||
verify(bindTarget).bind(0, "foo");
|
||||
verify(bindTarget).bind(1, "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nextShouldIncrementBindMarker() {
|
||||
String[] prefixes = { "$", "?" };
|
||||
|
||||
for (String prefix : prefixes) {
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.indexed(prefix, 0).create();
|
||||
|
||||
BindMarker marker1 = bindMarkers.next();
|
||||
BindMarker marker2 = bindMarkers.next();
|
||||
|
||||
assertThat(marker1.getPlaceholder()).isEqualTo(prefix + "0");
|
||||
assertThat(marker2.getPlaceholder()).isEqualTo(prefix + "1");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindValueShouldBindByIndex() {
|
||||
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create();
|
||||
|
||||
bindMarkers.next().bind(bindTarget, "foo");
|
||||
bindMarkers.next().bind(bindTarget, "bar");
|
||||
|
||||
verify(bindTarget).bind(0, "foo");
|
||||
verify(bindTarget).bind(1, "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindNullShouldBindByIndex() {
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create();
|
||||
|
||||
bindMarkers.next(); // ignore
|
||||
bindMarkers.next().bindNull(bindTarget, Integer.class);
|
||||
|
||||
verify(bindTarget).bindNull(1, Integer.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core.binding;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link NamedBindMarkers}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class NamedBindMarkersUnitTests {
|
||||
|
||||
@Test
|
||||
void shouldCreateNewBindMarkers() {
|
||||
BindMarkersFactory factory = BindMarkersFactory.named("@", "p", 32);
|
||||
|
||||
BindMarkers bindMarkers1 = factory.create();
|
||||
BindMarkers bindMarkers2 = factory.create();
|
||||
|
||||
assertThat(bindMarkers1.next().getPlaceholder()).isEqualTo("@p0");
|
||||
assertThat(bindMarkers2.next().getPlaceholder()).isEqualTo("@p0");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = { "$", "?" })
|
||||
void nextShouldIncrementBindMarker(String prefix) {
|
||||
BindMarkers bindMarkers = BindMarkersFactory.named(prefix, "p", 32).create();
|
||||
|
||||
BindMarker marker1 = bindMarkers.next();
|
||||
BindMarker marker2 = bindMarkers.next();
|
||||
|
||||
assertThat(marker1.getPlaceholder()).isEqualTo(prefix + "p0");
|
||||
assertThat(marker2.getPlaceholder()).isEqualTo(prefix + "p1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nextShouldConsiderNameHint() {
|
||||
BindMarkers bindMarkers = BindMarkersFactory.named("@", "x", 32).create();
|
||||
|
||||
BindMarker marker1 = bindMarkers.next("foo1bar");
|
||||
BindMarker marker2 = bindMarkers.next();
|
||||
|
||||
assertThat(marker1.getPlaceholder()).isEqualTo("@x0foo1bar");
|
||||
assertThat(marker2.getPlaceholder()).isEqualTo("@x1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nextShouldConsiderFilteredNameHint() {
|
||||
BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32,
|
||||
s -> s.chars().filter(Character::isAlphabetic).collect(StringBuilder::new,
|
||||
StringBuilder::appendCodePoint, StringBuilder::append).toString()).create();
|
||||
|
||||
BindMarker marker1 = bindMarkers.next("foo1.bar?");
|
||||
BindMarker marker2 = bindMarkers.next();
|
||||
|
||||
assertThat(marker1.getPlaceholder()).isEqualTo("@p0foobar");
|
||||
assertThat(marker2.getPlaceholder()).isEqualTo("@p1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nextShouldConsiderNameLimit() {
|
||||
BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 10).create();
|
||||
|
||||
BindMarker marker1 = bindMarkers.next("123456789");
|
||||
|
||||
assertThat(marker1.getPlaceholder()).isEqualTo("@p012345678");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindValueShouldBindByName() {
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create();
|
||||
|
||||
bindMarkers.next().bind(bindTarget, "foo");
|
||||
bindMarkers.next().bind(bindTarget, "bar");
|
||||
|
||||
verify(bindTarget).bind("p0", "foo");
|
||||
verify(bindTarget).bind("p1", "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindNullShouldBindByName() {
|
||||
BindTarget bindTarget = mock(BindTarget.class);
|
||||
|
||||
BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create();
|
||||
|
||||
bindMarkers.next(); // ignore
|
||||
bindMarkers.next().bindNull(bindTarget, Integer.class);
|
||||
|
||||
verify(bindTarget).bindNull("p1", Integer.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Unit tests for [DatabaseClient] extensions.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Jonas Bark
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DatabaseClientExtensionsTests {
|
||||
|
||||
@Test
|
||||
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, Parameter.fromOrEmpty("foo", String::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
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, Parameter.empty(String::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
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", Parameter.fromOrEmpty("foo", String::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
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", Parameter.empty(String::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun genericExecuteSpecAwait() {
|
||||
val spec = mockk<DatabaseClient.GenericExecuteSpec>()
|
||||
every { spec.then() } returns Mono.empty()
|
||||
|
||||
runBlocking {
|
||||
spec.await()
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.then()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.dao.EmptyResultDataAccessException
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Unit tests for [RowsFetchSpec] extensions.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class RowsFetchSpecExtensionsTests {
|
||||
|
||||
@Test
|
||||
fun awaitOneWithValue() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.one() } returns Mono.just("foo")
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitOne()).isEqualTo("foo")
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.one()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitOneWithNull() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.one() } returns Mono.empty()
|
||||
|
||||
assertThatExceptionOfType(EmptyResultDataAccessException::class.java).isThrownBy {
|
||||
runBlocking { spec.awaitOne() }
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.one()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitOneOrNullWithValue() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.one() } returns Mono.just("foo")
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitOneOrNull()).isEqualTo("foo")
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.one()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitOneOrNullWithNull() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.one() } returns Mono.empty()
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitOneOrNull()).isNull()
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.one()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitFirstWithValue() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.first() } returns Mono.just("foo")
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitFirst()).isEqualTo("foo")
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.first()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitFirstWithNull() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.first() } returns Mono.empty()
|
||||
|
||||
assertThatExceptionOfType(EmptyResultDataAccessException::class.java).isThrownBy {
|
||||
runBlocking { spec.awaitFirst() }
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.first()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitFirstOrNullWithValue() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.first() } returns Mono.just("foo")
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitFirstOrNull()).isEqualTo("foo")
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.first()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun awaitFirstOrNullWithNull() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.first() } returns Mono.empty()
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitFirstOrNull()).isNull()
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.first()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExperimentalCoroutinesApi
|
||||
fun allAsFlow() {
|
||||
val spec = mockk<RowsFetchSpec<String>>()
|
||||
every { spec.all() } returns Flux.just("foo", "bar", "baz")
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.flow().toList()).contains("foo", "bar", "baz")
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.all()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2002-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.r2dbc.core
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Unit tests for [UpdatedRowsFetchSpec] extensions.
|
||||
*
|
||||
* @author Fred Montariol
|
||||
*/
|
||||
class UpdatedRowsFetchSpecExtensionsTests {
|
||||
|
||||
@Test
|
||||
fun awaitRowsUpdatedWithValue() {
|
||||
val spec = mockk<UpdatedRowsFetchSpec>()
|
||||
every { spec.rowsUpdated() } returns Mono.just(42)
|
||||
|
||||
runBlocking {
|
||||
assertThat(spec.awaitRowsUpdated()).isEqualTo(42)
|
||||
}
|
||||
|
||||
verify {
|
||||
spec.rowsUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Failed DROP can be ignored if necessary
|
||||
drop table users;
|
||||
|
||||
-- Create the test table
|
||||
create table users (last_name varchar(50) not null);
|
||||
@@ -0,0 +1,3 @@
|
||||
drop table users if exists;
|
||||
|
||||
create table users (last_name varchar(50) not null);
|
||||
@@ -0,0 +1,2 @@
|
||||
insert into users (last_name) values ('Heisenberg')@@
|
||||
insert into users (last_name) values ('Jesse')@@
|
||||
@@ -0,0 +1 @@
|
||||
insert into users (last_name) values ('''Heisenberg''');
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO users(first_name, last_name) values('Walter', 'White');
|
||||
@@ -0,0 +1,5 @@
|
||||
insert into users (last_name)
|
||||
values ('Walter')
|
||||
|
||||
insert into users (last_name)
|
||||
values ('Jesse')
|
||||
@@ -0,0 +1,2 @@
|
||||
insert into users (last_name) values ('Heisenberg');
|
||||
insert into users (last_name) values ('Jesse');
|
||||
@@ -0,0 +1 @@
|
||||
insert into users (last_name) values ('\$Heisenberg\$');
|
||||
@@ -0,0 +1 @@
|
||||
insert into users (last_name) values ('Heisenberg');
|
||||
@@ -0,0 +1,9 @@
|
||||
-- The next comment line starts with a tab.
|
||||
-- x, y, z...
|
||||
|
||||
insert into customer (id, name)
|
||||
values (1, 'Walter White');
|
||||
-- This is also a comment with a leading tab.
|
||||
insert into orders(id, order_date, customer_id) values (1, '2013-06-08', 1);
|
||||
-- This is also a comment with a leading tab, a space, and a tab.
|
||||
insert into orders(id, order_date, customer_id) values (2, '2013-06-08', 1);
|
||||
@@ -0,0 +1,16 @@
|
||||
-- The next comment line has no text after the '--' prefix.
|
||||
--
|
||||
-- The next comment line starts with a space.
|
||||
-- x, y, z...
|
||||
|
||||
insert into customer (id, name)
|
||||
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
|
||||
-- This is also a comment.
|
||||
insert into orders(id, order_date, customer_id)
|
||||
values (1, '2008-01-02', 2);
|
||||
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
|
||||
INSERT INTO persons( person_id--
|
||||
, name)
|
||||
VALUES( 1 -- person_id
|
||||
, 'Name' --name
|
||||
);--
|
||||
@@ -0,0 +1,17 @@
|
||||
/* This is a multi line comment
|
||||
* The next comment line has no text
|
||||
|
||||
* The next comment line starts with a space.
|
||||
* x, y, z...
|
||||
*/
|
||||
|
||||
INSERT INTO users(first_name, last_name) VALUES('Walter', 'White');
|
||||
-- This is also a comment.
|
||||
/*
|
||||
* Let's add another comment
|
||||
* that covers multiple lines
|
||||
*/INSERT INTO
|
||||
users(first_name, last_name)
|
||||
VALUES( 'Jesse' -- first_name
|
||||
, 'Pinkman' -- last_name
|
||||
);--
|
||||
@@ -0,0 +1,23 @@
|
||||
/* This is a multi line comment
|
||||
* The next comment line has no text
|
||||
|
||||
* The next comment line starts with a space.
|
||||
* x, y, z...
|
||||
*/
|
||||
|
||||
INSERT INTO users(first_name, last_name) VALUES('Walter', 'White');
|
||||
-- This is also a comment.
|
||||
/*-------------------------------------------
|
||||
-- A fancy multi-line comments that puts
|
||||
-- single line comments inside of a multi-line
|
||||
-- comment block.
|
||||
Moreover, the block comment end delimiter
|
||||
appears on a line that can potentially also
|
||||
be a single-line comment if we weren't
|
||||
already inside a multi-line comment run.
|
||||
-------------------------------------------*/
|
||||
INSERT INTO
|
||||
users(first_name, last_name) -- This is a single line comment containing the block-end-comment sequence here */ but it's still a single-line comment
|
||||
VALUES( 'Jesse' -- first_name
|
||||
, 'Pinkman' -- last_name
|
||||
);--
|
||||
@@ -0,0 +1,3 @@
|
||||
INSERT INTO
|
||||
users(first_name, last_name)
|
||||
values('Sam', 'Brannen');
|
||||
@@ -0,0 +1,7 @@
|
||||
DROP TABLE users IF EXISTS;
|
||||
|
||||
CREATE TABLE users (
|
||||
id INTEGER NOT NULL IDENTITY,
|
||||
first_name VARCHAR(50) NOT NULL,
|
||||
last_name VARCHAR(50) NOT NULL
|
||||
);
|
||||
Reference in New Issue
Block a user