Browse Source

Merge branch '6.1.x'

pull/32308/head
Stéphane Nicoll 2 years ago
parent
commit
6383a0d7ca
  1. 112
      spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java
  2. 250
      spring-jdbc/src/test/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProviderTests.java
  3. 14
      spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcCallTests.java

112
spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java

@ -22,7 +22,6 @@ import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -305,47 +304,42 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
String metaDataSchemaName = metaDataSchemaNameToUse(schemaName); String metaDataSchemaName = metaDataSchemaNameToUse(schemaName);
String metaDataProcedureName = procedureNameToUse(procedureName); String metaDataProcedureName = procedureNameToUse(procedureName);
try { try {
String searchStringEscape = databaseMetaData.getSearchStringEscape(); ProcedureMetadata procedureMetadata = getProcedureMetadata(databaseMetaData,
String escapedSchemaName = escapeNamePattern(metaDataSchemaName, searchStringEscape); metaDataCatalogName, metaDataSchemaName, metaDataProcedureName);
String escapedProcedureName = escapeNamePattern(metaDataProcedureName, searchStringEscape); if (procedureMetadata.hits() > 1) {
if (logger.isDebugEnabled()) { // Try again with exact match in case of placeholders
String schemaInfo = (Objects.equals(escapedSchemaName, metaDataSchemaName) String searchStringEscape = databaseMetaData.getSearchStringEscape();
? metaDataSchemaName : metaDataCatalogName + "(" + escapedSchemaName + ")"); if (searchStringEscape != null) {
String procedureInfo = (Objects.equals(escapedProcedureName, metaDataProcedureName) procedureMetadata = getProcedureMetadata(databaseMetaData, metaDataCatalogName,
? metaDataProcedureName : metaDataProcedureName + "(" + escapedProcedureName + ")"); escapeNamePattern(metaDataSchemaName, searchStringEscape),
logger.debug("Retrieving meta-data for " + metaDataCatalogName + '/' + escapeNamePattern(metaDataProcedureName, searchStringEscape));
schemaInfo + '/' + procedureInfo);
}
List<String> found = new ArrayList<>();
boolean function = false;
try (ResultSet procedures = databaseMetaData.getProcedures(
metaDataCatalogName, escapedSchemaName, escapedProcedureName)) {
while (procedures.next()) {
found.add(procedures.getString("PROCEDURE_CAT") + '.' + procedures.getString("PROCEDURE_SCHEM") +
'.' + procedures.getString("PROCEDURE_NAME"));
} }
} }
if (procedureMetadata.hits() == 0) {
if (found.isEmpty()) {
// Functions not exposed as procedures anymore on PostgreSQL driver 42.2.11 // Functions not exposed as procedures anymore on PostgreSQL driver 42.2.11
try (ResultSet functions = databaseMetaData.getFunctions( procedureMetadata = getProcedureMetadataAsFunction(databaseMetaData,
metaDataCatalogName, escapedSchemaName, escapedProcedureName)) { metaDataCatalogName, metaDataSchemaName, metaDataProcedureName);
while (functions.next()) { if (procedureMetadata.hits() > 1) {
found.add(functions.getString("FUNCTION_CAT") + '.' + functions.getString("FUNCTION_SCHEM") + // Try again with exact match in case of placeholders
'.' + functions.getString("FUNCTION_NAME")); String searchStringEscape = databaseMetaData.getSearchStringEscape();
function = true; if (searchStringEscape != null) {
procedureMetadata = getProcedureMetadataAsFunction(
databaseMetaData, metaDataCatalogName,
escapeNamePattern(metaDataSchemaName, searchStringEscape),
escapeNamePattern(metaDataProcedureName, searchStringEscape));
} }
} }
} }
// Handling matches
if (found.size() > 1) { boolean isFunction = procedureMetadata.function();
List<String> matches = procedureMetadata.matches;
if (matches.size() > 1) {
throw new InvalidDataAccessApiUsageException( throw new InvalidDataAccessApiUsageException(
"Unable to determine the correct call signature - multiple signatures for '" + "Unable to determine the correct call signature - multiple signatures for '" +
metaDataProcedureName + "': found " + found + " " + (function ? "functions" : "procedures")); metaDataProcedureName + "': found " + matches + " " + (isFunction ? "functions" : "procedures"));
} }
else if (found.isEmpty()) { else if (matches.isEmpty()) {
if (metaDataProcedureName != null && metaDataProcedureName.contains(".") && if (metaDataProcedureName != null && metaDataProcedureName.contains(".") &&
!StringUtils.hasText(metaDataCatalogName)) { !StringUtils.hasText(metaDataCatalogName)) {
String packageName = metaDataProcedureName.substring(0, metaDataProcedureName.indexOf('.')); String packageName = metaDataProcedureName.substring(0, metaDataProcedureName.indexOf('.'));
@ -368,16 +362,16 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Retrieving column meta-data for " + (function ? "function" : "procedure") + ' ' + logger.debug("Retrieving column meta-data for " + (isFunction ? "function" : "procedure") + ' ' +
metaDataCatalogName + '/' + metaDataSchemaName + '/' + metaDataProcedureName); metaDataCatalogName + '/' + procedureMetadata.schemaName + '/' + procedureMetadata.procedureName);
} }
try (ResultSet columns = function ? try (ResultSet columns = isFunction ?
databaseMetaData.getFunctionColumns(metaDataCatalogName, escapedSchemaName, escapedProcedureName, null) : databaseMetaData.getFunctionColumns(metaDataCatalogName, procedureMetadata.schemaName, procedureMetadata.procedureName, null) :
databaseMetaData.getProcedureColumns(metaDataCatalogName, escapedSchemaName, escapedProcedureName, null)) { databaseMetaData.getProcedureColumns(metaDataCatalogName, procedureMetadata.schemaName, procedureMetadata.procedureName, null)) {
while (columns.next()) { while (columns.next()) {
String columnName = columns.getString("COLUMN_NAME"); String columnName = columns.getString("COLUMN_NAME");
int columnType = columns.getInt("COLUMN_TYPE"); int columnType = columns.getInt("COLUMN_TYPE");
if (columnName == null && isInOrOutColumn(columnType, function)) { if (columnName == null && isInOrOutColumn(columnType, isFunction)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Skipping meta-data for: " + columnType + " " + columns.getInt("DATA_TYPE") + logger.debug("Skipping meta-data for: " + columnType + " " + columns.getInt("DATA_TYPE") +
" " + columns.getString("TYPE_NAME") + " " + columns.getInt("NULLABLE") + " " + columns.getString("TYPE_NAME") + " " + columns.getInt("NULLABLE") +
@ -385,8 +379,8 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
} }
} }
else { else {
int nullable = (function ? DatabaseMetaData.functionNullable : DatabaseMetaData.procedureNullable); int nullable = (isFunction ? DatabaseMetaData.functionNullable : DatabaseMetaData.procedureNullable);
CallParameterMetaData meta = new CallParameterMetaData(function, columnName, columnType, CallParameterMetaData meta = new CallParameterMetaData(isFunction, columnName, columnType,
columns.getInt("DATA_TYPE"), columns.getString("TYPE_NAME"), columns.getInt("DATA_TYPE"), columns.getString("TYPE_NAME"),
columns.getInt("NULLABLE") == nullable); columns.getInt("NULLABLE") == nullable);
this.callParameterMetaData.add(meta); this.callParameterMetaData.add(meta);
@ -413,6 +407,36 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
} }
} }
private ProcedureMetadata getProcedureMetadata(DatabaseMetaData databaseMetaData,
@Nullable String catalogName, @Nullable String schemaName, @Nullable String procedureName) throws SQLException {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving meta-data for " + catalogName + '/' + schemaName + '/' + procedureName);
}
List<String> matches = new ArrayList<>();
try (ResultSet procedures = databaseMetaData.getProcedures(catalogName, schemaName, procedureName)) {
while (procedures.next()) {
matches.add(procedures.getString("PROCEDURE_CAT") + '.' + procedures.getString("PROCEDURE_SCHEM") +
'.' + procedures.getString("PROCEDURE_NAME"));
}
}
return new ProcedureMetadata(schemaName, procedureName, matches, false);
}
private ProcedureMetadata getProcedureMetadataAsFunction(DatabaseMetaData databaseMetaData,
@Nullable String catalogName, @Nullable String schemaName, @Nullable String procedureName) throws SQLException {
if (logger.isDebugEnabled()) {
logger.debug("Fallback on retrieving function meta-data for " + catalogName + '/' + schemaName + '/' + procedureName);
}
List<String> matches = new ArrayList<>();
try (ResultSet functions = databaseMetaData.getFunctions(catalogName, schemaName, procedureName)) {
while (functions.next()) {
matches.add(functions.getString("FUNCTION_CAT") + '.' + functions.getString("FUNCTION_SCHEM") +
'.' + functions.getString("FUNCTION_NAME"));
}
}
return new ProcedureMetadata(schemaName, procedureName, matches, true);
}
@Nullable @Nullable
private static String escapeNamePattern(@Nullable String name, @Nullable String escape) { private static String escapeNamePattern(@Nullable String name, @Nullable String escape) {
if (name == null || escape == null) { if (name == null || escape == null) {
@ -436,4 +460,12 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
} }
} }
private record ProcedureMetadata(@Nullable String schemaName, @Nullable String procedureName,
List<String> matches, boolean function) {
int hits() {
return this.matches.size();
}
}
} }

250
spring-jdbc/src/test/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProviderTests.java

@ -17,14 +17,26 @@
package org.springframework.jdbc.core.metadata; package org.springframework.jdbc.core.metadata;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException; 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.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.BDDMockito.given;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* Tests for {@link GenericCallMetaDataProvider}. * Tests for {@link GenericCallMetaDataProvider}.
@ -36,36 +48,230 @@ class GenericCallMetaDataProviderTests {
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class); private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
@Test @Test
void procedureNameWithPatternIsEscape() throws SQLException { void procedureNameWithNoMatch() throws SQLException {
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData); GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
given(this.databaseMetaData.getProcedures(null, null, "MY@_PROCEDURE"))
.willThrow(new IllegalStateException("Expected")); ResultSet noProcedure = mockProcedures();
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData( given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
this.databaseMetaData, null, null, "my_procedure")); .willReturn(noProcedure);
verify(this.databaseMetaData).getProcedures(null, null, "MY@_PROCEDURE"); 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 @Test
void schemaNameWithPatternIsEscape() throws SQLException { void procedureNameWithExactMatch() throws SQLException {
given(this.databaseMetaData.getSearchStringEscape()).willReturn("@"); 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); GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
given(this.databaseMetaData.getProcedures(null, "MY@_SCHEMA", "TEST"))
.willThrow(new IllegalStateException("Expected")); given(this.databaseMetaData.getSearchStringEscape()).willReturn("@");
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData( ResultSet myProcedures = mockProcedures(new Procedure(null, null, "MY_PROCEDURE"),
this.databaseMetaData, null, "my_schema", "test")); new Procedure(null, null, "MYBPROCEDURE"));
verify(this.databaseMetaData).getProcedures(null, "MY@_SCHEMA", "TEST"); 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"))
.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 @Test
void nameIsNotEscapedIfEscapeCharacterIsNotAvailable() throws SQLException { void procedureNameWithSeveralMatchesDoesNotFallBackOnEscapedIfEscapeCharacterIsNotAvailable() throws SQLException {
given(this.databaseMetaData.getSearchStringEscape()).willReturn(null); given(this.databaseMetaData.getSearchStringEscape()).willReturn(null);
GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData); GenericCallMetaDataProvider provider = new GenericCallMetaDataProvider(this.databaseMetaData);
given(this.databaseMetaData.getProcedures(null, "MY_SCHEMA", "MY_TEST"))
.willThrow(new IllegalStateException("Expected")); ResultSet myProcedures = mockProcedures(new Procedure(null, null, "MY_PROCEDURE"),
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData( new Procedure(null, null, "MYBPROCEDURE"));
this.databaseMetaData, null, "my_schema", "my_test")); given(this.databaseMetaData.getProcedures(null, null, "MY_PROCEDURE"))
verify(this.databaseMetaData).getProcedures(null, "MY_SCHEMA", "MY_TEST"); .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);
} }
} }

14
spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcCallTests.java

@ -239,9 +239,8 @@ class SimpleJdbcCallTests {
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle"); given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
given(databaseMetaData.getUserName()).willReturn("ME"); given(databaseMetaData.getUserName()).willReturn("ME");
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true); given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
given(databaseMetaData.getSearchStringEscape()).willReturn("@"); given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
given(databaseMetaData.getProcedures("", "ME", "ADD@_INVOICE")).willReturn(proceduresResultSet); given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
given(databaseMetaData.getProcedureColumns("", "ME", "ADD@_INVOICE", null)).willReturn(procedureColumnsResultSet);
given(proceduresResultSet.next()).willReturn(true, false); given(proceduresResultSet.next()).willReturn(true, false);
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice"); given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
@ -307,9 +306,8 @@ class SimpleJdbcCallTests {
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle"); given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
given(databaseMetaData.getUserName()).willReturn("ME"); given(databaseMetaData.getUserName()).willReturn("ME");
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true); given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
given(databaseMetaData.getSearchStringEscape()).willReturn("@"); given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
given(databaseMetaData.getProcedures("", "ME", "ADD@_INVOICE")).willReturn(proceduresResultSet); given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
given(databaseMetaData.getProcedureColumns("", "ME", "ADD@_INVOICE", null)).willReturn(procedureColumnsResultSet);
given(proceduresResultSet.next()).willReturn(true, false); given(proceduresResultSet.next()).willReturn(true, false);
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice"); given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
@ -332,8 +330,8 @@ class SimpleJdbcCallTests {
} }
private void verifyAddInvoiceWithMetaData(boolean isFunction) throws SQLException { private void verifyAddInvoiceWithMetaData(boolean isFunction) throws SQLException {
ResultSet proceduresResultSet = databaseMetaData.getProcedures("", "ME", "ADD@_INVOICE"); ResultSet proceduresResultSet = databaseMetaData.getProcedures("", "ME", "ADD_INVOICE");
ResultSet procedureColumnsResultSet = databaseMetaData.getProcedureColumns("", "ME", "ADD@_INVOICE", null); ResultSet procedureColumnsResultSet = databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null);
if (isFunction) { if (isFunction) {
verify(callableStatement).registerOutParameter(1, 4); verify(callableStatement).registerOutParameter(1, 4);
verify(callableStatement).setObject(2, 1103, 4); verify(callableStatement).setObject(2, 1103, 4);

Loading…
Cancel
Save