diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java index 941159b4b4..e184c65e0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle12cDialect.java @@ -10,6 +10,9 @@ import org.hibernate.boot.model.TypeContributions; import org.hibernate.cfg.Environment; import org.hibernate.dialect.identity.IdentityColumnSupport; 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.StandardConverters; import org.hibernate.service.ServiceRegistry; @@ -24,6 +27,8 @@ import org.hibernate.type.WrappedMaterializedBlobType; public class Oracle12cDialect extends Oracle10gDialect { public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw"; + private static final AbstractLimitHandler LIMIT_HANDLER = Oracle12LimitHandler.INSTANCE; + public Oracle12cDialect() { super(); getDefaultProperties().setProperty( Environment.BATCH_VERSIONED_DATA, "true" ); @@ -62,4 +67,9 @@ public class Oracle12cDialect extends Oracle10gDialect { public IdentityColumnSupport getIdentityColumnSupport() { return new Oracle12cIdentityColumnSupport(); } + + @Override + public LimitHandler getLimitHandler() { + return LIMIT_HANDLER; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java index 3013254bee..d0642b2277 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitHandler.java @@ -9,6 +9,7 @@ package org.hibernate.dialect.pagination; import java.sql.PreparedStatement; import java.sql.SQLException; +import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; /** @@ -37,13 +38,25 @@ public interface LimitHandler { /** * 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. * * @return Query statement with LIMIT clause applied. */ 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. * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java new file mode 100644 index 0000000000..3067a01a81 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java @@ -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 . + */ +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; + } + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index ff8808d632..5ef4da495f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -2004,7 +2004,7 @@ public abstract class Loader { final LimitHandler limitHandler = getLimitHandler( queryParameters.getRowSelection() ); - String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); + String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters ); // Adding locks and comments. sql = preprocessSQL( sql, queryParameters, getFactory(), afterLoadActions ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java index 3181c70632..f3fcead56b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java @@ -158,7 +158,7 @@ public abstract class AbstractLoadPlanBasedLoader { final LimitHandler limitHandler = getLimitHandler( queryParameters.getRowSelection() ); - String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); + String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters ); // Adding locks and comments. sql = session.getJdbcServices().getJdbcEnvironment().getDialect()