Skip to content

Commit 75a00df

Browse files
committed
HHH-14624 Oracle from version 12 started supporting the syntax for pagination
(cherry picked from commit 422b80b)
1 parent 120d17d commit 75a00df

File tree

5 files changed

+207
-3
lines changed

5 files changed

+207
-3
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import org.hibernate.cfg.Environment;
1111
import org.hibernate.dialect.identity.IdentityColumnSupport;
1212
import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport;
13+
import org.hibernate.dialect.pagination.AbstractLimitHandler;
14+
import org.hibernate.dialect.pagination.LimitHandler;
15+
import org.hibernate.dialect.pagination.Oracle12LimitHandler;
1316
import org.hibernate.engine.config.spi.ConfigurationService;
1417
import org.hibernate.engine.config.spi.StandardConverters;
1518
import org.hibernate.service.ServiceRegistry;
@@ -24,6 +27,8 @@
2427
public class Oracle12cDialect extends Oracle10gDialect {
2528
public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw";
2629

30+
private static final AbstractLimitHandler LIMIT_HANDLER = Oracle12LimitHandler.INSTANCE;
31+
2732
public Oracle12cDialect() {
2833
super();
2934
getDefaultProperties().setProperty( Environment.BATCH_VERSIONED_DATA, "true" );
@@ -62,4 +67,9 @@ public String getNativeIdentifierGeneratorStrategy() {
6267
public IdentityColumnSupport getIdentityColumnSupport() {
6368
return new Oracle12cIdentityColumnSupport();
6469
}
70+
71+
@Override
72+
public LimitHandler getLimitHandler() {
73+
return LIMIT_HANDLER;
74+
}
6575
}

hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.sql.PreparedStatement;
1010
import java.sql.SQLException;
1111

12+
import org.hibernate.engine.spi.QueryParameters;
1213
import org.hibernate.engine.spi.RowSelection;
1314

1415
/**
@@ -37,13 +38,25 @@ public interface LimitHandler {
3738
/**
3839
* Return processed SQL query.
3940
*
40-
* @param sql the SQL query to process.
41+
* @param sql the SQL query to process.
4142
* @param selection the selection criteria for rows.
4243
*
4344
* @return Query statement with LIMIT clause applied.
4445
*/
4546
String processSql(String sql, RowSelection selection);
4647

48+
/**
49+
* Return processed SQL query.
50+
*
51+
* @param sql the SQL query to process.
52+
* @param queryParameters the queryParameters.
53+
*
54+
* @return Query statement with LIMIT clause applied.
55+
*/
56+
default String processSql(String sql, QueryParameters queryParameters ){
57+
return processSql( sql, queryParameters.getRowSelection() );
58+
}
59+
4760
/**
4861
* Bind parameter values needed by the LIMIT clause before original SELECT statement.
4962
*
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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.dialect.pagination;
8+
9+
import java.util.Locale;
10+
11+
import org.hibernate.LockMode;
12+
import org.hibernate.LockOptions;
13+
import org.hibernate.engine.spi.QueryParameters;
14+
import org.hibernate.engine.spi.RowSelection;
15+
16+
/**
17+
* A {@link LimitHandler} for databases which support the
18+
* ANSI SQL standard syntax {@code FETCH FIRST m ROWS ONLY}
19+
* and {@code OFFSET n ROWS FETCH NEXT m ROWS ONLY}.
20+
*
21+
* @author Gavin King
22+
*/
23+
public class Oracle12LimitHandler extends AbstractLimitHandler {
24+
25+
public boolean bindLimitParametersInReverseOrder;
26+
public boolean useMaxForLimit;
27+
28+
public static final Oracle12LimitHandler INSTANCE = new Oracle12LimitHandler();
29+
30+
Oracle12LimitHandler() {
31+
}
32+
33+
@Override
34+
public String processSql(String sql, RowSelection selection) {
35+
final boolean hasFirstRow = LimitHelper.hasFirstRow( selection );
36+
final boolean hasMaxRows = LimitHelper.hasMaxRows( selection );
37+
38+
if ( !hasMaxRows ) {
39+
return sql;
40+
}
41+
42+
return processSql( sql, getForUpdateIndex( sql ), hasFirstRow );
43+
}
44+
45+
@Override
46+
public String processSql(String sql, QueryParameters queryParameters) {
47+
final RowSelection selection = queryParameters.getRowSelection();
48+
49+
final boolean hasFirstRow = LimitHelper.hasFirstRow( selection );
50+
final boolean hasMaxRows = LimitHelper.hasMaxRows( selection );
51+
52+
if ( !hasMaxRows ) {
53+
return sql;
54+
}
55+
56+
final LockOptions lockOptions = queryParameters.getLockOptions();
57+
if ( lockOptions != null ) {
58+
final LockMode lockMode = lockOptions.getLockMode();
59+
switch ( lockMode ) {
60+
case UPGRADE:
61+
case PESSIMISTIC_READ:
62+
case PESSIMISTIC_WRITE:
63+
case UPGRADE_NOWAIT:
64+
case FORCE:
65+
case PESSIMISTIC_FORCE_INCREMENT:
66+
case UPGRADE_SKIPLOCKED:
67+
return processSql( sql, selection );
68+
default:
69+
return processSqlOffsetFetch( sql, hasFirstRow );
70+
}
71+
}
72+
return processSqlOffsetFetch( sql, hasFirstRow );
73+
}
74+
75+
private String processSqlOffsetFetch(String sql, boolean hasFirstRow) {
76+
77+
final int forUpdateLastIndex = getForUpdateIndex( sql );
78+
79+
if ( forUpdateLastIndex > -1 ) {
80+
return processSql( sql, forUpdateLastIndex, hasFirstRow );
81+
}
82+
83+
bindLimitParametersInReverseOrder = false;
84+
useMaxForLimit = false;
85+
86+
sql = normalizeStatement( sql );
87+
final int offsetFetchLength;
88+
final String offsetFetchString;
89+
if ( hasFirstRow ) {
90+
offsetFetchString = " offset ? rows fetch next ? rows only";
91+
}
92+
else {
93+
offsetFetchString = " fetch first ? rows only";
94+
}
95+
offsetFetchLength = sql.length() + offsetFetchString.length();
96+
97+
return new StringBuilder( offsetFetchLength ).append( sql ).append( offsetFetchString ).toString();
98+
}
99+
100+
private String processSql(String sql, int forUpdateIndex, boolean hasFirstRow) {
101+
bindLimitParametersInReverseOrder = true;
102+
useMaxForLimit = true;
103+
sql = normalizeStatement( sql );
104+
105+
String forUpdateClause = null;
106+
boolean isForUpdate = false;
107+
if ( forUpdateIndex > -1 ) {
108+
// save 'for update ...' and then remove it
109+
forUpdateClause = sql.substring( forUpdateIndex );
110+
sql = sql.substring( 0, forUpdateIndex - 1 );
111+
isForUpdate = true;
112+
}
113+
114+
final StringBuilder pagingSelect;
115+
116+
final int forUpdateClauseLength;
117+
if ( forUpdateClause == null ) {
118+
forUpdateClauseLength = 0;
119+
}
120+
else {
121+
forUpdateClauseLength = forUpdateClause.length() + 1;
122+
}
123+
124+
if ( hasFirstRow ) {
125+
pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 98 );
126+
pagingSelect.append( "select * from ( select row_.*, rownum rownum_ from ( " );
127+
pagingSelect.append( sql );
128+
pagingSelect.append( " ) row_ where rownum <= ?) where rownum_ > ?" );
129+
}
130+
else {
131+
pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 37 );
132+
pagingSelect.append( "select * from ( " );
133+
pagingSelect.append( sql );
134+
pagingSelect.append( " ) where rownum <= ?" );
135+
}
136+
137+
if ( isForUpdate ) {
138+
pagingSelect.append( " " );
139+
pagingSelect.append( forUpdateClause );
140+
}
141+
142+
return pagingSelect.toString();
143+
}
144+
145+
private String normalizeStatement(String sql) {
146+
return sql.trim().replaceAll( "\\s+", " " );
147+
}
148+
149+
private int getForUpdateIndex(String sql) {
150+
final int forUpdateLastIndex = sql.toLowerCase( Locale.ROOT ).lastIndexOf( "for update" );
151+
// We need to recognize cases like : select a from t where b = 'for update';
152+
final int lastIndexOfQuote = sql.lastIndexOf( "'" );
153+
if ( forUpdateLastIndex > -1 ) {
154+
if ( lastIndexOfQuote == -1 ) {
155+
return forUpdateLastIndex;
156+
}
157+
if ( lastIndexOfQuote > forUpdateLastIndex ) {
158+
return -1;
159+
}
160+
return forUpdateLastIndex;
161+
}
162+
return forUpdateLastIndex;
163+
}
164+
165+
@Override
166+
public final boolean supportsLimit() {
167+
return true;
168+
}
169+
170+
@Override
171+
public boolean bindLimitParametersInReverseOrder() {
172+
return bindLimitParametersInReverseOrder;
173+
}
174+
175+
@Override
176+
public boolean useMaxForLimit() {
177+
return useMaxForLimit;
178+
}
179+
180+
181+
}

hibernate-core/src/main/java/org/hibernate/loader/Loader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2004,7 +2004,7 @@ protected SqlStatementWrapper executeQueryStatement(
20042004
final LimitHandler limitHandler = getLimitHandler(
20052005
queryParameters.getRowSelection()
20062006
);
2007-
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() );
2007+
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters );
20082008

20092009
// Adding locks and comments.
20102010
sql = preprocessSQL( sql, queryParameters, getFactory(), afterLoadActions );

hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ protected SqlStatementWrapper executeQueryStatement(
158158
final LimitHandler limitHandler = getLimitHandler(
159159
queryParameters.getRowSelection()
160160
);
161-
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() );
161+
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters );
162162

163163
// Adding locks and comments.
164164
sql = session.getJdbcServices().getJdbcEnvironment().getDialect()

0 commit comments

Comments
 (0)