Revisit stored procedure detection
This commit revisits the improved detection algorithm for stored procedure as, unfortunately, certain JDBC drivers do not support the documented pattern for schema and procedure name. To work around this limitation, this commit applies the escaping of wildcard characters to the case where multiple procedures have been found for a given search. Closes gh-32295
This commit is contained in:
@@ -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<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"));
|
||||
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<String> 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<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
|
||||
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<String> matches, boolean function) {
|
||||
|
||||
int hits() {
|
||||
return this.matches.size();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user