Skip to content

Commit 8961238

Browse files
committed
HHH-19888 Ensure static offset is always respected in FetchPlusOffsetParameterBinder
1 parent 5c4b43c commit 8961238

File tree

2 files changed

+164
-21
lines changed

2 files changed

+164
-21
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.community.dialect;
8+
9+
import java.util.List;
10+
11+
import org.hibernate.cfg.AvailableSettings;
12+
import org.hibernate.dialect.H2Dialect;
13+
import org.hibernate.dialect.H2SqlAstTranslator;
14+
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
15+
import org.hibernate.engine.spi.SessionFactoryImplementor;
16+
import org.hibernate.query.sqm.FetchClauseType;
17+
import org.hibernate.sql.ast.Clause;
18+
import org.hibernate.sql.ast.SqlAstTranslator;
19+
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
20+
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
21+
import org.hibernate.sql.ast.tree.Statement;
22+
import org.hibernate.sql.ast.tree.expression.Expression;
23+
import org.hibernate.sql.ast.tree.select.QueryPart;
24+
import org.hibernate.sql.exec.spi.JdbcOperation;
25+
26+
import org.hibernate.testing.orm.junit.DomainModel;
27+
import org.hibernate.testing.orm.junit.Jira;
28+
import org.hibernate.testing.orm.junit.RequiresDialect;
29+
import org.hibernate.testing.orm.junit.ServiceRegistry;
30+
import org.hibernate.testing.orm.junit.SessionFactory;
31+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
32+
import org.hibernate.testing.orm.junit.SettingProvider;
33+
import org.junit.jupiter.api.BeforeEach;
34+
import org.junit.jupiter.api.Test;
35+
36+
import jakarta.persistence.Entity;
37+
import jakarta.persistence.Id;
38+
39+
import static org.junit.jupiter.api.Assertions.assertEquals;
40+
41+
@RequiresDialect(H2Dialect.class)
42+
@DomainModel(annotatedClasses = FetchPlusOffsetParameterTest.Book.class)
43+
@SessionFactory
44+
@ServiceRegistry(
45+
settingProviders = @SettingProvider(settingName = AvailableSettings.DIALECT, provider = FetchPlusOffsetParameterTest.TestSettingProvider.class)
46+
)
47+
@Jira("https://hibernate.atlassian.net/browse/HHH-19888")
48+
public class FetchPlusOffsetParameterTest {
49+
50+
@BeforeEach
51+
protected void prepareTest(SessionFactoryScope scope) {
52+
scope.inTransaction(
53+
(session) -> {
54+
for ( int i = 1; i <= 3; i++ ) {
55+
session.persist( new Book( i, "Book " + i ) );
56+
}
57+
}
58+
);
59+
}
60+
61+
@Test
62+
public void testStaticOffset(SessionFactoryScope scope) {
63+
scope.inTransaction(
64+
(session) -> {
65+
final List<Book> books = session.createSelectionQuery(
66+
"from Book b order by b.id",
67+
Book.class
68+
)
69+
.setFirstResult( 2 )
70+
.setMaxResults( 1 ).getResultList();
71+
// The custom dialect will fetch offset + limit + staticOffset rows
72+
// Since staticOffset is -1, it must yield 2 rows
73+
assertEquals( 2, books.size() );
74+
}
75+
);
76+
}
77+
78+
@Entity(name = "Book")
79+
public static class Book {
80+
@Id
81+
private Integer id;
82+
private String title;
83+
84+
public Book() {
85+
}
86+
87+
public Book(Integer id, String title) {
88+
this.id = id;
89+
this.title = title;
90+
}
91+
}
92+
93+
94+
public static class TestSettingProvider implements SettingProvider.Provider<String> {
95+
96+
@Override
97+
public String getSetting() {
98+
return TestDialect.class.getName();
99+
}
100+
}
101+
102+
public static class TestDialect extends H2Dialect {
103+
104+
public TestDialect(DialectResolutionInfo info) {
105+
super( info );
106+
}
107+
108+
public TestDialect() {
109+
}
110+
111+
@Override
112+
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
113+
return new StandardSqlAstTranslatorFactory() {
114+
@Override
115+
protected <T extends JdbcOperation> SqlAstTranslator<T> buildTranslator(
116+
SessionFactoryImplementor sessionFactory, Statement statement) {
117+
return new H2SqlAstTranslator<>( sessionFactory, statement ) {
118+
@Override
119+
public void visitOffsetFetchClause(QueryPart queryPart) {
120+
final Expression offsetClauseExpression;
121+
final Expression fetchClauseExpression;
122+
if ( queryPart.isRoot() && hasLimit() ) {
123+
prepareLimitOffsetParameters();
124+
offsetClauseExpression = getOffsetParameter();
125+
fetchClauseExpression = getLimitParameter();
126+
}
127+
else {
128+
assert queryPart.getFetchClauseType() == FetchClauseType.ROWS_ONLY;
129+
offsetClauseExpression = queryPart.getOffsetClauseExpression();
130+
fetchClauseExpression = queryPart.getFetchClauseExpression();
131+
}
132+
if ( offsetClauseExpression != null && fetchClauseExpression != null ) {
133+
appendSql( " fetch first " );
134+
getClauseStack().push( Clause.FETCH );
135+
try {
136+
renderFetchPlusOffsetExpressionAsSingleParameter(
137+
fetchClauseExpression,
138+
offsetClauseExpression,
139+
-1
140+
);
141+
}
142+
finally {
143+
getClauseStack().pop();
144+
}
145+
appendSql( " rows only" );
146+
}
147+
}
148+
};
149+
}
150+
};
151+
}
152+
}
153+
}
154+
155+

hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4744,38 +4744,23 @@ protected void renderFetchPlusOffsetExpressionAsSingleParameter(
47444744
appendSql( PARAM_MARKER );
47454745
final JdbcParameter offsetParameter = (JdbcParameter) offsetClauseExpression;
47464746
final JdbcParameter fetchParameter = (JdbcParameter) fetchClauseExpression;
4747-
final OffsetReceivingParameterBinder fetchBinder = new OffsetReceivingParameterBinder(
4747+
final FetchPlusOffsetParameterBinder fetchBinder = new FetchPlusOffsetParameterBinder(
47484748
offsetParameter,
47494749
fetchParameter,
47504750
offset
47514751
);
4752-
// We don't register and bind the special OffsetJdbcParameter as that comes from the query options
4753-
// And in this case, we only want to bind a single JDBC parameter
4754-
if ( !( offsetParameter instanceof OffsetJdbcParameter ) ) {
4755-
jdbcParameters.addParameter( offsetParameter );
4756-
parameterBinders.add(
4757-
(statement, startPosition, jdbcParameterBindings, executionContext) -> {
4758-
final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter );
4759-
if ( binding == null ) {
4760-
throw new ExecutionException( "JDBC parameter value not bound - " + offsetParameter );
4761-
}
4762-
fetchBinder.dynamicOffset = (Number) binding.getBindValue();
4763-
}
4764-
);
4765-
}
47664752
jdbcParameters.addParameter( fetchParameter );
47674753
parameterBinders.add( fetchBinder );
47684754
}
47694755
}
47704756

4771-
private static class OffsetReceivingParameterBinder implements JdbcParameterBinder {
4757+
private static class FetchPlusOffsetParameterBinder implements JdbcParameterBinder {
47724758

47734759
private final JdbcParameter offsetParameter;
47744760
private final JdbcParameter fetchParameter;
47754761
private final int staticOffset;
4776-
private Number dynamicOffset;
47774762

4778-
public OffsetReceivingParameterBinder(
4763+
public FetchPlusOffsetParameterBinder(
47794764
JdbcParameter offsetParameter,
47804765
JdbcParameter fetchParameter,
47814766
int staticOffset) {
@@ -4806,13 +4791,16 @@ public void bindParameterValue(
48064791
offsetValue = executionContext.getQueryOptions().getEffectiveLimit().getFirstRow();
48074792
}
48084793
else {
4809-
offsetValue = dynamicOffset.intValue() + staticOffset;
4810-
dynamicOffset = null;
4794+
final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter );
4795+
if ( binding == null ) {
4796+
throw new ExecutionException( "JDBC parameter value not bound - " + offsetParameter );
4797+
}
4798+
offsetValue = ((Number) binding.getBindValue()).intValue();
48114799
}
48124800
//noinspection unchecked
48134801
fetchParameter.getExpressionType().getSingleJdbcMapping().getJdbcValueBinder().bind(
48144802
statement,
4815-
bindValue.intValue() + offsetValue,
4803+
bindValue.intValue() + offsetValue + staticOffset,
48164804
startPosition,
48174805
executionContext.getSession()
48184806
);

0 commit comments

Comments
 (0)