Skip to content

proposal for NativeQuery.setParameterEscapes() #6582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -24,8 +24,8 @@ public class NativeQueryInterpreterStandardImpl implements NativeQueryInterprete
public static final NativeQueryInterpreterStandardImpl NATIVE_QUERY_INTERPRETER = new NativeQueryInterpreterStandardImpl();

@Override
public void recognizeParameters(String nativeQuery, ParameterRecognizer recognizer) {
ParameterParser.parse( nativeQuery, recognizer );
public void recognizeParameters(String nativeQuery, ParameterRecognizer recognizer, char namedParamPrefix, char ordinalParamPrefix) {
ParameterParser.parse( nativeQuery, recognizer, namedParamPrefix, ordinalParamPrefix );
}

@Override
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
import org.hibernate.Incubating;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sql.internal.NativeSelectQueryPlanImpl;
import org.hibernate.query.sql.internal.ParameterParser;
import org.hibernate.query.sql.spi.NativeSelectQueryDefinition;
import org.hibernate.query.sql.spi.NativeSelectQueryPlan;
import org.hibernate.query.sql.spi.ParameterRecognizer;
@@ -30,7 +31,15 @@ public interface NativeQueryInterpreter extends Service {
* @param nativeQuery The query to recognize parameters in
* @param recognizer The recognizer to call
*/
void recognizeParameters(String nativeQuery, ParameterRecognizer recognizer);
void recognizeParameters(String nativeQuery, ParameterRecognizer recognizer, char namedParamPrefix, char ordinalParamPrefix);

/**
* @deprecated use {@link #recognizeParameters(String, ParameterRecognizer, char, char)}
*/
@Deprecated
default void recognizeParameters(String nativeQuery, ParameterRecognizer recognizer) {
recognizeParameters( nativeQuery, recognizer, ParameterParser.NAMED_PARAM_PREFIX, ParameterParser.ORDINAL_PARAM_PREFIX );
}

/**
* Creates a new query plan for the passed native query definition
14 changes: 14 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/jpa/HibernateHints.java
Original file line number Diff line number Diff line change
@@ -156,4 +156,18 @@ public interface HibernateHints {
* to a function rather than a call to a procedure.
*/
String HINT_CALLABLE_FUNCTION = "org.hibernate.callableFunction";

/**
* The prefix character used to recognize a named parameter.
*
* @see org.hibernate.query.NativeQuery#setParameterEscapes(char, char)
*/
String HINT_NAMED_PARAMETER_PREFIX = "org.hibernate.namedParameterPrefix";

/**
* The prefix character used to recognize an ordinal parameter.
*
* @see org.hibernate.query.NativeQuery#setParameterEscapes(char, char)
*/
String HINT_ORDINAL_PARAMETER_PREFIX = "org.hibernate.ordinalParameterPrefix";
}
12 changes: 12 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/query/NativeQuery.java
Original file line number Diff line number Diff line change
@@ -626,6 +626,18 @@ interface FetchReturn extends ResultNode {
@Override
NativeQuery<T> setFirstResult(int startPosition);

/**
* Set the prefix characters used to recognize named and ordinal parameters.
* By default, named parameters are of form {@code :name}, and ordinal
* parameters are of form {@code ?n}.
*
* @param namedParamPrefix the prefix for named parameters
* @param ordinalParamPrefix the prefix for ordinal parameters
*
* @since 6.3
*/
NativeQuery<T> setParameterEscapes(char namedParamPrefix, char ordinalParamPrefix);

@Override
NativeQuery<T> setHint(String hintName, Object value);

Original file line number Diff line number Diff line change
@@ -102,7 +102,9 @@
import jakarta.persistence.Tuple;
import jakarta.persistence.metamodel.SingularAttribute;

import static org.hibernate.jpa.HibernateHints.HINT_NAMED_PARAMETER_PREFIX;
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_LOCK_MODE;
import static org.hibernate.jpa.HibernateHints.HINT_ORDINAL_PARAMETER_PREFIX;
import static org.hibernate.query.results.Builders.resultClassBuilder;

/**
@@ -111,11 +113,11 @@
public class NativeQueryImpl<R>
extends AbstractQuery<R>
implements NativeQueryImplementor<R>, DomainQueryExecutionContext, ResultSetMappingResolutionContext {
private final String sqlString;
private String sqlString;
private final String originalSqlString;
private final ParameterMetadataImplementor parameterMetadata;
private final List<ParameterOccurrence> parameterOccurrences;
private final QueryParameterBindings parameterBindings;
private ParameterMetadataImplementor parameterMetadata;
private List<ParameterOccurrence> parameterOccurrences;
private QueryParameterBindings parameterBindings;

private final ResultSetMapping resultSetMapping;
private final boolean resultMappingSuppliedToCtor;
@@ -125,6 +127,8 @@ public class NativeQueryImpl<R>
private Boolean startsWithSelect;
private Set<String> querySpaces;
private Callback callback;
private char namedParamPrefix = ParameterParser.NAMED_PARAM_PREFIX;
private char ordinalParamPrefix = ParameterParser.ORDINAL_PARAM_PREFIX;

/**
* Constructs a NativeQueryImpl given a sql query defined in the mappings.
@@ -196,15 +200,8 @@ public NativeQueryImpl(

this.originalSqlString = memento.getOriginalSqlString();

final ParameterInterpretation parameterInterpretation = resolveParameterInterpretation(
originalSqlString,
session
);
interpretParameters( 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();
@@ -341,18 +338,23 @@ private ParameterInterpretation resolveParameterInterpretation(
final QueryEngine queryEngine = sessionFactory.getQueryEngine();
final QueryInterpretationCache interpretationCache = queryEngine.getInterpretationCache();

return interpretationCache.resolveNativeQueryParameters(
if ( namedParamPrefix != ParameterParser.NAMED_PARAM_PREFIX || ordinalParamPrefix != ParameterParser.ORDINAL_PARAM_PREFIX ) {
return createParameterInterpretation( sqlString, session );
}
else {
return interpretationCache.resolveNativeQueryParameters(
sqlString,
s -> {
final ParameterRecognizerImpl parameterRecognizer = new ParameterRecognizerImpl();

session.getFactory().getServiceRegistry()
.getService( NativeQueryInterpreter.class )
.recognizeParameters( sqlString, parameterRecognizer );

return new ParameterInterpretationImpl( parameterRecognizer );
}
s -> createParameterInterpretation( sqlString, session )
);
}
}

private ParameterInterpretationImpl createParameterInterpretation(String sqlString, SharedSessionContractImplementor session) {
final ParameterRecognizerImpl parameterRecognizer = new ParameterRecognizerImpl();
session.getFactory().getServiceRegistry()
.getService( NativeQueryInterpreter.class )
.recognizeParameters( sqlString, parameterRecognizer, namedParamPrefix, ordinalParamPrefix );
return new ParameterInterpretationImpl( parameterRecognizer) ;
}

protected void applyOptions(NamedNativeQueryMemento memento) {
@@ -378,17 +380,21 @@ public NativeQueryImpl(String sqlString, SharedSessionContractImplementor sessio

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() );
interpretParameters( session );

this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( sqlString, true, session.getFactory() );
this.resultMappingSuppliedToCtor = false;
}

private void interpretParameters(SharedSessionContractImplementor session) {
final ParameterInterpretation interpretation = resolveParameterInterpretation( originalSqlString, session );
this.sqlString = interpretation.getAdjustedSqlString();
this.parameterMetadata = interpretation.toParameterMetadata(session);
this.parameterOccurrences = interpretation.getOrderedParameterOccurrences();
this.parameterBindings = QueryParameterBindingsImpl.from( parameterMetadata, session.getFactory() );
}

private IllegalArgumentException buildIncompatibleException(Class<?> resultClass, Class<?> actualResultClass) {
final String resultClassName = resultClass.getName();
final String actualResultClassName = actualResultClass.getName();
@@ -1494,14 +1500,29 @@ public NativeQueryImplementor<R> setFirstResult(int startPosition) {
return this;
}


@Override
public NativeQueryImplementor<R> setParameterEscapes(char namedParamPrefix, char ordinalParamPrefix) {
this.namedParamPrefix = namedParamPrefix;
this.ordinalParamPrefix = ordinalParamPrefix;
interpretParameters( getSession() );
return this;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Hints

@Override
public NativeQueryImplementor<R> setHint(String hintName, Object value) {
super.setHint( hintName, value );
switch ( hintName ) {
case HINT_NAMED_PARAMETER_PREFIX:
setParameterEscapes( (Character) value, ordinalParamPrefix );
break;
case HINT_ORDINAL_PARAMETER_PREFIX:
setParameterEscapes( namedParamPrefix, (Character) value );
break;
default:
super.setHint( hintName, value );
}
return this;
}

Original file line number Diff line number Diff line change
@@ -23,6 +23,9 @@ public class ParameterParser {
private static final String HQL_SEPARATORS = " \n\r\f\t,()=<>&|+-=/*'^![]#~\\";
private static final BitSet HQL_SEPARATORS_BITSET = new BitSet();

public static final char NAMED_PARAM_PREFIX = ':';
public static final char ORDINAL_PARAM_PREFIX = '?';

static {
for ( int i = 0; i < HQL_SEPARATORS.length(); i++ ) {
HQL_SEPARATORS_BITSET.set( HQL_SEPARATORS.charAt( i ) );
@@ -35,6 +38,10 @@ public class ParameterParser {
private ParameterParser() {
}

public static void parse(String sqlString, ParameterRecognizer recognizer) {
parse( sqlString, recognizer, NAMED_PARAM_PREFIX, ORDINAL_PARAM_PREFIX );
}

/**
* Performs the actual parsing and tokenizing of the query string making appropriate
* callbacks to the given recognizer upon recognition of the various tokens.
@@ -47,7 +54,8 @@ private ParameterParser() {
* @param recognizer The thing which handles recognition events.
* @throws QueryException Indicates unexpected parameter conditions.
*/
public static void parse(String sqlString, ParameterRecognizer recognizer) throws QueryException {
public static void parse(String sqlString, ParameterRecognizer recognizer, char namedParamPrefix, char ordinalParamPrefix)
throws QueryException {
checkIsNotAFunctionCall( sqlString );
final int stringLength = sqlString.length();

@@ -125,12 +133,12 @@ else if ( '\\' == c ) {
}
// otherwise
else {
if ( c == ':' && indx < stringLength - 1 && sqlString.charAt( indx + 1 ) == ':') {
if ( c == namedParamPrefix && indx < stringLength - 1 && sqlString.charAt( indx + 1 ) == namedParamPrefix) {
// colon character has been escaped
recognizer.other( c );
indx++;
}
else if ( c == ':' ) {
else if ( c == namedParamPrefix) {
// named parameter
final int right = StringHelper.firstIndexOfChar( sqlString, HQL_SEPARATORS_BITSET, indx + 1 );
final int chopLocation = right < 0 ? sqlString.length() : right;
@@ -143,7 +151,7 @@ else if ( c == ':' ) {
recognizer.namedParameter( param, indx );
indx = chopLocation - 1;
}
else if ( c == '?' ) {
else if ( c == ordinalParamPrefix) {
// could be either a positional or JPA-style ordinal parameter
if ( indx < stringLength - 1 && Character.isDigit( sqlString.charAt( indx + 1 ) ) ) {
// a peek ahead showed this as a JPA-positional parameter
Original file line number Diff line number Diff line change
@@ -191,6 +191,9 @@ NativeQueryImplementor<R> addJoin(
@Override
NativeQueryImplementor<R> setFirstResult(int startPosition);

@Override
NativeQueryImplementor<R> setParameterEscapes(char namedParamPrefix, char ordinalParamPrefix);

@Override
NativeQueryImplementor<R> addQueryHint(String hint);

Loading