From 30bf8708105db3bd49c4ee470165daaab98f3f4e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 17 Jul 2020 17:47:53 +0200 Subject: [PATCH] Check JDBC 4 getFunctions (for compatibility with PostgreSQL driver 42.2.11) Closes gh-25399 --- .../metadata/GenericCallMetaDataProvider.java | 112 +++++++++++------- 1 file changed, 66 insertions(+), 46 deletions(-) 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 71983d28bc..ef4ef9a818 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,9 +46,8 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { /** Logger available to subclasses. */ protected static final Log logger = LogFactory.getLog(CallMetaDataProvider.class); - private boolean procedureColumnMetaDataUsed = false; - private String userName; + private final String userName; private boolean supportsCatalogsInProcedureCalls = true; @@ -58,7 +57,9 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { private boolean storesLowerCaseIdentifiers = false; - private List callParameterMetaData = new ArrayList<>(); + private boolean procedureColumnMetaDataUsed = false; + + private final List callParameterMetaData = new ArrayList<>(); /** @@ -328,20 +329,34 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { metaDataSchemaName + '/' + metaDataProcedureName); } - ResultSet procs = null; try { - procs = databaseMetaData.getProcedures(metaDataCatalogName, metaDataSchemaName, metaDataProcedureName); List found = new ArrayList<>(); - while (procs.next()) { - found.add(procs.getString("PROCEDURE_CAT") + '.' + procs.getString("PROCEDURE_SCHEM") + - '.' + procs.getString("PROCEDURE_NAME")); + boolean function = false; + + try (ResultSet procedures = databaseMetaData.getProcedures( + metaDataCatalogName, metaDataSchemaName, metaDataProcedureName)) { + while (procedures.next()) { + found.add(procedures.getString("PROCEDURE_CAT") + '.' + procedures.getString("PROCEDURE_SCHEM") + + '.' + procedures.getString("PROCEDURE_NAME")); + } + } + + if (found.isEmpty()) { + // Functions not exposed as procedures anymore on PostgreSQL driver 42.2.11 + try (ResultSet functions = databaseMetaData.getFunctions( + metaDataCatalogName, metaDataSchemaName, metaDataProcedureName)) { + while (functions.next()) { + found.add(functions.getString("FUNCTION_CAT") + '.' + functions.getString("FUNCTION_SCHEM") + + '.' + functions.getString("FUNCTION_NAME")); + function = true; + } + } } - procs.close(); if (found.size() > 1) { throw new InvalidDataAccessApiUsageException( - "Unable to determine the correct call signature - multiple " + - "procedures/functions/signatures for '" + metaDataProcedureName + "': found " + found); + "Unable to determine the correct call signature - multiple signatures for '" + + metaDataProcedureName + "': found " + found + " " + (function ? "functions" : "procedures")); } else if (found.isEmpty()) { if (metaDataProcedureName != null && metaDataProcedureName.contains(".") && @@ -365,30 +380,34 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { } } - procs = databaseMetaData.getProcedureColumns( - metaDataCatalogName, metaDataSchemaName, metaDataProcedureName, null); - while (procs.next()) { - String columnName = procs.getString("COLUMN_NAME"); - int columnType = procs.getInt("COLUMN_TYPE"); - if (columnName == null && ( - columnType == DatabaseMetaData.procedureColumnIn || - columnType == DatabaseMetaData.procedureColumnInOut || - columnType == DatabaseMetaData.procedureColumnOut)) { - if (logger.isDebugEnabled()) { - logger.debug("Skipping meta-data for: " + columnType + " " + procs.getInt("DATA_TYPE") + - " " + procs.getString("TYPE_NAME") + " " + procs.getInt("NULLABLE") + - " (probably a member of a collection)"); + if (logger.isDebugEnabled()) { + logger.debug("Retrieving column meta-data for " + (function ? "function" : "procedure") + ' ' + + metaDataCatalogName + '/' + metaDataSchemaName + '/' + metaDataProcedureName); + } + try (ResultSet columns = function ? + databaseMetaData.getFunctionColumns(metaDataCatalogName, metaDataSchemaName, metaDataProcedureName, null) : + databaseMetaData.getProcedureColumns(metaDataCatalogName, metaDataSchemaName, metaDataProcedureName, null)) { + while (columns.next()) { + String columnName = columns.getString("COLUMN_NAME"); + int columnType = columns.getInt("COLUMN_TYPE"); + if (columnName == null && isInOrOutColumn(columnType, function)) { + if (logger.isDebugEnabled()) { + logger.debug("Skipping meta-data for: " + columnType + " " + columns.getInt("DATA_TYPE") + + " " + columns.getString("TYPE_NAME") + " " + columns.getInt("NULLABLE") + + " (probably a member of a collection)"); + } } - } - else { - CallParameterMetaData meta = new CallParameterMetaData(columnName, columnType, - procs.getInt("DATA_TYPE"), procs.getString("TYPE_NAME"), - procs.getInt("NULLABLE") == DatabaseMetaData.procedureNullable); - this.callParameterMetaData.add(meta); - if (logger.isDebugEnabled()) { - logger.debug("Retrieved meta-data: " + meta.getParameterName() + " " + - meta.getParameterType() + " " + meta.getSqlType() + " " + - meta.getTypeName() + " " + meta.isNullable()); + else { + int nullable = (function ? DatabaseMetaData.functionNullable : DatabaseMetaData.procedureNullable); + CallParameterMetaData meta = new CallParameterMetaData(columnName, columnType, + columns.getInt("DATA_TYPE"), columns.getString("TYPE_NAME"), + columns.getInt("NULLABLE") == nullable); + this.callParameterMetaData.add(meta); + if (logger.isDebugEnabled()) { + logger.debug("Retrieved meta-data: " + meta.getParameterName() + " " + + meta.getParameterType() + " " + meta.getSqlType() + " " + + meta.getTypeName() + " " + meta.isNullable()); + } } } } @@ -398,17 +417,18 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider { logger.warn("Error while retrieving meta-data for procedure columns: " + ex); } } - finally { - try { - if (procs != null) { - procs.close(); - } - } - catch (SQLException ex) { - if (logger.isWarnEnabled()) { - logger.warn("Problem closing ResultSet for procedure column meta-data: " + ex); - } - } + } + + private static boolean isInOrOutColumn(int columnType, boolean function) { + if (function) { + return (columnType == DatabaseMetaData.functionColumnIn || + columnType == DatabaseMetaData.functionColumnInOut || + columnType == DatabaseMetaData.functionColumnOut); + } + else { + return (columnType == DatabaseMetaData.procedureColumnIn || + columnType == DatabaseMetaData.procedureColumnInOut || + columnType == DatabaseMetaData.procedureColumnOut); } }