Merge branch '6.1.x'
This commit is contained in:
@@ -17,14 +17,26 @@
|
||||
package org.springframework.jdbc.core.metadata;
|
||||
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.function.ThrowingBiFunction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link GenericCallMetaDataProvider}.
|
||||
@@ -36,36 +48,230 @@ class GenericCallMetaDataProviderTests {
|
||||
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
|
||||
|
||||
@Test
|
||||
void procedureNameWithPatternIsEscape() throws SQLException {
|
||||
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
void procedureNameWithNoMatch() throws SQLException {
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
|
||||
ResultSet noProcedure = mockProcedures();
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(noProcedure);
|
||||
ResultSet noFunction = mockProcedures();
|
||||
given(this.databaseMetaData.getFunctions(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(noFunction);
|
||||
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> provider.initializeWithProcedureColumnMetaData(this.databaseMetaData, null, null, "my_procedure"))
|
||||
.withMessageContaining("'MY_PROCEDURE'");
|
||||
|
||||
InOrder inOrder = inOrder(this.databaseMetaData);
|
||||
inOrder.verify(this.databaseMetaData).getUserName();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getFunctions(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getDatabaseProductName();
|
||||
verifyNoMoreInteractions(this.databaseMetaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
void procedureNameWithExactMatch() throws SQLException {
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
|
||||
ResultSet myProcedure = mockProcedures(new Procedure(null, null, "MY_PROCEDURE"));
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(myProcedure);
|
||||
ResultSet myProcedureColumn = mockProcedureColumns("TEST", DatabaseMetaData.procedureColumnIn);
|
||||
given(this.databaseMetaData.getProcedureColumns(null, null, "MY_PROCEDURE", null))
|
||||
.willReturn(myProcedureColumn);
|
||||
|
||||
provider.initializeWithProcedureColumnMetaData(this.databaseMetaData, null, null, "my_procedure");
|
||||
assertThat(provider.getCallParameterMetaData()).singleElement().satisfies(callParameterMetaData -> {
|
||||
assertThat(callParameterMetaData.getParameterName()).isEqualTo("TEST");
|
||||
assertThat(callParameterMetaData.getParameterType()).isEqualTo(DatabaseMetaData.procedureColumnIn);
|
||||
});
|
||||
|
||||
InOrder inOrder = inOrder(this.databaseMetaData);
|
||||
inOrder.verify(this.databaseMetaData).getUserName();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getProcedureColumns(null, null, "MY_PROCEDURE", null);
|
||||
verifyNoMoreInteractions(this.databaseMetaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
void procedureNameWithSeveralMatchesFallBackOnEscaped() throws SQLException {
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
|
||||
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
ResultSet myProcedures = mockProcedures(new Procedure(null, null, "MY_PROCEDURE"),
|
||||
new Procedure(null, null, "MYBPROCEDURE"));
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(myProcedures);
|
||||
ResultSet myProcedureEscaped = mockProcedures(new Procedure(null, null, "MY@_PROCEDURE"));
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY@_PROCEDURE"))
|
||||
.willThrow(new IllegalStateException("Expected"));
|
||||
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData(
|
||||
this.databaseMetaData, null, null, "my_procedure"));
|
||||
verify(this.databaseMetaData).getProcedures(null, null, "MY@_PROCEDURE");
|
||||
.willReturn(myProcedureEscaped);
|
||||
ResultSet myProcedureColumn = mockProcedureColumns("TEST", DatabaseMetaData.procedureColumnIn);
|
||||
given(this.databaseMetaData.getProcedureColumns(null, null, "MY@_PROCEDURE", null))
|
||||
.willReturn(myProcedureColumn);
|
||||
|
||||
provider.initializeWithProcedureColumnMetaData(this.databaseMetaData, null, null, "my_procedure");
|
||||
assertThat(provider.getCallParameterMetaData()).singleElement().satisfies(callParameterMetaData -> {
|
||||
assertThat(callParameterMetaData.getParameterName()).isEqualTo("TEST");
|
||||
assertThat(callParameterMetaData.getParameterType()).isEqualTo(DatabaseMetaData.procedureColumnIn);
|
||||
});
|
||||
|
||||
InOrder inOrder = inOrder(this.databaseMetaData);
|
||||
inOrder.verify(this.databaseMetaData).getUserName();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getSearchStringEscape();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY@_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getProcedureColumns(null, null, "MY@_PROCEDURE", null);
|
||||
verifyNoMoreInteractions(this.databaseMetaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
void schemaNameWithPatternIsEscape() throws SQLException {
|
||||
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
given(this.databaseMetaData.getProcedures(null, "MY@_SCHEMA", "TEST"))
|
||||
.willThrow(new IllegalStateException("Expected"));
|
||||
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData(
|
||||
this.databaseMetaData, null, "my_schema", "test"));
|
||||
verify(this.databaseMetaData).getProcedures(null, "MY@_SCHEMA", "TEST");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameIsNotEscapedIfEscapeCharacterIsNotAvailable() throws SQLException {
|
||||
void procedureNameWithSeveralMatchesDoesNotFallBackOnEscapedIfEscapeCharacterIsNotAvailable() throws SQLException {
|
||||
given(this.databaseMetaData.getSearchStringEscape()).willReturn(null);
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
given(this.databaseMetaData.getProcedures(null, "MY_SCHEMA", "MY_TEST"))
|
||||
.willThrow(new IllegalStateException("Expected"));
|
||||
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData(
|
||||
this.databaseMetaData, null, "my_schema", "my_test"));
|
||||
verify(this.databaseMetaData).getProcedures(null, "MY_SCHEMA", "MY_TEST");
|
||||
|
||||
ResultSet myProcedures = mockProcedures(new Procedure(null, null, "MY_PROCEDURE"),
|
||||
new Procedure(null, null, "MYBPROCEDURE"));
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(myProcedures);
|
||||
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> provider.initializeWithProcedureColumnMetaData(this.databaseMetaData, null, null, "my_procedure"))
|
||||
.withMessageContainingAll("'MY_PROCEDURE'", "null.null.MY_PROCEDURE", "null.null.MYBPROCEDURE");
|
||||
|
||||
InOrder inOrder = inOrder(this.databaseMetaData);
|
||||
inOrder.verify(this.databaseMetaData).getUserName();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getSearchStringEscape();
|
||||
verifyNoMoreInteractions(this.databaseMetaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
void procedureNameWitNoMatchFallbackOnFunction() throws SQLException {
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
|
||||
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
ResultSet noProcedure = mockProcedures();
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(noProcedure);
|
||||
ResultSet noProcedureWithEscaped = mockProcedures();
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY@_PROCEDURE"))
|
||||
.willReturn(noProcedureWithEscaped);
|
||||
ResultSet function = mockFunctions(new Procedure(null, null, "MY_PROCEDURE"));
|
||||
given(this.databaseMetaData.getFunctions(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(function);
|
||||
ResultSet myProcedureColumn = mockProcedureColumns("TEST", DatabaseMetaData.procedureColumnIn);
|
||||
given(this.databaseMetaData.getFunctionColumns(null, null, "MY_PROCEDURE", null))
|
||||
.willReturn(myProcedureColumn);
|
||||
|
||||
provider.initializeWithProcedureColumnMetaData(this.databaseMetaData, null, null, "my_procedure");
|
||||
assertThat(provider.getCallParameterMetaData()).singleElement().satisfies(callParameterMetaData -> {
|
||||
assertThat(callParameterMetaData.getParameterName()).isEqualTo("TEST");
|
||||
assertThat(callParameterMetaData.getParameterType()).isEqualTo(DatabaseMetaData.procedureColumnIn);
|
||||
});
|
||||
|
||||
InOrder inOrder = inOrder(this.databaseMetaData);
|
||||
inOrder.verify(this.databaseMetaData).getUserName();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getFunctions(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getFunctionColumns(null, null, "MY_PROCEDURE", null);
|
||||
verifyNoMoreInteractions(this.databaseMetaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
void procedureNameWitNoMatchAndSeveralFunctionsFallbacksOnEscaped() throws SQLException {
|
||||
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
|
||||
|
||||
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
ResultSet noProcedure = mockProcedures();
|
||||
given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(noProcedure);
|
||||
ResultSet functions = mockFunctions(new Procedure(null, null, "MY_PROCEDURE"),
|
||||
new Procedure(null, null, "MYBPROCEDURE"));
|
||||
given(this.databaseMetaData.getFunctions(null, null, "MY_PROCEDURE"))
|
||||
.willReturn(functions);
|
||||
ResultSet functionEscaped = mockFunctions(new Procedure(null, null, "MY@_PROCEDURE"));
|
||||
given(this.databaseMetaData.getFunctions(null, null, "MY@_PROCEDURE"))
|
||||
.willReturn(functionEscaped);
|
||||
ResultSet myProcedureColumn = mockProcedureColumns("TEST", DatabaseMetaData.procedureColumnIn);
|
||||
given(this.databaseMetaData.getFunctionColumns(null, null, "MY@_PROCEDURE", null))
|
||||
.willReturn(myProcedureColumn);
|
||||
|
||||
provider.initializeWithProcedureColumnMetaData(this.databaseMetaData, null, null, "my_procedure");
|
||||
assertThat(provider.getCallParameterMetaData()).singleElement().satisfies(callParameterMetaData -> {
|
||||
assertThat(callParameterMetaData.getParameterName()).isEqualTo("TEST");
|
||||
assertThat(callParameterMetaData.getParameterType()).isEqualTo(DatabaseMetaData.procedureColumnIn);
|
||||
});
|
||||
|
||||
InOrder inOrder = inOrder(this.databaseMetaData);
|
||||
inOrder.verify(this.databaseMetaData).getUserName();
|
||||
inOrder.verify(this.databaseMetaData).getProcedures(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getFunctions(null, null, "MY_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getSearchStringEscape();
|
||||
inOrder.verify(this.databaseMetaData).getFunctions(null, null, "MY@_PROCEDURE");
|
||||
inOrder.verify(this.databaseMetaData).getFunctionColumns(null, null, "MY@_PROCEDURE", null);
|
||||
verifyNoMoreInteractions(this.databaseMetaData);
|
||||
}
|
||||
|
||||
private ResultSet mockProcedures(Procedure... procedures) {
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
List<Boolean> next = new ArrayList<>();
|
||||
Arrays.stream(procedures).forEach(p -> next.add(true));
|
||||
applyStrings(Arrays.stream(procedures).map(Procedure::catalog).toList(), (first, then) ->
|
||||
given(resultSet.getString("PROCEDURE_CAT")).willReturn(first, then));
|
||||
applyStrings(Arrays.stream(procedures).map(Procedure::schema).toList(), (first, then) ->
|
||||
given(resultSet.getString("PROCEDURE_SCHEM")).willReturn(first, then));
|
||||
applyStrings(Arrays.stream(procedures).map(Procedure::name).toList(), (first, then) ->
|
||||
given(resultSet.getString("PROCEDURE_NAME")).willReturn(first, then));
|
||||
next.add(false);
|
||||
applyBooleans(next, (first, then) -> given(resultSet.next()).willReturn(first, then));
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
private ResultSet mockFunctions(Procedure... procedures) {
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
List<Boolean> next = new ArrayList<>();
|
||||
Arrays.stream(procedures).forEach(p -> next.add(true));
|
||||
applyStrings(Arrays.stream(procedures).map(Procedure::catalog).toList(), (first, then) ->
|
||||
given(resultSet.getString("FUNCTION_CAT")).willReturn(first, then));
|
||||
applyStrings(Arrays.stream(procedures).map(Procedure::schema).toList(), (first, then) ->
|
||||
given(resultSet.getString("FUNCTION_SCHEM")).willReturn(first, then));
|
||||
applyStrings(Arrays.stream(procedures).map(Procedure::name).toList(), (first, then) ->
|
||||
given(resultSet.getString("FUNCTION_NAME")).willReturn(first, then));
|
||||
next.add(false);
|
||||
applyBooleans(next, (first, then) -> given(resultSet.next()).willReturn(first, then));
|
||||
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
private ResultSet mockProcedureColumns(String columnName, int columnType) throws SQLException {
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.next()).willReturn(true, false);
|
||||
given(resultSet.getString("COLUMN_NAME")).willReturn(columnName);
|
||||
given(resultSet.getInt("COLUMN_TYPE")).willReturn(columnType);
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
record Procedure(@Nullable String catalog, @Nullable String schema, String name) {
|
||||
|
||||
}
|
||||
|
||||
private void applyBooleans(List<Boolean> content, ThrowingBiFunction<Boolean, Boolean[], Object> split) {
|
||||
apply(content, Boolean[]::new, split);
|
||||
}
|
||||
|
||||
private void applyStrings(List<String> content, ThrowingBiFunction<String, String[], Object> split) {
|
||||
apply(content, String[]::new, split);
|
||||
}
|
||||
|
||||
private <T> void apply(List<T> content, IntFunction<T[]> generator, ThrowingBiFunction<T, T[], Object> split) {
|
||||
if (content.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
T first = content.get(0);
|
||||
T[] array = content.subList(1, content.size()).toArray(generator);
|
||||
split.apply(first, array);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -239,9 +239,8 @@ class SimpleJdbcCallTests {
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
|
||||
given(databaseMetaData.getUserName()).willReturn("ME");
|
||||
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
|
||||
given(databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
given(databaseMetaData.getProcedures("", "ME", "ADD@_INVOICE")).willReturn(proceduresResultSet);
|
||||
given(databaseMetaData.getProcedureColumns("", "ME", "ADD@_INVOICE", null)).willReturn(procedureColumnsResultSet);
|
||||
given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
|
||||
given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
|
||||
|
||||
given(proceduresResultSet.next()).willReturn(true, false);
|
||||
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
|
||||
@@ -307,9 +306,8 @@ class SimpleJdbcCallTests {
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
|
||||
given(databaseMetaData.getUserName()).willReturn("ME");
|
||||
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
|
||||
given(databaseMetaData.getSearchStringEscape()).willReturn("@");
|
||||
given(databaseMetaData.getProcedures("", "ME", "ADD@_INVOICE")).willReturn(proceduresResultSet);
|
||||
given(databaseMetaData.getProcedureColumns("", "ME", "ADD@_INVOICE", null)).willReturn(procedureColumnsResultSet);
|
||||
given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
|
||||
given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
|
||||
|
||||
given(proceduresResultSet.next()).willReturn(true, false);
|
||||
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
|
||||
@@ -332,8 +330,8 @@ class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
private void verifyAddInvoiceWithMetaData(boolean isFunction) throws SQLException {
|
||||
ResultSet proceduresResultSet = databaseMetaData.getProcedures("", "ME", "ADD@_INVOICE");
|
||||
ResultSet procedureColumnsResultSet = databaseMetaData.getProcedureColumns("", "ME", "ADD@_INVOICE", null);
|
||||
ResultSet proceduresResultSet = databaseMetaData.getProcedures("", "ME", "ADD_INVOICE");
|
||||
ResultSet procedureColumnsResultSet = databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null);
|
||||
if (isFunction) {
|
||||
verify(callableStatement).registerOutParameter(1, 4);
|
||||
verify(callableStatement).setObject(2, 1103, 4);
|
||||
|
||||
Reference in New Issue
Block a user