Merge branch '6.1.x'
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
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"))
|
given(this.databaseMetaData.getProcedures(null, null, "MY@_PROCEDURE"))
|
||||||
.willThrow(new IllegalStateException("Expected"));
|
.willReturn(myProcedureEscaped);
|
||||||
assertThatIllegalStateException().isThrownBy(() -> provider.initializeWithProcedureColumnMetaData(
|
ResultSet myProcedureColumn = mockProcedureColumns("TEST", DatabaseMetaData.procedureColumnIn);
|
||||||
this.databaseMetaData, null, null, "my_procedure"));
|
given(this.databaseMetaData.getProcedureColumns(null, null, "MY@_PROCEDURE", null))
|
||||||
verify(this.databaseMetaData).getProcedures(null, null, "MY@_PROCEDURE");
|
.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 schemaNameWithPatternIsEscape() throws SQLException {
|
void procedureNameWithSeveralMatchesDoesNotFallBackOnEscapedIfEscapeCharacterIsNotAvailable() 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 {
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user