HHH-17314 - Add a configuration option to ignore jdbc parameters in native queries

Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
Jan Schatteman 2023-10-18 21:06:54 +02:00 committed by Jan Schatteman
parent ffee08d853
commit 763a70f633
16 changed files with 220 additions and 72 deletions

View File

@ -248,6 +248,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
private TimeZone jdbcTimeZone;
private final ValueHandlingMode criteriaValueHandlingMode;
private final boolean criteriaCopyTreeEnabled;
private final boolean nativeJdbcParametersIgnored;
private final ImmutableEntityUpdateQueryHandlingMode immutableEntityUpdateQueryHandlingMode;
// These two settings cannot be modified from the builder,
// in order to maintain consistency.
@ -551,6 +552,12 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
jpaBootstrap
);
this.nativeJdbcParametersIgnored = getBoolean(
AvailableSettings.NATIVE_IGNORE_JDBC_PARAMETERS,
configurationSettings,
false
);
// added the boolean parameter in case we want to define some form of "all" as discussed
this.jpaCompliance = context.getJpaCompliance();
@ -1145,6 +1152,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
return criteriaCopyTreeEnabled;
}
@Override
public boolean getNativeJdbcParametersIgnored() {
return nativeJdbcParametersIgnored;
}
@Override
public ImmutableEntityUpdateQueryHandlingMode getImmutableEntityUpdateQueryHandlingMode() {
return immutableEntityUpdateQueryHandlingMode;

View File

@ -382,6 +382,10 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
return delegate.isCriteriaCopyTreeEnabled();
}
public boolean getNativeJdbcParametersIgnored() {
return delegate.getNativeJdbcParametersIgnored();
}
@Override
public JpaCompliance getJpaCompliance() {
return delegate.getJpaCompliance();

View File

@ -231,6 +231,13 @@ public interface SessionFactoryOptions extends QueryEngineOptions {
return false;
}
/**
* @see org.hibernate.cfg.AvailableSettings#NATIVE_IGNORE_JDBC_PARAMETERS
*/
default boolean getNativeJdbcParametersIgnored() {
return false;
}
JpaCompliance getJpaCompliance();
boolean isFailOnPaginationOverCollectionFetchEnabled();

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.cfg;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.query.NullPrecedence;
import org.hibernate.query.spi.QueryPlan;
@ -124,6 +125,16 @@ public interface QuerySettings {
*/
String CRITERIA_COPY_TREE = "hibernate.criteria.copy_tree";
/**
* When set to true, indicates that ordinal parameters (represented by the '?' placeholder) in native queries will be ignored.
* <p>
* By default, this is set to false, i.e. native queries will be checked for ordinal placeholders.
* <p>
*
* @see SessionFactoryOptions#getIgnoreNativeJdbcParameters()
*/
String NATIVE_IGNORE_JDBC_PARAMETERS = "hibernate.query.native.ignore_jdbc_parameters";
/**
* When {@linkplain org.hibernate.query.Query#setMaxResults(int) pagination} is used
* in combination with a {@code fetch join} applied to a collection or many-valued

View File

@ -21,11 +21,17 @@ public class NativeQueryInterpreterStandardImpl implements NativeQueryInterprete
/**
* Singleton access
*/
public static final NativeQueryInterpreterStandardImpl NATIVE_QUERY_INTERPRETER = new NativeQueryInterpreterStandardImpl();
public static final NativeQueryInterpreterStandardImpl NATIVE_QUERY_INTERPRETER = new NativeQueryInterpreterStandardImpl( false );
private boolean nativeJdbcParametersIgnored;
public NativeQueryInterpreterStandardImpl(boolean nativeJdbcParametersIgnored) {
this.nativeJdbcParametersIgnored = nativeJdbcParametersIgnored;
}
@Override
public void recognizeParameters(String nativeQuery, ParameterRecognizer recognizer) {
ParameterParser.parse( nativeQuery, recognizer );
ParameterParser.parse( nativeQuery, recognizer, nativeJdbcParametersIgnored );
}
@Override

View File

@ -21,7 +21,7 @@ public class NativeQueryInterpreterInitiator implements SessionFactoryServiceIni
@Override
public NativeQueryInterpreter initiateService(SessionFactoryServiceInitiatorContext context) {
return new NativeQueryInterpreterStandardImpl();
return new NativeQueryInterpreterStandardImpl( context.getSessionFactoryOptions().getNativeJdbcParametersIgnored() );
}
@Override

View File

@ -240,6 +240,16 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
return delegate.isCriteriaCopyTreeEnabled();
}
@Override
public boolean getNativeJdbcParametersIgnored() {
return delegate.getNativeJdbcParametersIgnored();
}
@Override
public void setNativeJdbcParametersIgnored(boolean nativeJdbcParametersIgnored) {
delegate.setNativeJdbcParametersIgnored( nativeJdbcParametersIgnored );
}
@Override
public boolean isOpen() {
return delegate.isOpen();

View File

@ -381,6 +381,10 @@ public interface SharedSessionContractImplementor
boolean isCriteriaCopyTreeEnabled();
boolean getNativeJdbcParametersIgnored();
void setNativeJdbcParametersIgnored(boolean nativeJdbcParametersIgnored);
/**
* Get the current {@link FlushModeType} for this session.
* <p>

View File

@ -474,6 +474,16 @@ public class SharedSessionDelegatorBaseImpl implements SharedSessionContractImpl
return delegate.isCriteriaCopyTreeEnabled();
}
@Override
public boolean getNativeJdbcParametersIgnored() {
return delegate.getNativeJdbcParametersIgnored();
}
@Override
public void setNativeJdbcParametersIgnored(boolean nativeJdbcParametersIgnored) {
delegate.setNativeJdbcParametersIgnored( nativeJdbcParametersIgnored );
}
@Override
public FlushModeType getFlushMode() {
return delegate.getFlushMode();

View File

@ -162,6 +162,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
private boolean criteriaCopyTreeEnabled;
private boolean nativeJdbcParametersIgnored;
protected boolean closed;
protected boolean waitingForAutoClose;
@ -183,6 +185,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
sessionEventsManager = createSessionEventsManager(options);
entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor );
setCriteriaCopyTreeEnabled( factory.getSessionFactoryOptions().isCriteriaCopyTreeEnabled() );
setNativeJdbcParametersIgnored( factory.getSessionFactoryOptions().getNativeJdbcParametersIgnored() );
final StatementInspector statementInspector = interpret( options.getStatementInspector() );
@ -701,6 +704,16 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
return criteriaCopyTreeEnabled;
}
@Override
public boolean getNativeJdbcParametersIgnored() {
return nativeJdbcParametersIgnored;
}
@Override
public void setNativeJdbcParametersIgnored(boolean nativeJdbcParametersIgnored) {
this.nativeJdbcParametersIgnored = nativeJdbcParametersIgnored;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// dynamic HQL handling

View File

@ -173,55 +173,6 @@ public class NativeQueryImpl<R>
);
}
@FunctionalInterface
private interface ResultSetMappingHandler {
boolean resolveResultSetMapping(
ResultSetMapping resultSetMapping,
Consumer<String> querySpaceConsumer,
ResultSetMappingResolutionContext context);
}
private static ResultSetMapping buildResultSetMapping(
String registeredName,
boolean isDynamic,
SharedSessionContractImplementor session) {
return ResultSetMapping.resolveResultSetMapping( registeredName, isDynamic, session.getFactory() );
}
public NativeQueryImpl(
NamedNativeQueryMemento memento,
Supplier<ResultSetMapping> resultSetMappingCreator,
ResultSetMappingHandler resultSetMappingHandler,
SharedSessionContractImplementor session) {
super( session );
this.originalSqlString = memento.getOriginalSqlString();
final ParameterInterpretation parameterInterpretation = resolveParameterInterpretation(
originalSqlString,
session
);
this.sqlString = parameterInterpretation.getAdjustedSqlString();
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
this.querySpaces = new HashSet<>();
this.resultSetMapping = resultSetMappingCreator.get();
//noinspection UnnecessaryLocalVariable
final boolean appliedAnyResults = resultSetMappingHandler.resolveResultSetMapping(
resultSetMapping,
querySpaces::add,
this
);
this.resultMappingSuppliedToCtor = appliedAnyResults;
applyOptions( memento );
}
/**
* Constructs a NativeQueryImpl given a sql query defined in the mappings.
*/
@ -306,6 +257,40 @@ public class NativeQueryImpl<R>
}
public NativeQueryImpl(
NamedNativeQueryMemento memento,
Supplier<ResultSetMapping> resultSetMappingCreator,
ResultSetMappingHandler resultSetMappingHandler,
SharedSessionContractImplementor session) {
super( session );
this.originalSqlString = memento.getOriginalSqlString();
final ParameterInterpretation parameterInterpretation = resolveParameterInterpretation(
originalSqlString,
session
);
this.sqlString = parameterInterpretation.getAdjustedSqlString();
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
this.querySpaces = new HashSet<>();
this.resultSetMapping = resultSetMappingCreator.get();
//noinspection UnnecessaryLocalVariable
final boolean appliedAnyResults = resultSetMappingHandler.resolveResultSetMapping(
resultSetMapping,
querySpaces::add,
this
);
this.resultMappingSuppliedToCtor = appliedAnyResults;
applyOptions( memento );
}
public NativeQueryImpl(
String sqlString,
NamedResultSetMappingMemento resultSetMappingMemento,
@ -331,6 +316,37 @@ public class NativeQueryImpl<R>
this.resultMappingSuppliedToCtor = true;
}
public NativeQueryImpl(String sqlString, SharedSessionContractImplementor session) {
super( session );
this.querySpaces = new HashSet<>();
final ParameterInterpretation parameterInterpretation = resolveParameterInterpretation( sqlString, session );
this.originalSqlString = sqlString;
this.sqlString = parameterInterpretation.getAdjustedSqlString();
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( sqlString, true, session.getFactory() );
this.resultMappingSuppliedToCtor = false;
}
@FunctionalInterface
private interface ResultSetMappingHandler {
boolean resolveResultSetMapping(
ResultSetMapping resultSetMapping,
Consumer<String> querySpaceConsumer,
ResultSetMappingResolutionContext context);
}
private static ResultSetMapping buildResultSetMapping(
String registeredName,
boolean isDynamic,
SharedSessionContractImplementor session) {
return ResultSetMapping.resolveResultSetMapping( registeredName, isDynamic, session.getFactory() );
}
public List<ParameterOccurrence> getParameterOccurrences() {
return parameterOccurrences;
}
@ -374,22 +390,6 @@ public class NativeQueryImpl<R>
// todo (6.0) : query returns
}
public NativeQueryImpl(String sqlString, SharedSessionContractImplementor session) {
super( session );
this.querySpaces = new HashSet<>();
final ParameterInterpretation parameterInterpretation = resolveParameterInterpretation( sqlString, session );
this.originalSqlString = sqlString;
this.sqlString = parameterInterpretation.getAdjustedSqlString();
this.parameterMetadata = parameterInterpretation.toParameterMetadata( session );
this.parameterOccurrences = parameterInterpretation.getOrderedParameterOccurrences();
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( sqlString, true, session.getFactory() );
this.resultMappingSuppliedToCtor = false;
}
private IllegalArgumentException buildIncompatibleException(Class<?> resultClass, Class<?> actualResultClass) {
final String resultClassName = resultClass.getName();
final String actualResultClassName = actualResultClass.getName();

View File

@ -47,9 +47,10 @@ public class ParameterParser {
*
* @param sqlString The string to be parsed/tokenized.
* @param recognizer The thing which handles recognition events.
* @param nativeJdbcParametersIgnored Whether to ignore ordinal parameters in native queries or not.
* @throws QueryException Indicates unexpected parameter conditions.
*/
public static void parse(String sqlString, ParameterRecognizer recognizer) throws QueryException {
public static void parse(String sqlString, ParameterRecognizer recognizer, boolean nativeJdbcParametersIgnored) throws QueryException {
checkIsNotAFunctionCall( sqlString );
final int stringLength = sqlString.length();
@ -163,7 +164,9 @@ public class ParameterParser {
}
}
else {
recognizer.ordinalParameter( indx );
if ( !nativeJdbcParametersIgnored ) {
recognizer.ordinalParameter( indx );
}
}
}
else {
@ -175,6 +178,10 @@ public class ParameterParser {
recognizer.complete();
}
public static void parse(String sqlString, ParameterRecognizer recognizer) throws QueryException {
parse( sqlString, recognizer, false );
}
private static void checkIsNotAFunctionCall(String sqlString) {
final String trimmed = sqlString.trim();
if ( !( trimmed.startsWith( "{" ) && trimmed.endsWith( "}" ) ) ) {

View File

@ -7,7 +7,7 @@
package org.hibernate.query.sql.spi;
/**
* Defines the "callback" the process of recognizing native query parameters.
* Defines the "callback" process of recognizing native query parameters.
*
* @see org.hibernate.engine.query.spi.NativeQueryInterpreter#recognizeParameters
*/

View File

@ -9,12 +9,15 @@ package org.hibernate.orm.test.query.sql;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
/**
@ -34,6 +37,24 @@ public class NativeQueryParameterTests {
);
}
@DomainModel( standardModels = StandardDomainModel.HELPDESK )
@SessionFactory
@ServiceRegistry(
settings = {
@Setting(name = AvailableSettings.NATIVE_IGNORE_JDBC_PARAMETERS, value = "true")
}
)
@Test
public void testJdbcParameterScanningDisabled(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
// Nonsensical query just to test that the '?' is ignored
session.createNativeQuery( "select t.id, t.ticket_key, t.subject ? from Ticket t where t.ticket_key = :key" )
.setParameter( "key", "ABC-123" );
}
);
}
@Test
public void testJpaStylePositionalParametersInNativeSql(SessionFactoryScope scope) {
scope.inTransaction(

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.orm.test.query.sql;
import org.hibernate.engine.query.ParameterRecognitionException;
import org.hibernate.engine.query.internal.NativeQueryInterpreterStandardImpl;
import org.hibernate.engine.query.spi.ParamLocationRecognizer;
import org.hibernate.query.sql.internal.ParameterParser;
import org.hibernate.query.sql.spi.ParameterRecognizer;
@ -15,6 +17,7 @@ import org.junit.jupiter.api.Test;
import static org.hibernate.engine.query.internal.NativeQueryInterpreterStandardImpl.NATIVE_QUERY_INTERPRETER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@ -197,4 +200,44 @@ public class ParameterParserTest {
assertEquals( 2, recognizer.getOrdinalParameterDescriptionMap().size() );
}
@Test
public void testJdbcParameterScanningEnabled() {
ParamLocationRecognizer recognizer = createRecognizer();
assertThrows(
ParameterRecognitionException.class,
() -> {
NATIVE_QUERY_INTERPRETER.recognizeParameters(
"SELECT column FROM Table WHERE column.id = :param and column.name = ?1",
recognizer
);
recognizer.validate();
},
"Mixed parameter strategies - use just one of named, positional or JPA-ordinal strategy"
);
}
@Test
public void testJdbcParameterScanningDisabled() {
ParamLocationRecognizer recognizer = createRecognizer();
// Should recognize the jpa style ordinal parameters
new NativeQueryInterpreterStandardImpl( true ).recognizeParameters(
"SELECT column FROM Table WHERE column.id = ?1 and column.name = ?2",
recognizer
);
recognizer.validate();
assertEquals( 2, recognizer.getOrdinalParameterDescriptionMap().size() );
recognizer = createRecognizer();
// Should ignore the '?'
new NativeQueryInterpreterStandardImpl( true ).recognizeParameters(
"SELECT column ? FROM Table WHERE column.id = :id",
recognizer
);
recognizer.validate();
assertTrue(recognizer.getNamedParameterDescriptionMap().containsKey("id"));
assertEquals( 0, recognizer.getOrdinalParameterDescriptionMap().size() );
}
}

View File

@ -427,7 +427,7 @@ public abstract class MockSessionFactory
@Override
public NativeQueryInterpreter getNativeQueryInterpreter() {
return new NativeQueryInterpreterStandardImpl();
return new NativeQueryInterpreterStandardImpl( this.getNativeJdbcParametersIgnored() );
}
@Override