HHH-18280 Support named procedure parameters down to the JDBC level

This commit is contained in:
Andrea Boriero 2024-07-04 13:57:41 +02:00 committed by Steve Ebersole
parent 79480ab490
commit 8766a8e012
11 changed files with 110 additions and 38 deletions

View File

@ -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 );

View File

@ -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

View File

@ -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;
}
}

View File

@ -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( "?" );

View File

@ -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( "?" );

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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( " => ?" );
}
}

View File

@ -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( ')' );
}

View File

@ -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<T> extends AbstractQueryParameter<T> impleme
);
final String jdbcParamName;
if ( isNamed && canDoNameParameterBinding( typeToUse, procedureCall ) ) {
jdbcParamName = this.name;
}
else {
jdbcParamName = null;
}
final JdbcParameterBinder parameterBinder;
final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final JdbcCallParameterExtractorImpl<T> 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<T> extends AbstractQueryParameter<T> impleme
return new JdbcCallParameterRegistrationImpl( jdbcParamName, startIndex, mode, typeToUse, parameterBinder, parameterExtractor, refCursorExtractor );
}
private String getJdbcParamName(
ProcedureCallImplementor<?> procedureCall,
boolean isNamed,
OutputableType<T> typeToUse,
ExtractedDatabaseMetaData databaseMetaData) {
return isNamed && canDoNameParameterBinding( typeToUse, procedureCall, databaseMetaData ) ? this.name : null;
}
private void validateBindableType(BindableType<T> bindableType, int startIndex) {
if ( bindableType == null ) {
throw new ParameterTypeException(
@ -210,7 +221,7 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
}
if ( typeToUse instanceof BasicType<?> ) {
if ( name == null ) {
if ( name == null ) {
return new JdbcParameterImpl( (BasicType<T>) typeToUse );
}
else {
@ -242,12 +253,8 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> 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

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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( " = ?" );
}
}

View File

@ -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" );

View File

@ -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 );
}