From 8766a8e012cd08616847f26f3ccb3e0bd7441698 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 4 Jul 2024 13:57:41 +0200 Subject: [PATCH] HHH-18280 Support named procedure parameters down to the JDBC level --- .../dialect/OracleLegacyDialect.java | 6 ---- .../org/hibernate/dialect/OracleDialect.java | 4 +-- .../hibernate/dialect/SQLServerDialect.java | 9 ++++- .../internal/DB2CallableStatementSupport.java | 4 +-- .../JTDSCallableStatementSupport.java | 4 +-- .../OracleCallableStatementSupport.java | 33 +++++++++++++++++ .../PostgreSQLCallableStatementSupport.java | 10 +++--- .../internal/ProcedureParameterImpl.java | 35 +++++++++++-------- .../SQLServerCallableStatementSupport.java | 24 +++++++++++++ .../StandardCallableStatementSupport.java | 11 ++++-- .../SybaseCallableStatementSupport.java | 8 +++-- 11 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/OracleCallableStatementSupport.java create mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/SQLServerCallableStatementSupport.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index 616199bb60..14d0037cb2 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -1501,12 +1501,6 @@ public class OracleLegacyDialect extends Dialect { return 1; } - @Override - public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { - // Not sure if it's a JDBC driver issue, but it doesn't work - return false; - } - @Override public ResultSet getResultSet(CallableStatement statement, String name) throws SQLException { return (ResultSet) statement.getObject( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index bfc316f833..6b6ef56d63 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -54,7 +54,7 @@ import org.hibernate.mapping.UserDefinedType; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.mutation.EntityMutationTarget; -import org.hibernate.procedure.internal.StandardCallableStatementSupport; +import org.hibernate.procedure.internal.OracleCallableStatementSupport; import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.query.SemanticException; import org.hibernate.query.spi.QueryOptions; @@ -1314,7 +1314,7 @@ public class OracleDialect extends Dialect { @Override public CallableStatementSupport getCallableStatementSupport() { // Oracle supports returning cursors - return StandardCallableStatementSupport.REF_CURSOR_INSTANCE; + return OracleCallableStatementSupport.REF_CURSOR_INSTANCE; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index 940dc58cec..c588071b4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -54,6 +54,8 @@ import org.hibernate.exception.spi.ViolatedConstraintNameExtractor; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.mapping.Column; import org.hibernate.persister.entity.mutation.EntityMutationTarget; +import org.hibernate.procedure.internal.SQLServerCallableStatementSupport; +import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.FetchClauseType; import org.hibernate.query.sqm.IntervalType; @@ -1122,7 +1124,6 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { @Override public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { - // Not sure if it's a JDBC driver issue, but it doesn't work return false; } @@ -1168,4 +1169,10 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { public boolean supportsFromClauseInUpdate() { return true; } + + @Override + public CallableStatementSupport getCallableStatementSupport() { + return SQLServerCallableStatementSupport.INSTANCE; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/DB2CallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/DB2CallableStatementSupport.java index 4be80f54dd..a45e491555 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/DB2CallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/DB2CallableStatementSupport.java @@ -84,8 +84,8 @@ public class DB2CallableStatementSupport extends AbstractStandardCallableStateme i + offset, procedureCall ); - if ( registration.getName() != null ) { - buffer.append( ':' ).append( registration.getName() ); + if ( parameter.getName() != null ) { + buffer.append( ':' ).append( parameter.getName() ); } else { buffer.append( "?" ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/JTDSCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/JTDSCallableStatementSupport.java index a54477f1ba..7a4cce5b40 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/JTDSCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/JTDSCallableStatementSupport.java @@ -69,8 +69,8 @@ public class JTDSCallableStatementSupport extends AbstractStandardCallableStatem i + offset, procedureCall ); - if ( registration.getName() != null ) { - buffer.append( '@' ).append( registration.getName() ).append( "=?" ); + if ( parameter.getName() != null ) { + buffer.append( '@' ).append( parameter.getName() ).append( "=?" ); } else { buffer.append( "?" ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/OracleCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/OracleCallableStatementSupport.java new file mode 100644 index 0000000000..4f7db51b1a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/OracleCallableStatementSupport.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.procedure.internal; + +import org.hibernate.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; + +/** + * Standard implementation of {@link org.hibernate.procedure.spi.CallableStatementSupport}. + * + * @author Steve Ebersole + */ +public class OracleCallableStatementSupport extends StandardCallableStatementSupport { + + public static final StandardCallableStatementSupport REF_CURSOR_INSTANCE = new OracleCallableStatementSupport( true ); + + + public OracleCallableStatementSupport(boolean supportsRefCursors) { + super( supportsRefCursors ); + } + + protected void appendNameParameter( + StringBuilder buffer, + ProcedureParameterImplementor parameter, + JdbcCallParameterRegistration registration) { + buffer.append( parameter.getName() ).append( " => ?" ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java index 09cb60c37d..94ddbcd237 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PostgreSQLCallableStatementSupport.java @@ -156,6 +156,9 @@ public class PostgreSQLCallableStatementSupport extends AbstractStandardCallable ); final OutputableType type = registration.getParameterType(); final String castType; + if ( parameter.getName() != null ) { + buffer.append( parameter.getName() ).append( " => " ); + } if ( type != null && type.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType ) { // We have to cast struct type parameters so that PostgreSQL understands nulls castType = ( (AbstractPostgreSQLStructJdbcType) type.getJdbcType() ).getStructTypeName(); @@ -164,12 +167,7 @@ public class PostgreSQLCallableStatementSupport extends AbstractStandardCallable else { castType = null; } - if ( registration.getName() != null ) { - buffer.append( ':' ).append( registration.getName() ); - } - else { - buffer.append( "?" ); - } + buffer.append( "?" ); if ( castType != null ) { buffer.append( " as " ).append( castType ).append( ')' ); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java index a425a79e49..5944db288a 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java @@ -7,6 +7,7 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; +import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Locale; @@ -144,36 +145,38 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme ); final String jdbcParamName; - if ( isNamed && canDoNameParameterBinding( typeToUse, procedureCall ) ) { - jdbcParamName = this.name; - } - else { - jdbcParamName = null; - } - final JdbcParameterBinder parameterBinder; final JdbcCallRefCursorExtractorImpl refCursorExtractor; final JdbcCallParameterExtractorImpl parameterExtractor; + final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() + .getFactory() + .getJdbcServices() + .getJdbcEnvironment() + .getExtractedDatabaseMetaData(); switch ( mode ) { case REF_CURSOR: + jdbcParamName = this.name != null && databaseMetaData.supportsNamedParameters() ? this.name : null; refCursorExtractor = new JdbcCallRefCursorExtractorImpl( jdbcParamName, startIndex ); parameterBinder = null; parameterExtractor = null; break; case IN: + jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData ); validateBindableType( typeToUse, startIndex ); parameterBinder = getParameterBinder( typeToUse, jdbcParamName ); parameterExtractor = null; refCursorExtractor = null; break; case INOUT: + jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData ); validateBindableType( typeToUse, startIndex ); parameterBinder = getParameterBinder( typeToUse, jdbcParamName ); parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse ); refCursorExtractor = null; break; default: + jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData ); validateBindableType( typeToUse, startIndex ); parameterBinder = null; parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse ); @@ -184,6 +187,14 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme return new JdbcCallParameterRegistrationImpl( jdbcParamName, startIndex, mode, typeToUse, parameterBinder, parameterExtractor, refCursorExtractor ); } + private String getJdbcParamName( + ProcedureCallImplementor procedureCall, + boolean isNamed, + OutputableType typeToUse, + ExtractedDatabaseMetaData databaseMetaData) { + return isNamed && canDoNameParameterBinding( typeToUse, procedureCall, databaseMetaData ) ? this.name : null; + } + private void validateBindableType(BindableType bindableType, int startIndex) { if ( bindableType == null ) { throw new ParameterTypeException( @@ -210,7 +221,7 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme } if ( typeToUse instanceof BasicType ) { - if ( name == null ) { + if ( name == null ) { return new JdbcParameterImpl( (BasicType) typeToUse ); } else { @@ -242,12 +253,8 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme private boolean canDoNameParameterBinding( BindableType hibernateType, - ProcedureCallImplementor procedureCall) { - final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() - .getFactory() - .getJdbcServices() - .getJdbcEnvironment() - .getExtractedDatabaseMetaData(); + ProcedureCallImplementor procedureCall, + ExtractedDatabaseMetaData databaseMetaData) { return procedureCall.getFunctionReturn() == null && databaseMetaData.supportsNamedParameters() && hibernateType instanceof ProcedureParameterNamedBinder diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/SQLServerCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/SQLServerCallableStatementSupport.java new file mode 100644 index 0000000000..eb75abd3c0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/SQLServerCallableStatementSupport.java @@ -0,0 +1,24 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.procedure.internal; + +import org.hibernate.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; + +public class SQLServerCallableStatementSupport extends StandardCallableStatementSupport { + + public static final StandardCallableStatementSupport INSTANCE = new SQLServerCallableStatementSupport( ); + + + private SQLServerCallableStatementSupport() { + super( false ); + } + + protected void appendNameParameter(StringBuilder buffer, ProcedureParameterImplementor parameter, JdbcCallParameterRegistration registration) { + buffer.append( '@' ).append( parameter.getName() ).append( " = ?" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java index df17328ca1..51c250a55b 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/StandardCallableStatementSupport.java @@ -91,8 +91,8 @@ public class StandardCallableStatementSupport extends AbstractStandardCallableSt i + offset, procedureCall ); - if ( registration.getName() != null ) { - buffer.append( ':' ).append( registration.getName() ); + if ( parameter.getName() != null ) { + appendNameParameter( buffer, parameter, registration ); } else { buffer.append( "?" ); @@ -108,6 +108,13 @@ public class StandardCallableStatementSupport extends AbstractStandardCallableSt return builder.buildJdbcCall(); } + protected void appendNameParameter( + StringBuilder buffer, + ProcedureParameterImplementor parameter, + JdbcCallParameterRegistration registration) { + buffer.append( '?' ); + } + private void verifyRefCursorSupport(Dialect dialect) { if ( ! supportsRefCursors ) { throw new QueryException( "Dialect [" + dialect.getClass().getName() + "] not known to support REF_CURSOR parameters" ); diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/SybaseCallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/SybaseCallableStatementSupport.java index 1721cb3e77..dd7f2b4536 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/SybaseCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/SybaseCallableStatementSupport.java @@ -80,10 +80,12 @@ public class SybaseCallableStatementSupport extends AbstractStandardCallableStat i + offset, procedureCall ); - if ( registration.getName() != null ) { - throw new QueryException( "The JDBC driver does not support named parameters" ); + if ( parameter.getName() != null ) { + buffer.append("@").append( parameter.getName() ).append( " = ?" ); + } + else { + buffer.append( "?" ); } - buffer.append( "?" ); sep = ','; builder.addParameterRegistration( registration ); }