From 03e48d835558a4d334f18113b5568342f0f19a76 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 31 Jul 2024 18:50:22 +0200 Subject: [PATCH] HHH-18280 add hibernate.query.pass_procedure_paramater_names setting --- .../dialect/SQLServerLegacyDialect.java | 6 - .../SessionFactoryOptionsBuilder.java | 12 ++ ...stractDelegatingSessionFactoryOptions.java | 5 + .../boot/spi/SessionFactoryOptions.java | 2 + .../java/org/hibernate/cfg/QuerySettings.java | 7 + .../org/hibernate/dialect/DB2Dialect.java | 5 - .../org/hibernate/dialect/OracleDialect.java | 6 - .../hibernate/dialect/SQLServerDialect.java | 5 - .../internal/DB2CallableStatementSupport.java | 6 +- .../internal/FunctionReturnImpl.java | 2 +- .../OracleCallableStatementSupport.java | 1 + .../internal/ProcedureParameterImpl.java | 56 ++---- .../SQLServerCallableStatementSupport.java | 1 + .../StandardCallableStatementSupport.java | 4 +- .../SybaseCallableStatementSupport.java | 6 +- .../JdbcCallParameterExtractorImpl.java | 14 +- .../JdbcCallParameterRegistrationImpl.java | 20 +-- .../JdbcCallRefCursorExtractorImpl.java | 21 +-- .../procedure/DB2StoredProcedureTest.java | 19 ++- .../procedure/OracleStoredProcedureTest.java | 5 +- .../PostgreSQLStoredProcedureTest.java | 5 +- ...verStoredProcedureForcePositionalTest.java | 159 ++++++++++++++++++ .../SQLServerStoredProcedureTest.java | 5 +- .../procedure/SybaseStoredProcedureTest.java | 5 +- 24 files changed, 254 insertions(+), 123 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureForcePositionalTest.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index 484dd4ca2f..f43a35a040 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -1136,12 +1136,6 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { } } - @Override - public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { - // Not sure if it's a JDBC driver issue, but it doesn't work - return false; - } - @Override public String generatedAs(String generatedAs) { return " as (" + generatedAs + ") persisted"; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 9ad55a7051..85008f7ccd 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -208,6 +208,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean orderInsertsEnabled; private boolean collectionsInDefaultFetchGroupEnabled = true; private boolean UnownedAssociationTransientCheck; + private boolean passProcedureParameterNames; // JPA callbacks private final boolean callbacksEnabled; @@ -627,6 +628,12 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { configurationSettings, isJpaBootstrap() ); + + this.passProcedureParameterNames = ConfigurationHelper.getBoolean( + AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, + configurationSettings, + false + ); } private boolean disallowBatchUpdates(Dialect dialect, ExtractedDatabaseMetaData meta) { @@ -1318,6 +1325,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { return xmlFormatMapper; } + @Override + public boolean isPassProcedureParameterNames() { + return passProcedureParameterNames; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index cdb440307b..39cb6e1665 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -512,4 +512,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp public FormatMapper getXmlFormatMapper() { return delegate.getXmlFormatMapper(); } + + @Override + public boolean isPassProcedureParameterNames() { + return delegate.isPassProcedureParameterNames(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 9b2b86fbb4..b9f8ed8114 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -374,4 +374,6 @@ public interface SessionFactoryOptions extends QueryEngineOptions { default JavaType getDefaultTenantIdentifierJavaType() { return ObjectJavaType.INSTANCE; } + + boolean isPassProcedureParameterNames(); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/QuerySettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/QuerySettings.java index 0590cc0295..08d9aa70d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/QuerySettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/QuerySettings.java @@ -242,4 +242,11 @@ public interface QuerySettings { */ @Deprecated(since="6.0") String QUERY_PLAN_CACHE_PARAMETER_METADATA_MAX_SIZE = "hibernate.query.plan_parameter_metadata_max_size"; + + /** + * For database supporting name parameters this setting allows to use named parameter is the procedure call. + * + * By default, this is set to false + */ + String QUERY_PASS_PROCEDURE_PARAMETER_NAMES = "hibernate.query.pass_procedure_paramater_names"; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index 19471d5610..070091a266 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -857,11 +857,6 @@ public class DB2Dialect extends Dialect { return false; } - @Override - public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) throws SQLException { - return false; - } - @Override public int registerResultSetOutParameter(CallableStatement statement, int col) throws SQLException { statement.registerOutParameter( col++, Types.REF_CURSOR ); 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 6b6ef56d63..24e33db59d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1582,12 +1582,6 @@ public class OracleDialect 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/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index c588071b4a..2a4afa1877 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -1122,11 +1122,6 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { } } - @Override - public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { - return false; - } - @Override public String generatedAs(String generatedAs) { return " as (" + generatedAs + ") persisted"; 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 80e872acbd..7aed3903a1 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 @@ -8,6 +8,7 @@ package org.hibernate.procedure.internal; import java.util.List; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.spi.FunctionReturnImplementor; import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ProcedureCallImplementor; @@ -84,7 +85,10 @@ public class DB2CallableStatementSupport extends AbstractStandardCallableStateme i + offset, procedureCall ); - if ( parameter.getName() != null ) { + final SharedSessionContractImplementor session = procedureCall.getSession(); + if ( parameter.getName() != null + && session.getJdbcServices().getExtractedMetaDataSupport().supportsNamedParameters() + && session.getFactory().getSessionFactoryOptions().isPassProcedureParameterNames() ) { buffer.append( parameter.getName() ).append( " => ?" ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java index 4c5481001c..7b1fa98b8f 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java @@ -52,7 +52,7 @@ public class FunctionReturnImpl implements FunctionReturnImplementor { final JdbcCallParameterExtractorImpl parameterExtractor; if ( getJdbcTypeCode() == Types.REF_CURSOR ) { - refCursorExtractor = new JdbcCallRefCursorExtractorImpl( null, 1 ); + refCursorExtractor = new JdbcCallRefCursorExtractorImpl( 1 ); ormType = null; parameterExtractor = null; } 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 index 4f7db51b1a..c03a3aab65 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/OracleCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/OracleCallableStatementSupport.java @@ -23,6 +23,7 @@ public class OracleCallableStatementSupport extends StandardCallableStatementSup super( supportsRefCursors ); } + @Override protected void appendNameParameter( StringBuilder buffer, ProcedureParameterImplementor parameter, 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 5944db288a..9c4f0c3de0 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 @@ -6,15 +6,11 @@ */ 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; import java.util.Objects; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.ParameterTypeException; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; @@ -29,7 +25,6 @@ import org.hibernate.sql.exec.internal.JdbcCallParameterExtractorImpl; import org.hibernate.sql.exec.internal.JdbcCallParameterRegistrationImpl; import org.hibernate.sql.exec.internal.JdbcCallRefCursorExtractorImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcCallParameterRegistration; import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.type.BasicType; @@ -138,45 +133,49 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme bindableType = null; } + final SharedSessionContractImplementor session = procedureCall.getSession(); + final OutputableType typeToUse = (OutputableType) BindingTypeHelper.INSTANCE.resolveTemporalPrecision( binding == null ? null : binding.getExplicitTemporalPrecision(), bindableType, - procedureCall.getSession().getFactory() + session.getFactory() ); final String jdbcParamName; final JdbcParameterBinder parameterBinder; final JdbcCallRefCursorExtractorImpl refCursorExtractor; final JdbcCallParameterExtractorImpl parameterExtractor; - final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() + final ExtractedDatabaseMetaData databaseMetaData = session .getFactory() .getJdbcServices() .getJdbcEnvironment() .getExtractedDatabaseMetaData(); - + final boolean passProcedureParameterNames = session.getFactory() + .getSessionFactoryOptions() + .isPassProcedureParameterNames(); switch ( mode ) { case REF_CURSOR: - jdbcParamName = this.name != null && databaseMetaData.supportsNamedParameters() ? this.name : null; - refCursorExtractor = new JdbcCallRefCursorExtractorImpl( jdbcParamName, startIndex ); + jdbcParamName = this.name != null && databaseMetaData.supportsNamedParameters() && passProcedureParameterNames ? this.name : null; + refCursorExtractor = new JdbcCallRefCursorExtractorImpl( startIndex ); parameterBinder = null; parameterExtractor = null; break; case IN: - jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData ); + jdbcParamName = getJdbcParamName( procedureCall, isNamed, passProcedureParameterNames, typeToUse, databaseMetaData ); validateBindableType( typeToUse, startIndex ); parameterBinder = getParameterBinder( typeToUse, jdbcParamName ); parameterExtractor = null; refCursorExtractor = null; break; case INOUT: - jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData ); + jdbcParamName = getJdbcParamName( procedureCall, isNamed, passProcedureParameterNames, 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 ); + jdbcParamName = getJdbcParamName( procedureCall, isNamed, passProcedureParameterNames, typeToUse, databaseMetaData ); validateBindableType( typeToUse, startIndex ); parameterBinder = null; parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse ); @@ -190,9 +189,10 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme private String getJdbcParamName( ProcedureCallImplementor procedureCall, boolean isNamed, + boolean passProcedureParameterNames, OutputableType typeToUse, ExtractedDatabaseMetaData databaseMetaData) { - return isNamed && canDoNameParameterBinding( typeToUse, procedureCall, databaseMetaData ) ? this.name : null; + return isNamed && passProcedureParameterNames && canDoNameParameterBinding( typeToUse, procedureCall, databaseMetaData ) ? this.name : null; } private void validateBindableType(BindableType bindableType, int startIndex) { @@ -221,31 +221,7 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme } if ( typeToUse instanceof BasicType ) { - if ( name == null ) { - return new JdbcParameterImpl( (BasicType) typeToUse ); - } - else { - return new JdbcParameterImpl( (BasicType) typeToUse ) { - @Override - protected void bindParameterValue( - JdbcMapping jdbcMapping, - PreparedStatement statement, - Object bindValue, - int startPosition, - ExecutionContext executionContext) throws SQLException { - jdbcMapping.getJdbcValueBinder().bind( - (CallableStatement) statement, - bindValue, - name, - executionContext.getSession() - ); - } - @Override - public String toString() { - return "JdbcParameter(" + name + ")"; - } - }; - } + return new JdbcParameterImpl( (BasicType) typeToUse ); } throw new UnsupportedOperationException(); 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 index eb75abd3c0..d216373e16 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/SQLServerCallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/SQLServerCallableStatementSupport.java @@ -18,6 +18,7 @@ public class SQLServerCallableStatementSupport extends StandardCallableStatement super( false ); } + @Override 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 51c250a55b..a23d2e9b10 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,7 +91,9 @@ public class StandardCallableStatementSupport extends AbstractStandardCallableSt i + offset, procedureCall ); - if ( parameter.getName() != null ) { + if ( parameter.getName() != null + && session.getJdbcServices().getExtractedMetaDataSupport().supportsNamedParameters() + && session.getFactory().getSessionFactoryOptions().isPassProcedureParameterNames() ) { appendNameParameter( buffer, parameter, registration ); } else { 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 dd7f2b4536..949c8fffcb 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 @@ -9,6 +9,7 @@ package org.hibernate.procedure.internal; import java.util.List; import org.hibernate.QueryException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.spi.FunctionReturnImplementor; import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.procedure.spi.ProcedureParameterImplementor; @@ -80,7 +81,10 @@ public class SybaseCallableStatementSupport extends AbstractStandardCallableStat i + offset, procedureCall ); - if ( parameter.getName() != null ) { + final SharedSessionContractImplementor session = procedureCall.getSession(); + if ( parameter.getName() != null + && session.getJdbcServices().getExtractedMetaDataSupport().supportsNamedParameters() + && session.getFactory().getSessionFactoryOptions().isPassProcedureParameterNames() ) { buffer.append("@").append( parameter.getName() ).append( " = ?" ); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java index 6a6b0d55d2..41742f1c00 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterExtractorImpl.java @@ -50,20 +50,8 @@ public class JdbcCallParameterExtractorImpl implements JdbcCallParameterExtra CallableStatement callableStatement, boolean shouldUseJdbcNamedParameters, SharedSessionContractImplementor session) { - - final boolean useNamed = shouldUseJdbcNamedParameters - && parameterName != null; - - // todo (6.0) : we should just ask BasicValuedExpressibleType for the JdbcValueExtractor... - - try { - if ( useNamed ) { - return ormType.extract( callableStatement, parameterName, session ); - } - else { - return ormType.extract( callableStatement, parameterPosition, session ); - } + return ormType.extract( callableStatement, parameterPosition, session ); } catch (SQLException e) { throw session.getJdbcServices().getSqlExceptionHelper().convert( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java index 1cce03cfad..0a29deeed7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallParameterRegistrationImpl.java @@ -101,16 +101,9 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis private void registerRefCursorParameter( CallableStatement callableStatement, SharedSessionContractImplementor session) { - if ( name != null ) { - session.getFactory().getServiceRegistry() - .requireService( RefCursorSupport.class ) - .registerRefCursorParameter( callableStatement, name ); - } - else { - session.getFactory().getServiceRegistry() - .requireService( RefCursorSupport.class ) - .registerRefCursorParameter( callableStatement, jdbcParameterPositionStart ); - } + session.getFactory().getServiceRegistry() + .requireService( RefCursorSupport.class ) + .registerRefCursorParameter( callableStatement, jdbcParameterPositionStart ); } @@ -119,12 +112,7 @@ public class JdbcCallParameterRegistrationImpl implements JdbcCallParameterRegis SharedSessionContractImplementor session) { final JdbcType sqlTypeDescriptor = ormType.getJdbcType(); try { - if ( name != null ) { - sqlTypeDescriptor.registerOutParameter( callableStatement, name ); - } - else { - sqlTypeDescriptor.registerOutParameter( callableStatement, jdbcParameterPositionStart ); - } + sqlTypeDescriptor.registerOutParameter( callableStatement, jdbcParameterPositionStart ); } catch (SQLException e) { throw session.getJdbcServices().getSqlExceptionHelper().convert( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java index cbeee4e773..f9099325b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcCallRefCursorExtractorImpl.java @@ -21,13 +21,10 @@ import org.hibernate.sql.exec.spi.JdbcCallRefCursorExtractor; * @author Steve Ebersole */ public class JdbcCallRefCursorExtractorImpl implements JdbcCallRefCursorExtractor { - private final String jdbcParameterName; private final int jdbcParameterPosition; public JdbcCallRefCursorExtractorImpl( - String jdbcParameterName, int jdbcParameterPosition) { - this.jdbcParameterName = jdbcParameterName; this.jdbcParameterPosition = jdbcParameterPosition; } @@ -39,19 +36,9 @@ public class JdbcCallRefCursorExtractorImpl implements JdbcCallRefCursorExtracto .getJdbcEnvironment() .getExtractedDatabaseMetaData() .supportsNamedParameters(); - final boolean useNamed = supportsNamedParameters && jdbcParameterName != null; - - if ( useNamed ) { - return session.getFactory() - .getServiceRegistry() - .requireService( RefCursorSupport.class ) - .getResultSet( callableStatement, jdbcParameterName ); - } - else { - return session.getFactory() - .getServiceRegistry() - .requireService( RefCursorSupport.class ) - .getResultSet( callableStatement, jdbcParameterPosition ); - } + return session.getFactory() + .getServiceRegistry() + .requireService( RefCursorSupport.class ) + .getResultSet( callableStatement, jdbcParameterPosition ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/DB2StoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/DB2StoredProcedureTest.java index c7b8a48a06..e87edb8a47 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/DB2StoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/DB2StoredProcedureTest.java @@ -16,6 +16,7 @@ import java.time.ZoneOffset; import java.util.List; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.DB2Dialect; import org.hibernate.procedure.ProcedureCall; import org.hibernate.query.procedure.ProcedureParameter; @@ -29,6 +30,7 @@ import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -58,13 +60,16 @@ import static org.junit.jupiter.api.Assertions.fail; /** * @author Marco Belladelli */ -@Jpa( annotatedClasses = { - Person.class, - Phone.class, - Vote.class, - DB2StoredProcedureTest.IdHolder.class, - DB2StoredProcedureTest.Address.class, -} ) +@Jpa( + annotatedClasses = { + Person.class, + Phone.class, + Vote.class, + DB2StoredProcedureTest.IdHolder.class, + DB2StoredProcedureTest.Address.class, + }, + properties = @Setting(name = AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, value = "true") +) @RequiresDialect( value = DB2Dialect.class ) @Jira( "https://hibernate.atlassian.net/browse/HHH-18332" ) public class DB2StoredProcedureTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java index 8e0fc467ad..378c3d89ab 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/OracleStoredProcedureTest.java @@ -19,6 +19,7 @@ import java.util.List; import org.hibernate.Session; import org.hibernate.annotations.QueryHints; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.OracleDialect; import org.hibernate.procedure.ProcedureCall; import org.hibernate.query.procedure.ProcedureParameter; @@ -31,6 +32,7 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.Setting; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -65,7 +67,8 @@ import static org.junit.jupiter.api.Assertions.fail; OracleStoredProcedureTest.IdHolder.class, Vote.class, OracleStoredProcedureTest.Address.class - } + }, + properties = @Setting( name = AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, value = "true") ) @RequiresDialect(value = OracleDialect.class) public class OracleStoredProcedureTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java index b64864583e..fc0f79bd93 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/PostgreSQLStoredProcedureTest.java @@ -18,6 +18,7 @@ import java.time.ZoneOffset; import java.util.List; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.procedure.ProcedureCall; import org.hibernate.type.StandardBasicTypes; @@ -26,6 +27,7 @@ import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.Setting; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -53,7 +55,8 @@ import static org.junit.Assert.fail; Person.class, Phone.class, PostgreSQLStoredProcedureTest.Address.class - } + }, + properties = @Setting( name = AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, value = "true") ) @RequiresDialect(value = PostgreSQLDialect.class) public class PostgreSQLStoredProcedureTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureForcePositionalTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureForcePositionalTest.java new file mode 100644 index 0000000000..c168dafc2c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureForcePositionalTest.java @@ -0,0 +1,159 @@ +/* + * 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.orm.test.procedure; + +import java.sql.CallableStatement; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.regex.Pattern; + +import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.SQLServerDialect; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ParameterMode; +import jakarta.persistence.StoredProcedureQuery; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@RequiresDialect(value = SQLServerDialect.class, majorVersion = 11) +@Jpa( + annotatedClasses = { + Person.class, + Phone.class, + SQLServerStoredProcedureForcePositionalTest.Address.class + }, + properties = @Setting( name = AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, value = "false") +) +public class SQLServerStoredProcedureForcePositionalTest { + + private static final String CITY = "London"; + private static final String STREET = "Lollard Street"; + private static final String ZIP = "SE116UG"; + + @BeforeEach + public void init(EntityManagerFactoryScope scope) { + doInAutoCommit( + "DROP PROCEDURE sp_count_phones", + "CREATE PROCEDURE sp_count_phones " + + " @personId INT, " + + " @phoneCount INT OUTPUT " + + "AS " + + "BEGIN " + + " SELECT @phoneCount = COUNT(*) " + + " FROM Phone " + + " WHERE person_id = @personId " + + "END" + ); + + scope.inTransaction( entityManager -> { + Person person1 = new Person( 1L, "John Doe" ); + person1.setNickName( "JD" ); + person1.setAddress( "Earth" ); + person1.setCreatedOn( Timestamp.from( LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ) + .toInstant( ZoneOffset.UTC ) ) ); + + entityManager.persist( person1 ); + + Phone phone1 = new Phone( "123-456-7890" ); + phone1.setId( 1L ); + + person1.addPhone( phone1 ); + + Phone phone2 = new Phone( "098_765-4321" ); + phone2.setId( 2L ); + + person1.addPhone( phone2 ); + + Address address = new Address( 1l, STREET, CITY, ZIP ); + entityManager.persist( address ); + } ); + } + + @AfterEach + public void tearDown(EntityManagerFactoryScope scope) { + scope.releaseEntityManagerFactory(); + } + + @Test + public void testStoredProcedure(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones" ); + query.registerStoredProcedureParameter( "personId2", Long.class, ParameterMode.IN ); + query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT ); + + query.setParameter( "personId2", 1L ); + + query.execute(); + Long phoneCount = (Long) query.getOutputParameterValue( "phoneCount" ); + assertEquals( Long.valueOf( 2 ), phoneCount ); + } ); + } + + @Entity(name = "Address") + @Table(name = "ADDRESS_TABLE") + public static class Address { + @Id + @Column(name = "ID") + private long id; + @Column(name = "STREET") + private String street; + @Column(name = "CITY") + private String city; + @Column(name = "ZIP") + private String zip; + + public Address() { + } + + public Address(long id, String street, String city, String zip) { + this.id = id; + this.street = street; + this.city = city; + this.zip = zip; + } + + public long getId() { + return id; + } + + public String getStreet() { + return street; + } + + public String getCity() { + return city; + } + + public String getZip() { + return zip; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureTest.java index 7fca4ed968..32071447f5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SQLServerStoredProcedureTest.java @@ -15,12 +15,14 @@ import java.util.List; import java.util.regex.Pattern; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.Setting; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,7 +50,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; Person.class, Phone.class, SQLServerStoredProcedureTest.Address.class - } + }, + properties = @Setting( name = AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, value = "true") ) public class SQLServerStoredProcedureTest { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SybaseStoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SybaseStoredProcedureTest.java index cce2897ce8..9a176fd957 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SybaseStoredProcedureTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/SybaseStoredProcedureTest.java @@ -10,6 +10,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneOffset; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.jpa.HibernateHints; @@ -18,6 +19,7 @@ import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.Setting; import org.junit.Assert; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -39,7 +41,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; annotatedClasses = { Person.class, Phone.class, - } + }, + properties = @Setting( name = AvailableSettings.QUERY_PASS_PROCEDURE_PARAMETER_NAMES, value = "true") ) public class SybaseStoredProcedureTest {