HHH-14624 Oracle from version 12 started supporting the syntax for pagination

(cherry picked from commit 422b80b80d)
This commit is contained in:
Andrea Boriero 2021-05-20 16:18:07 +02:00 committed by Andrea Boriero
parent 120d17dcb4
commit 75a00df126
5 changed files with 207 additions and 3 deletions

View File

@ -10,6 +10,9 @@ import org.hibernate.boot.model.TypeContributions;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport; import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport;
import org.hibernate.dialect.pagination.AbstractLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.Oracle12LimitHandler;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
@ -24,6 +27,8 @@ import org.hibernate.type.WrappedMaterializedBlobType;
public class Oracle12cDialect extends Oracle10gDialect { public class Oracle12cDialect extends Oracle10gDialect {
public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw"; public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw";
private static final AbstractLimitHandler LIMIT_HANDLER = Oracle12LimitHandler.INSTANCE;
public Oracle12cDialect() { public Oracle12cDialect() {
super(); super();
getDefaultProperties().setProperty( Environment.BATCH_VERSIONED_DATA, "true" ); getDefaultProperties().setProperty( Environment.BATCH_VERSIONED_DATA, "true" );
@ -62,4 +67,9 @@ public class Oracle12cDialect extends Oracle10gDialect {
public IdentityColumnSupport getIdentityColumnSupport() { public IdentityColumnSupport getIdentityColumnSupport() {
return new Oracle12cIdentityColumnSupport(); return new Oracle12cIdentityColumnSupport();
} }
@Override
public LimitHandler getLimitHandler() {
return LIMIT_HANDLER;
}
} }

View File

@ -9,6 +9,7 @@ package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.RowSelection;
/** /**
@ -37,13 +38,25 @@ public interface LimitHandler {
/** /**
* Return processed SQL query. * Return processed SQL query.
* *
* @param sql the SQL query to process. * @param sql the SQL query to process.
* @param selection the selection criteria for rows. * @param selection the selection criteria for rows.
* *
* @return Query statement with LIMIT clause applied. * @return Query statement with LIMIT clause applied.
*/ */
String processSql(String sql, RowSelection selection); String processSql(String sql, RowSelection selection);
/**
* Return processed SQL query.
*
* @param sql the SQL query to process.
* @param queryParameters the queryParameters.
*
* @return Query statement with LIMIT clause applied.
*/
default String processSql(String sql, QueryParameters queryParameters ){
return processSql( sql, queryParameters.getRowSelection() );
}
/** /**
* Bind parameter values needed by the LIMIT clause before original SELECT statement. * Bind parameter values needed by the LIMIT clause before original SELECT statement.
* *

View File

@ -0,0 +1,181 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.dialect.pagination;
import java.util.Locale;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
/**
* A {@link LimitHandler} for databases which support the
* ANSI SQL standard syntax {@code FETCH FIRST m ROWS ONLY}
* and {@code OFFSET n ROWS FETCH NEXT m ROWS ONLY}.
*
* @author Gavin King
*/
public class Oracle12LimitHandler extends AbstractLimitHandler {
public boolean bindLimitParametersInReverseOrder;
public boolean useMaxForLimit;
public static final Oracle12LimitHandler INSTANCE = new Oracle12LimitHandler();
Oracle12LimitHandler() {
}
@Override
public String processSql(String sql, RowSelection selection) {
final boolean hasFirstRow = LimitHelper.hasFirstRow( selection );
final boolean hasMaxRows = LimitHelper.hasMaxRows( selection );
if ( !hasMaxRows ) {
return sql;
}
return processSql( sql, getForUpdateIndex( sql ), hasFirstRow );
}
@Override
public String processSql(String sql, QueryParameters queryParameters) {
final RowSelection selection = queryParameters.getRowSelection();
final boolean hasFirstRow = LimitHelper.hasFirstRow( selection );
final boolean hasMaxRows = LimitHelper.hasMaxRows( selection );
if ( !hasMaxRows ) {
return sql;
}
final LockOptions lockOptions = queryParameters.getLockOptions();
if ( lockOptions != null ) {
final LockMode lockMode = lockOptions.getLockMode();
switch ( lockMode ) {
case UPGRADE:
case PESSIMISTIC_READ:
case PESSIMISTIC_WRITE:
case UPGRADE_NOWAIT:
case FORCE:
case PESSIMISTIC_FORCE_INCREMENT:
case UPGRADE_SKIPLOCKED:
return processSql( sql, selection );
default:
return processSqlOffsetFetch( sql, hasFirstRow );
}
}
return processSqlOffsetFetch( sql, hasFirstRow );
}
private String processSqlOffsetFetch(String sql, boolean hasFirstRow) {
final int forUpdateLastIndex = getForUpdateIndex( sql );
if ( forUpdateLastIndex > -1 ) {
return processSql( sql, forUpdateLastIndex, hasFirstRow );
}
bindLimitParametersInReverseOrder = false;
useMaxForLimit = false;
sql = normalizeStatement( sql );
final int offsetFetchLength;
final String offsetFetchString;
if ( hasFirstRow ) {
offsetFetchString = " offset ? rows fetch next ? rows only";
}
else {
offsetFetchString = " fetch first ? rows only";
}
offsetFetchLength = sql.length() + offsetFetchString.length();
return new StringBuilder( offsetFetchLength ).append( sql ).append( offsetFetchString ).toString();
}
private String processSql(String sql, int forUpdateIndex, boolean hasFirstRow) {
bindLimitParametersInReverseOrder = true;
useMaxForLimit = true;
sql = normalizeStatement( sql );
String forUpdateClause = null;
boolean isForUpdate = false;
if ( forUpdateIndex > -1 ) {
// save 'for update ...' and then remove it
forUpdateClause = sql.substring( forUpdateIndex );
sql = sql.substring( 0, forUpdateIndex - 1 );
isForUpdate = true;
}
final StringBuilder pagingSelect;
final int forUpdateClauseLength;
if ( forUpdateClause == null ) {
forUpdateClauseLength = 0;
}
else {
forUpdateClauseLength = forUpdateClause.length() + 1;
}
if ( hasFirstRow ) {
pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 98 );
pagingSelect.append( "select * from ( select row_.*, rownum rownum_ from ( " );
pagingSelect.append( sql );
pagingSelect.append( " ) row_ where rownum <= ?) where rownum_ > ?" );
}
else {
pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 37 );
pagingSelect.append( "select * from ( " );
pagingSelect.append( sql );
pagingSelect.append( " ) where rownum <= ?" );
}
if ( isForUpdate ) {
pagingSelect.append( " " );
pagingSelect.append( forUpdateClause );
}
return pagingSelect.toString();
}
private String normalizeStatement(String sql) {
return sql.trim().replaceAll( "\\s+", " " );
}
private int getForUpdateIndex(String sql) {
final int forUpdateLastIndex = sql.toLowerCase( Locale.ROOT ).lastIndexOf( "for update" );
// We need to recognize cases like : select a from t where b = 'for update';
final int lastIndexOfQuote = sql.lastIndexOf( "'" );
if ( forUpdateLastIndex > -1 ) {
if ( lastIndexOfQuote == -1 ) {
return forUpdateLastIndex;
}
if ( lastIndexOfQuote > forUpdateLastIndex ) {
return -1;
}
return forUpdateLastIndex;
}
return forUpdateLastIndex;
}
@Override
public final boolean supportsLimit() {
return true;
}
@Override
public boolean bindLimitParametersInReverseOrder() {
return bindLimitParametersInReverseOrder;
}
@Override
public boolean useMaxForLimit() {
return useMaxForLimit;
}
}

View File

@ -2004,7 +2004,7 @@ public abstract class Loader {
final LimitHandler limitHandler = getLimitHandler( final LimitHandler limitHandler = getLimitHandler(
queryParameters.getRowSelection() queryParameters.getRowSelection()
); );
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters );
// Adding locks and comments. // Adding locks and comments.
sql = preprocessSQL( sql, queryParameters, getFactory(), afterLoadActions ); sql = preprocessSQL( sql, queryParameters, getFactory(), afterLoadActions );

View File

@ -158,7 +158,7 @@ public abstract class AbstractLoadPlanBasedLoader {
final LimitHandler limitHandler = getLimitHandler( final LimitHandler limitHandler = getLimitHandler(
queryParameters.getRowSelection() queryParameters.getRowSelection()
); );
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters );
// Adding locks and comments. // Adding locks and comments.
sql = session.getJdbcServices().getJdbcEnvironment().getDialect() sql = session.getJdbcServices().getJdbcEnvironment().getDialect()