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; 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 @Override
public ResultSet getResultSet(CallableStatement statement, String name) throws SQLException { public ResultSet getResultSet(CallableStatement statement, String name) throws SQLException {
return (ResultSet) statement.getObject( name ); 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.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.mutation.EntityMutationTarget; 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.procedure.spi.CallableStatementSupport;
import org.hibernate.query.SemanticException; import org.hibernate.query.SemanticException;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
@ -1314,7 +1314,7 @@ public class OracleDialect extends Dialect {
@Override @Override
public CallableStatementSupport getCallableStatementSupport() { public CallableStatementSupport getCallableStatementSupport() {
// Oracle supports returning cursors // Oracle supports returning cursors
return StandardCallableStatementSupport.REF_CURSOR_INSTANCE; return OracleCallableStatementSupport.REF_CURSOR_INSTANCE;
} }
@Override @Override

View File

@ -54,6 +54,8 @@ import org.hibernate.exception.spi.ViolatedConstraintNameExtractor;
import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.persister.entity.mutation.EntityMutationTarget; 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.CastType;
import org.hibernate.query.sqm.FetchClauseType; import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.IntervalType; import org.hibernate.query.sqm.IntervalType;
@ -1122,7 +1124,6 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
@Override @Override
public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) {
// Not sure if it's a JDBC driver issue, but it doesn't work
return false; return false;
} }
@ -1168,4 +1169,10 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
public boolean supportsFromClauseInUpdate() { public boolean supportsFromClauseInUpdate() {
return true; return true;
} }
@Override
public CallableStatementSupport getCallableStatementSupport() {
return SQLServerCallableStatementSupport.INSTANCE;
}
} }

View File

@ -84,8 +84,8 @@ public class DB2CallableStatementSupport extends AbstractStandardCallableStateme
i + offset, i + offset,
procedureCall procedureCall
); );
if ( registration.getName() != null ) { if ( parameter.getName() != null ) {
buffer.append( ':' ).append( registration.getName() ); buffer.append( ':' ).append( parameter.getName() );
} }
else { else {
buffer.append( "?" ); buffer.append( "?" );

View File

@ -69,8 +69,8 @@ public class JTDSCallableStatementSupport extends AbstractStandardCallableStatem
i + offset, i + offset,
procedureCall procedureCall
); );
if ( registration.getName() != null ) { if ( parameter.getName() != null ) {
buffer.append( '@' ).append( registration.getName() ).append( "=?" ); buffer.append( '@' ).append( parameter.getName() ).append( "=?" );
} }
else { else {
buffer.append( "?" ); 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 OutputableType<?> type = registration.getParameterType();
final String castType; final String castType;
if ( parameter.getName() != null ) {
buffer.append( parameter.getName() ).append( " => " );
}
if ( type != null && type.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType ) { if ( type != null && type.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType ) {
// We have to cast struct type parameters so that PostgreSQL understands nulls // We have to cast struct type parameters so that PostgreSQL understands nulls
castType = ( (AbstractPostgreSQLStructJdbcType) type.getJdbcType() ).getStructTypeName(); castType = ( (AbstractPostgreSQLStructJdbcType) type.getJdbcType() ).getStructTypeName();
@ -164,12 +167,7 @@ public class PostgreSQLCallableStatementSupport extends AbstractStandardCallable
else { else {
castType = null; castType = null;
} }
if ( registration.getName() != null ) { buffer.append( "?" );
buffer.append( ':' ).append( registration.getName() );
}
else {
buffer.append( "?" );
}
if ( castType != null ) { if ( castType != null ) {
buffer.append( " as " ).append( castType ).append( ')' ); buffer.append( " as " ).append( castType ).append( ')' );
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.procedure.internal; package org.hibernate.procedure.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Locale; import java.util.Locale;
@ -144,36 +145,38 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
); );
final String jdbcParamName; final String jdbcParamName;
if ( isNamed && canDoNameParameterBinding( typeToUse, procedureCall ) ) {
jdbcParamName = this.name;
}
else {
jdbcParamName = null;
}
final JdbcParameterBinder parameterBinder; final JdbcParameterBinder parameterBinder;
final JdbcCallRefCursorExtractorImpl refCursorExtractor; final JdbcCallRefCursorExtractorImpl refCursorExtractor;
final JdbcCallParameterExtractorImpl<T> parameterExtractor; final JdbcCallParameterExtractorImpl<T> parameterExtractor;
final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession()
.getFactory()
.getJdbcServices()
.getJdbcEnvironment()
.getExtractedDatabaseMetaData();
switch ( mode ) { switch ( mode ) {
case REF_CURSOR: case REF_CURSOR:
jdbcParamName = this.name != null && databaseMetaData.supportsNamedParameters() ? this.name : null;
refCursorExtractor = new JdbcCallRefCursorExtractorImpl( jdbcParamName, startIndex ); refCursorExtractor = new JdbcCallRefCursorExtractorImpl( jdbcParamName, startIndex );
parameterBinder = null; parameterBinder = null;
parameterExtractor = null; parameterExtractor = null;
break; break;
case IN: case IN:
jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData );
validateBindableType( typeToUse, startIndex ); validateBindableType( typeToUse, startIndex );
parameterBinder = getParameterBinder( typeToUse, jdbcParamName ); parameterBinder = getParameterBinder( typeToUse, jdbcParamName );
parameterExtractor = null; parameterExtractor = null;
refCursorExtractor = null; refCursorExtractor = null;
break; break;
case INOUT: case INOUT:
jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData );
validateBindableType( typeToUse, startIndex ); validateBindableType( typeToUse, startIndex );
parameterBinder = getParameterBinder( typeToUse, jdbcParamName ); parameterBinder = getParameterBinder( typeToUse, jdbcParamName );
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse ); parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse );
refCursorExtractor = null; refCursorExtractor = null;
break; break;
default: default:
jdbcParamName = getJdbcParamName( procedureCall, isNamed, typeToUse, databaseMetaData );
validateBindableType( typeToUse, startIndex ); validateBindableType( typeToUse, startIndex );
parameterBinder = null; parameterBinder = null;
parameterExtractor = new JdbcCallParameterExtractorImpl<>( procedureCall.getProcedureName(), jdbcParamName, startIndex, typeToUse ); 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 ); 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) { private void validateBindableType(BindableType<T> bindableType, int startIndex) {
if ( bindableType == null ) { if ( bindableType == null ) {
throw new ParameterTypeException( throw new ParameterTypeException(
@ -210,7 +221,7 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
} }
if ( typeToUse instanceof BasicType<?> ) { if ( typeToUse instanceof BasicType<?> ) {
if ( name == null ) { if ( name == null ) {
return new JdbcParameterImpl( (BasicType<T>) typeToUse ); return new JdbcParameterImpl( (BasicType<T>) typeToUse );
} }
else { else {
@ -242,12 +253,8 @@ public class ProcedureParameterImpl<T> extends AbstractQueryParameter<T> impleme
private boolean canDoNameParameterBinding( private boolean canDoNameParameterBinding(
BindableType<?> hibernateType, BindableType<?> hibernateType,
ProcedureCallImplementor<?> procedureCall) { ProcedureCallImplementor<?> procedureCall,
final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() ExtractedDatabaseMetaData databaseMetaData) {
.getFactory()
.getJdbcServices()
.getJdbcEnvironment()
.getExtractedDatabaseMetaData();
return procedureCall.getFunctionReturn() == null return procedureCall.getFunctionReturn() == null
&& databaseMetaData.supportsNamedParameters() && databaseMetaData.supportsNamedParameters()
&& hibernateType instanceof ProcedureParameterNamedBinder && 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, i + offset,
procedureCall procedureCall
); );
if ( registration.getName() != null ) { if ( parameter.getName() != null ) {
buffer.append( ':' ).append( registration.getName() ); appendNameParameter( buffer, parameter, registration );
} }
else { else {
buffer.append( "?" ); buffer.append( "?" );
@ -108,6 +108,13 @@ public class StandardCallableStatementSupport extends AbstractStandardCallableSt
return builder.buildJdbcCall(); return builder.buildJdbcCall();
} }
protected void appendNameParameter(
StringBuilder buffer,
ProcedureParameterImplementor parameter,
JdbcCallParameterRegistration registration) {
buffer.append( '?' );
}
private void verifyRefCursorSupport(Dialect dialect) { private void verifyRefCursorSupport(Dialect dialect) {
if ( ! supportsRefCursors ) { if ( ! supportsRefCursors ) {
throw new QueryException( "Dialect [" + dialect.getClass().getName() + "] not known to support REF_CURSOR parameters" ); 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, i + offset,
procedureCall procedureCall
); );
if ( registration.getName() != null ) { if ( parameter.getName() != null ) {
throw new QueryException( "The JDBC driver does not support named parameters" ); buffer.append("@").append( parameter.getName() ).append( " = ?" );
}
else {
buffer.append( "?" );
} }
buffer.append( "?" );
sep = ','; sep = ',';
builder.addParameterRegistration( registration ); builder.addParameterRegistration( registration );
} }