diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java index 6305251156e..36383fefc57 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProvider.java +++ b/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.util.ArrayList; import java.util.List; -import java.util.Objects; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -305,47 +304,42 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { String metaDataSchemaName = metaDataSchemaNameToUse(schemaName); String metaDataProcedureName = procedureNameToUse(procedureName); try { - String searchStringEscape = databaseMetaData.getSearchStringEscape(); - String escapedSchemaName = escapeNamePattern(metaDataSchemaName, searchStringEscape); - String escapedProcedureName = escapeNamePattern(metaDataProcedureName, searchStringEscape); - if (logger.isDebugEnabled()) { - String schemaInfo = (Objects.equals(escapedSchemaName, metaDataSchemaName) - ? metaDataSchemaName : metaDataCatalogName + "(" + escapedSchemaName + ")"); - String procedureInfo = (Objects.equals(escapedProcedureName, metaDataProcedureName) - ? metaDataProcedureName : metaDataProcedureName + "(" + escapedProcedureName + ")"); - logger.debug("Retrieving meta-data for " + metaDataCatalogName + '/' + - schemaInfo + '/' + procedureInfo); - } - - List 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")); + ProcedureMetadata procedureMetadata = getProcedureMetadata(databaseMetaData, + metaDataCatalogName, metaDataSchemaName, metaDataProcedureName); + if (procedureMetadata.hits() > 1) { + // Try again with exact match in case of placeholders + String searchStringEscape = databaseMetaData.getSearchStringEscape(); + if (searchStringEscape != null) { + procedureMetadata = getProcedureMetadata(databaseMetaData, metaDataCatalogName, + escapeNamePattern(metaDataSchemaName, searchStringEscape), + escapeNamePattern(metaDataProcedureName, searchStringEscape)); } } - - if (found.isEmpty()) { + if (procedureMetadata.hits() == 0) { // Functions not exposed as procedures anymore on PostgreSQL driver 42.2.11 - try (ResultSet functions = databaseMetaData.getFunctions( - metaDataCatalogName, escapedSchemaName, escapedProcedureName)) { - while (functions.next()) { - found.add(functions.getString("FUNCTION_CAT") + '.' + functions.getString("FUNCTION_SCHEM") + - '.' + functions.getString("FUNCTION_NAME")); - function = true; + procedureMetadata = getProcedureMetadataAsFunction(databaseMetaData, + metaDataCatalogName, metaDataSchemaName, metaDataProcedureName); + if (procedureMetadata.hits() > 1) { + // Try again with exact match in case of placeholders + String searchStringEscape = databaseMetaData.getSearchStringEscape(); + if (searchStringEscape != null) { + procedureMetadata = getProcedureMetadataAsFunction( + databaseMetaData, metaDataCatalogName, + escapeNamePattern(metaDataSchemaName, searchStringEscape), + escapeNamePattern(metaDataProcedureName, searchStringEscape)); } } } + // Handling matches - if (found.size() > 1) { + boolean isFunction = procedureMetadata.function(); + List matches = procedureMetadata.matches; + if (matches.size() > 1) { throw new InvalidDataAccessApiUsageException( "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(".") && !StringUtils.hasText(metaDataCatalogName)) { String packageName = metaDataProcedureName.substring(0, metaDataProcedureName.indexOf('.')); @@ -368,16 +362,16 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { } if (logger.isDebugEnabled()) { - logger.debug("Retrieving column meta-data for " + (function ? "function" : "procedure") + ' ' + - metaDataCatalogName + '/' + metaDataSchemaName + '/' + metaDataProcedureName); + logger.debug("Retrieving column meta-data for " + (isFunction ? "function" : "procedure") + ' ' + + metaDataCatalogName + '/' + procedureMetadata.schemaName + '/' + procedureMetadata.procedureName); } - try (ResultSet columns = function ? - databaseMetaData.getFunctionColumns(metaDataCatalogName, escapedSchemaName, escapedProcedureName, null) : - databaseMetaData.getProcedureColumns(metaDataCatalogName, escapedSchemaName, escapedProcedureName, null)) { + try (ResultSet columns = isFunction ? + databaseMetaData.getFunctionColumns(metaDataCatalogName, procedureMetadata.schemaName, procedureMetadata.procedureName, null) : + databaseMetaData.getProcedureColumns(metaDataCatalogName, procedureMetadata.schemaName, procedureMetadata.procedureName, null)) { while (columns.next()) { String columnName = columns.getString("COLUMN_NAME"); int columnType = columns.getInt("COLUMN_TYPE"); - if (columnName == null && isInOrOutColumn(columnType, function)) { + if (columnName == null && isInOrOutColumn(columnType, isFunction)) { if (logger.isDebugEnabled()) { logger.debug("Skipping meta-data for: " + columnType + " " + columns.getInt("DATA_TYPE") + " " + columns.getString("TYPE_NAME") + " " + columns.getInt("NULLABLE") + @@ -385,8 +379,8 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { } } else { - int nullable = (function ? DatabaseMetaData.functionNullable : DatabaseMetaData.procedureNullable); - CallParameterMetaData meta = new CallParameterMetaData(function, columnName, columnType, + int nullable = (isFunction ? DatabaseMetaData.functionNullable : DatabaseMetaData.procedureNullable); + CallParameterMetaData meta = new CallParameterMetaData(isFunction, columnName, columnType, columns.getInt("DATA_TYPE"), columns.getString("TYPE_NAME"), columns.getInt("NULLABLE") == nullable); 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 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 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 private static String escapeNamePattern(@Nullable String name, @Nullable String escape) { if (name == null || escape == null) { @@ -436,4 +460,12 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { } } + private record ProcedureMetadata(@Nullable String schemaName, @Nullable String procedureName, + List matches, boolean function) { + + int hits() { + return this.matches.size(); + } + } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProviderTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProviderTests.java index 7aaf9e5271d..7370f6cc678 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProviderTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/metadata/GenericCallMetaDataProviderTests.java @@ -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); - 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"); + + 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 schemaNameWithPatternIsEscape() throws SQLException { - given(this.databaseMetaData.getSearchStringEscape()).willReturn("@"); + 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.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"); + + 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")) + .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 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 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 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 content, ThrowingBiFunction split) { + apply(content, Boolean[]::new, split); + } + + private void applyStrings(List content, ThrowingBiFunction split) { + apply(content, String[]::new, split); + } + + private void apply(List content, IntFunction generator, ThrowingBiFunction split) { + if (content.isEmpty()) { + return; + } + T first = content.get(0); + T[] array = content.subList(1, content.size()).toArray(generator); + split.apply(first, array); } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcCallTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcCallTests.java index 2e6050d6358..9d4342a7857 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/SimpleJdbcCallTests.java +++ b/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.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);