Skip to content

Commit 2db9578

Browse files
author
nathan.xu
committed
HHH-16283 - Integrate ParameterMarkerStrategy into NativeQuery
1 parent afca931 commit 2db9578

File tree

4 files changed

+101
-84
lines changed

4 files changed

+101
-84
lines changed

hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
5656
import org.hibernate.query.internal.ParameterMetadataImpl;
5757
import org.hibernate.query.internal.QueryOptionsImpl;
58-
import org.hibernate.query.internal.QueryParameterBindingsImpl;
5958
import org.hibernate.query.internal.ResultSetMappingResolutionContext;
6059
import org.hibernate.query.named.NamedResultSetMappingMemento;
6160
import org.hibernate.query.results.Builders;
@@ -90,6 +89,8 @@
9089
import org.hibernate.query.sql.spi.ParameterInterpretation;
9190
import org.hibernate.query.sql.spi.ParameterOccurrence;
9291
import org.hibernate.query.sql.spi.SelectInterpretationsKey;
92+
import org.hibernate.sql.ast.internal.ParameterMarkerStrategyStandard;
93+
import org.hibernate.sql.ast.spi.ParameterMarkerStrategy;
9394
import org.hibernate.sql.exec.internal.CallbackImpl;
9495
import org.hibernate.sql.exec.spi.Callback;
9596
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
@@ -117,6 +118,7 @@
117118

118119
/**
119120
* @author Steve Ebersole
121+
* @author Nathan Xu
120122
*/
121123
public class NativeQueryImpl<R>
122124
extends AbstractQuery<R>
@@ -370,7 +372,8 @@ private ParameterInterpretation resolveParameterInterpretation(
370372
return interpretationCache.resolveNativeQueryParameters(
371373
sqlString,
372374
s -> {
373-
final ParameterRecognizerImpl parameterRecognizer = new ParameterRecognizerImpl();
375+
final ParameterMarkerStrategy parameterMarkerStrategy = sessionFactory.getJdbcServices().getDialect().getNativeParameterMarkerStrategy();
376+
final ParameterRecognizerImpl parameterRecognizer = new ParameterRecognizerImpl(parameterMarkerStrategy);
374377

375378
session.getFactory().getServiceRegistry()
376379
.requireService( NativeQueryInterpreter.class )
@@ -736,23 +739,38 @@ protected String expandParameterLists() {
736739
// Some DBs limit number of IN expressions. For now, warn...
737740
final SessionFactoryImplementor sessionFactory = getSessionFactory();
738741
final Dialect dialect = sessionFactory.getJdbcServices().getDialect();
742+
743+
ParameterMarkerStrategy parameterMarkerStrategy = dialect.getNativeParameterMarkerStrategy();
744+
if (parameterMarkerStrategy == null) {
745+
parameterMarkerStrategy = ParameterMarkerStrategyStandard.INSTANCE;
746+
}
739747
final boolean paddingEnabled = sessionFactory.getSessionFactoryOptions().inClauseParameterPaddingEnabled();
740748
final int inExprLimit = dialect.getInExpressionCountLimit();
741749

742750
StringBuilder sb = null;
751+
StringBuilder occurrenceExpansionSB = null;
743752

744753
// Handle parameter lists
745-
int offset = 0;
746-
for ( ParameterOccurrence occurrence : parameterOccurrences ) {
754+
int sourceOffset = 0;
755+
int expandedParamPosition = 1;
756+
for ( int originalParamPosition = 1; originalParamPosition <= parameterOccurrences.size(); originalParamPosition++ ) {
757+
final ParameterOccurrence occurrence = parameterOccurrences.get( originalParamPosition - 1 );
747758
final QueryParameterImplementor<?> queryParameter = occurrence.getParameter();
748759
final QueryParameterBinding<?> binding = parameterBindings.getBinding( queryParameter );
749760
if ( !binding.isMultiValued() ) {
761+
if ( originalParamPosition != expandedParamPosition ) {
762+
if ( sb == null ) {
763+
sb = new StringBuilder( sqlString );
764+
}
765+
sourceOffset = getNewSourceOffsetAfterReplacement( sb, sourceOffset, occurrence, parameterMarkerStrategy.createMarker( expandedParamPosition, occurrence.getJdbcType() ) );
766+
}
767+
expandedParamPosition++;
750768
continue;
751769
}
752770
final Collection<?> bindValues = binding.getBindValues();
753771

754-
int bindValueCount = bindValues.size();
755-
int bindValueMaxCount = determineBindValueMaxCount( paddingEnabled, inExprLimit, bindValueCount );
772+
final int bindValueCount = bindValues.size();
773+
final int bindValueMaxCount = determineBindValueMaxCount( paddingEnabled, inExprLimit, bindValueCount );
756774

757775
if ( inExprLimit > 0 && bindValueCount > inExprLimit ) {
758776
log.tooManyInExpressions(
@@ -767,6 +785,7 @@ protected String expandParameterLists() {
767785

768786
final int sourcePosition = occurrence.getSourcePosition();
769787
if ( sourcePosition < 0 ) {
788+
expandedParamPosition++;
770789
continue;
771790
}
772791

@@ -781,7 +800,7 @@ protected String expandParameterLists() {
781800
}
782801
}
783802
if ( isEnclosedInParens ) {
784-
for ( int i = sourcePosition + 1; i < sqlString.length(); i++ ) {
803+
for ( int i = sourcePosition + occurrence.getLength(); i < sqlString.length(); i++ ) {
785804
final char ch = sqlString.charAt( i );
786805
if ( !Character.isWhitespace( ch ) ) {
787806
isEnclosedInParens = ch == ')';
@@ -790,62 +809,53 @@ protected String expandParameterLists() {
790809
}
791810
}
792811

793-
if ( bindValueCount == 1 && isEnclosedInParens ) {
794-
// short-circuit for performance when only 1 value and the
795-
// placeholder is already enclosed in parentheses...
796-
continue;
812+
if ( sb == null ) {
813+
sb = new StringBuilder( sqlString );
797814
}
798815

799-
if ( sb == null ) {
800-
sb = new StringBuilder( sqlString.length() + 20 );
801-
sb.append( sqlString );
816+
if ( occurrenceExpansionSB == null ) {
817+
occurrenceExpansionSB = new StringBuilder();
818+
} else {
819+
occurrenceExpansionSB.setLength( 0 );
820+
}
821+
822+
if ( !isEnclosedInParens ) {
823+
occurrenceExpansionSB.append( '(' );
802824
}
803825

804-
final String expansionListAsString;
805826
// HHH-8901
806827
if ( bindValueMaxCount == 0 ) {
807-
if ( isEnclosedInParens ) {
808-
expansionListAsString = "null";
809-
}
810-
else {
811-
expansionListAsString = "(null)";
812-
}
813-
}
814-
else {
815-
// Shift 1 bit instead of multiplication by 2
816-
char[] chars;
817-
if ( isEnclosedInParens ) {
818-
chars = new char[( bindValueMaxCount << 1 ) - 1];
819-
chars[0] = '?';
820-
for ( int i = 1; i < bindValueMaxCount; i++ ) {
821-
final int index = i << 1;
822-
chars[index - 1] = ',';
823-
chars[index] = '?';
824-
}
825-
}
826-
else {
827-
chars = new char[( bindValueMaxCount << 1 ) + 1];
828-
chars[0] = '(';
829-
chars[1] = '?';
830-
for ( int i = 1; i < bindValueMaxCount; i++ ) {
831-
final int index = i << 1;
832-
chars[index] = ',';
833-
chars[index + 1] = '?';
828+
occurrenceExpansionSB.append( "null" );
829+
} else {
830+
for ( int i = 0; i < bindValueMaxCount; i++ ) {
831+
final String marker = parameterMarkerStrategy.createMarker(
832+
expandedParamPosition + i,
833+
occurrence.getJdbcType()
834+
);
835+
occurrenceExpansionSB.append( marker );
836+
if ( i + 1 < bindValueMaxCount ) {
837+
occurrenceExpansionSB.append( ',' );
834838
}
835-
chars[chars.length - 1] = ')';
836839
}
837-
838-
expansionListAsString = new String(chars);
839840
}
841+
if ( !isEnclosedInParens ) {
842+
occurrenceExpansionSB.append( ')' );
843+
}
844+
845+
sourceOffset = getNewSourceOffsetAfterReplacement( sb, sourceOffset, occurrence, occurrenceExpansionSB.toString() );
840846

841-
final int start = sourcePosition + offset;
842-
final int end = start + 1;
843-
sb.replace( start, end, expansionListAsString );
844-
offset += expansionListAsString.length() - 1;
847+
expandedParamPosition += bindValueMaxCount;
845848
}
846849
return sb == null ? sqlString : sb.toString();
847850
}
848851

852+
private int getNewSourceOffsetAfterReplacement(StringBuilder sb, int sourceOffset, ParameterOccurrence occurrence, String replacement) {
853+
final int start = occurrence.getSourcePosition() + sourceOffset;
854+
final int end = start + occurrence.getLength();
855+
sb.replace( start, end, replacement );
856+
return sourceOffset + ( replacement.length() - occurrence.getLength() );
857+
}
858+
849859
public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprLimit, int bindValueCount) {
850860
int bindValueMaxCount = bindValueCount;
851861

hibernate-core/src/main/java/org/hibernate/query/sql/internal/ParameterRecognizerImpl.java

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.hibernate.query.spi.QueryParameterImplementor;
2121
import org.hibernate.query.sql.spi.ParameterOccurrence;
2222
import org.hibernate.query.sql.spi.ParameterRecognizer;
23+
import org.hibernate.sql.ast.internal.ParameterMarkerStrategyStandard;
24+
import org.hibernate.sql.ast.spi.ParameterMarkerStrategy;
2325

2426
/**
2527
* @author Steve Ebersole
@@ -36,13 +38,15 @@ private enum ParameterStyle {
3638
private Map<String, QueryParameterImplementor<?>> namedQueryParameters;
3739
private Map<Integer, QueryParameterImplementor<?>> positionalQueryParameters;
3840

39-
private int ordinalParameterImplicitPosition;
41+
private int parameterImplicitPosition;
42+
private final ParameterMarkerStrategy parameterMarkerStrategy;
4043

4144
private List<ParameterOccurrence> parameterList;
4245
private final StringBuilder sqlStringBuffer = new StringBuilder();
4346

44-
public ParameterRecognizerImpl() {
45-
ordinalParameterImplicitPosition = 1;
47+
public ParameterRecognizerImpl(ParameterMarkerStrategy parameterMarkerStrategy) {
48+
this.parameterMarkerStrategy = parameterMarkerStrategy == null ? ParameterMarkerStrategyStandard.INSTANCE : parameterMarkerStrategy;
49+
parameterImplicitPosition = 1;
4650
}
4751

4852
@Override
@@ -103,7 +107,7 @@ else if ( parameterStyle != ParameterStyle.JDBC ) {
103107
throw new ParameterRecognitionException( "Cannot mix parameter styles between JDBC-style, ordinal and named in the same query" );
104108
}
105109

106-
int implicitPosition = ordinalParameterImplicitPosition++;
110+
int implicitPosition = parameterImplicitPosition++;
107111

108112
QueryParameterImplementor<?> parameter = null;
109113

@@ -119,12 +123,7 @@ else if ( parameterStyle != ParameterStyle.JDBC ) {
119123
positionalQueryParameters.put( implicitPosition, parameter );
120124
}
121125

122-
if ( parameterList == null ) {
123-
parameterList = new ArrayList<>();
124-
}
125-
126-
parameterList.add( new ParameterOccurrence( parameter, sqlStringBuffer.length() ) );
127-
sqlStringBuffer.append( "?" );
126+
recognizeParameter( parameter, implicitPosition );
128127
}
129128

130129
@Override
@@ -150,12 +149,7 @@ else if ( parameterStyle != ParameterStyle.NAMED ) {
150149
namedQueryParameters.put( name, parameter );
151150
}
152151

153-
if ( parameterList == null ) {
154-
parameterList = new ArrayList<>();
155-
}
156-
157-
parameterList.add( new ParameterOccurrence( parameter, sqlStringBuffer.length() ) );
158-
sqlStringBuffer.append( "?" );
152+
recognizeParameter( parameter, parameterImplicitPosition++ );
159153
}
160154

161155
@Override
@@ -185,16 +179,21 @@ else if ( parameterStyle != ParameterStyle.NAMED ) {
185179
positionalQueryParameters.put( position, parameter );
186180
}
187181

188-
if ( parameterList == null ) {
189-
parameterList = new ArrayList<>();
190-
}
191-
192-
parameterList.add( new ParameterOccurrence( parameter, sqlStringBuffer.length() ) );
193-
sqlStringBuffer.append( "?" );
182+
recognizeParameter(parameter, position);
194183
}
195184

196185
@Override
197186
public void other(char character) {
198187
sqlStringBuffer.append( character );
199188
}
189+
190+
private void recognizeParameter(QueryParameterImplementor parameter, int position) {
191+
final String marker = parameterMarkerStrategy.createMarker( position, null );
192+
final int markerLength = marker.length();
193+
if ( parameterList == null ) {
194+
parameterList = new ArrayList<>();
195+
}
196+
sqlStringBuffer.append( marker );
197+
parameterList.add( new ParameterOccurrence( parameter, sqlStringBuffer.length() - markerLength, markerLength, null ) );
198+
}
200199
}

hibernate-core/src/main/java/org/hibernate/query/sql/spi/ParameterOccurrence.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.query.sql.spi;
88

99
import org.hibernate.query.spi.QueryParameterImplementor;
10+
import org.hibernate.type.descriptor.jdbc.JdbcType;
1011

1112
/**
1213
* @author Christian Beikov
@@ -15,10 +16,14 @@ public final class ParameterOccurrence {
1516

1617
private final QueryParameterImplementor<?> parameter;
1718
private final int sourcePosition;
19+
private final int length;
20+
private final JdbcType jdbcType;
1821

19-
public ParameterOccurrence(QueryParameterImplementor<?> parameter, int sourcePosition) {
22+
public ParameterOccurrence(QueryParameterImplementor<?> parameter, int sourcePosition, int length, JdbcType jdbcType) {
2023
this.parameter = parameter;
2124
this.sourcePosition = sourcePosition;
25+
this.length = length;
26+
this.jdbcType = jdbcType;
2227
}
2328

2429
public QueryParameterImplementor<?> getParameter() {
@@ -28,4 +33,12 @@ public QueryParameterImplementor<?> getParameter() {
2833
public int getSourcePosition() {
2934
return sourcePosition;
3035
}
36+
37+
public int getLength() {
38+
return length;
39+
}
40+
41+
public JdbcType getJdbcType() {
42+
return jdbcType;
43+
}
3144
}

hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/ParameterMarkerStrategyTests.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,31 @@
88

99
import java.util.List;
1010

11+
import jakarta.persistence.Basic;
12+
import jakarta.persistence.Entity;
13+
import jakarta.persistence.Id;
14+
import jakarta.persistence.Table;
15+
import jakarta.persistence.Version;
1116
import org.hibernate.LockMode;
1217
import org.hibernate.annotations.Filter;
1318
import org.hibernate.annotations.FilterDef;
1419
import org.hibernate.annotations.ParamDef;
1520
import org.hibernate.dialect.H2Dialect;
16-
import org.hibernate.internal.util.StringHelper;
1721
import org.hibernate.sql.ast.spi.ParameterMarkerStrategy;
18-
import org.hibernate.type.descriptor.jdbc.JdbcType;
19-
2022
import org.hibernate.testing.jdbc.SQLStatementInspector;
2123
import org.hibernate.testing.orm.domain.gambit.EntityOfBasics;
2224
import org.hibernate.testing.orm.junit.DomainModel;
23-
import org.hibernate.testing.orm.junit.FailureExpected;
2425
import org.hibernate.testing.orm.junit.Jira;
2526
import org.hibernate.testing.orm.junit.RequiresDialect;
2627
import org.hibernate.testing.orm.junit.ServiceRegistry;
2728
import org.hibernate.testing.orm.junit.SessionFactory;
2829
import org.hibernate.testing.orm.junit.SessionFactoryScope;
30+
import org.hibernate.type.descriptor.jdbc.JdbcType;
2931
import org.junit.jupiter.api.AfterEach;
3032
import org.junit.jupiter.api.Test;
3133

32-
import jakarta.persistence.Basic;
33-
import jakarta.persistence.Entity;
34-
import jakarta.persistence.Id;
35-
import jakarta.persistence.Table;
36-
import jakarta.persistence.Version;
37-
3834
import static org.assertj.core.api.Assertions.assertThat;
39-
import static org.hibernate.internal.util.StringHelper.*;
35+
import static org.hibernate.internal.util.StringHelper.count;
4036

4137
/**
4238
* @implNote Restricted to H2 as there is nothing intrinsically Dialect specific here,
@@ -151,7 +147,6 @@ public void testLocking(SessionFactoryScope scope) {
151147
}
152148

153149
@Test
154-
@FailureExpected
155150
@Jira( "https://hibernate.atlassian.net/browse/HHH-16283" )
156151
public void testNativeQuery(SessionFactoryScope scope) {
157152
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();

0 commit comments

Comments
 (0)