HHH-17409 Support offset without limit in some LimitHandlers

This commit is contained in:
Christian Beikov 2023-11-09 14:26:39 +01:00
parent 4087774691
commit 016dc56208
11 changed files with 97 additions and 31 deletions

View File

@ -146,7 +146,7 @@ public class H2LegacyDialect extends Dialect {
// https://github.com/h2database/h2database/commit/b2cdf84e0b84eb8a482fa7dccdccc1ab95241440
limitHandler = version.isSameOrAfter( 1, 4, 195 )
? OffsetFetchLimitHandler.INSTANCE
: LimitOffsetLimitHandler.INSTANCE;
: LimitOffsetLimitHandler.OFFSET_ONLY_INSTANCE;
if ( version.isBefore( 1, 2, 139 ) ) {
LOG.unsupportedMultiTableBulkHqlJpaql( version.getMajor(), version.getMinor(), version.getMicro() );

View File

@ -422,7 +422,7 @@ public class HSQLLegacyDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
return getVersion().isBefore( 2 ) ? LegacyHSQLLimitHandler.INSTANCE
: getVersion().isBefore( 2, 5 ) ? LimitOffsetLimitHandler.INSTANCE
: getVersion().isBefore( 2, 5 ) ? LimitOffsetLimitHandler.OFFSET_ONLY_INSTANCE
: OffsetFetchLimitHandler.INSTANCE;
}

View File

@ -16,6 +16,7 @@ import org.hibernate.dialect.AbstractTransactSQLDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitLimitHandler;
import org.hibernate.dialect.pagination.LimitOffsetLimitHandler;
import org.hibernate.dialect.sequence.SequenceSupport;
import org.hibernate.dialect.temptable.TemporaryTable;
@ -130,7 +131,7 @@ public class MaxDBDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
return LimitOffsetLimitHandler.INSTANCE;
return LimitLimitHandler.INSTANCE;
}
@Override

View File

@ -750,7 +750,7 @@ public class PostgreSQLLegacyDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
return getVersion().isBefore( 8, 4 )
? LimitOffsetLimitHandler.INSTANCE
? LimitOffsetLimitHandler.OFFSET_ONLY_INSTANCE
: OffsetFetchLimitHandler.INSTANCE;
}

View File

@ -31,6 +31,11 @@ public class RowsLimitHandler extends AbstractSimpleLimitHandler {
return hasFirstRow ? " rows ? to ?" : " rows ?";
}
@Override
protected String offsetOnlyClause() {
return " rows ? to " + Integer.MAX_VALUE;
}
@Override
public final boolean useMaxForLimit() {
return true;
@ -48,4 +53,9 @@ public class RowsLimitHandler extends AbstractSimpleLimitHandler {
protected Pattern getForUpdatePattern() {
return FOR_UPDATE_PATTERN;
}
@Override
public boolean supportsOffset() {
return true;
}
}

View File

@ -168,7 +168,6 @@ public class OracleDialect extends Dialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 19 );
private final LimitHandler limitHandler = Oracle12LimitHandler.INSTANCE;
private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this);
// Is it an Autonomous Database Cloud Service?
@ -939,7 +938,7 @@ public class OracleDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
return limitHandler;
return Oracle12LimitHandler.INSTANCE;
}
@Override

View File

@ -18,9 +18,17 @@ public abstract class AbstractSimpleLimitHandler extends AbstractLimitHandler {
protected abstract String limitClause(boolean hasFirstRow);
protected String offsetOnlyClause() {
return null;
}
@Override
public String processSql(String sql, Limit limit) {
if ( !hasMaxRows( limit ) ) {
final String offsetOnlyClause = offsetOnlyClause();
if ( offsetOnlyClause != null && hasFirstRow( limit ) ) {
return insert( offsetOnlyClause, sql );
}
return sql;
}
return insert( limitClause( hasFirstRow( limit ) ), sql );
@ -39,4 +47,9 @@ public abstract class AbstractSimpleLimitHandler extends AbstractLimitHandler {
public final boolean supportsVariableLimit() {
return true;
}
@Override
public boolean supportsOffset() {
return super.supportsOffset();
}
}

View File

@ -28,6 +28,11 @@ public class LimitLimitHandler extends AbstractSimpleLimitHandler {
return hasFirstRow ? " limit ?,?" : " limit ?";
}
@Override
protected String offsetOnlyClause() {
return " limit ?," + Integer.MAX_VALUE;
}
private static final Pattern FOR_UPDATE_PATTERN =
compile("\\s+for\\s+update\\b|\\s+lock\\s+in\\s+shared\\s+mode\\b|\\s*(;|$)", CASE_INSENSITIVE);
@ -35,4 +40,9 @@ public class LimitLimitHandler extends AbstractSimpleLimitHandler {
protected Pattern getForUpdatePattern() {
return FOR_UPDATE_PATTERN;
}
@Override
public boolean supportsOffset() {
return true;
}
}

View File

@ -15,14 +15,30 @@ package org.hibernate.dialect.pagination;
public class LimitOffsetLimitHandler extends AbstractSimpleLimitHandler {
public static LimitOffsetLimitHandler INSTANCE = new LimitOffsetLimitHandler();
public static LimitOffsetLimitHandler OFFSET_ONLY_INSTANCE = new LimitOffsetLimitHandler() {
@Override
protected String offsetOnlyClause() {
return " offset ?";
}
};
@Override
protected String limitClause(boolean hasFirstRow) {
return hasFirstRow ? " limit ? offset ?" : " limit ?";
}
@Override
protected String offsetOnlyClause() {
return " limit " + Integer.MAX_VALUE + " offset ?";
}
@Override
public final boolean bindLimitParametersInReverseOrder() {
return true;
}
@Override
public boolean supportsOffset() {
return true;
}
}

View File

@ -33,18 +33,23 @@ public class Oracle12LimitHandler extends AbstractLimitHandler {
@Override
public String processSql(String sql, Limit limit, QueryOptions queryOptions) {
final boolean hasMaxRows = hasMaxRows( limit );
if ( !hasMaxRows ) {
return sql;
}
return processSql(
sql,
hasFirstRow( limit ),
hasMaxRows( limit ),
queryOptions.getLockOptions()
);
}
/**
* @deprecated Use {@link #processSql(String, boolean, boolean, LockOptions)} instead
*/
@Deprecated(forRemoval = true)
protected String processSql(String sql, boolean hasFirstRow, LockOptions lockOptions) {
return processSql( sql, hasFirstRow, true, lockOptions );
}
protected String processSql(String sql, boolean hasFirstRow, boolean hasMaxRows, LockOptions lockOptions) {
if ( lockOptions != null ) {
final LockMode lockMode = lockOptions.getLockMode();
switch ( lockMode ) {
@ -53,42 +58,59 @@ public class Oracle12LimitHandler extends AbstractLimitHandler {
case UPGRADE_NOWAIT:
case PESSIMISTIC_FORCE_INCREMENT:
case UPGRADE_SKIPLOCKED: {
return processSql( sql, getForUpdateIndex( sql ), hasFirstRow );
return processSql( sql, getForUpdateIndex( sql ), hasFirstRow, hasMaxRows );
}
default: {
return processSqlOffsetFetch( sql, hasFirstRow );
return processSqlOffsetFetch( sql, hasFirstRow, hasMaxRows );
}
}
}
return processSqlOffsetFetch( sql, hasFirstRow );
return processSqlOffsetFetch( sql, hasFirstRow, hasMaxRows );
}
/**
* @deprecated Use {@link #processSqlOffsetFetch(String, boolean, boolean)} instead
*/
@Deprecated(forRemoval = true)
protected String processSqlOffsetFetch(String sql, boolean hasFirstRow) {
return processSqlOffsetFetch( sql, hasFirstRow, true );
}
protected String processSqlOffsetFetch(String sql, boolean hasFirstRow, boolean hasMaxRows) {
final int forUpdateLastIndex = getForUpdateIndex( sql );
if ( forUpdateLastIndex > -1 ) {
return processSql( sql, forUpdateLastIndex, hasFirstRow );
return processSql( sql, forUpdateLastIndex, hasFirstRow, hasMaxRows );
}
bindLimitParametersInReverseOrder = false;
useMaxForLimit = false;
supportOffset = true;
final int offsetFetchLength;
final String offsetFetchString;
if ( hasFirstRow ) {
if ( hasFirstRow && hasMaxRows ) {
offsetFetchString = " offset ? rows fetch next ? rows only";
}
else if ( hasFirstRow ) {
offsetFetchString = " offset ? rows";
}
else {
offsetFetchString = " fetch first ? rows only";
}
offsetFetchLength = sql.length() + offsetFetchString.length();
return sql + offsetFetchString;
}
/**
* @deprecated Use {@link #processSql(String, int, boolean, boolean)} instead
*/
@Deprecated(forRemoval = true)
protected String processSql(String sql, int forUpdateIndex, boolean hasFirstRow) {
return processSql( sql, forUpdateIndex, hasFirstRow, true );
}
protected String processSql(String sql, int forUpdateIndex, boolean hasFirstRow, boolean hasMaxRows) {
bindLimitParametersInReverseOrder = true;
useMaxForLimit = true;
supportOffset = false;
@ -112,12 +134,18 @@ public class Oracle12LimitHandler extends AbstractLimitHandler {
forUpdateClauseLength = forUpdateClause.length() + 1;
}
if ( hasFirstRow ) {
if ( hasFirstRow && hasMaxRows ) {
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 if ( hasFirstRow ) {
pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 98 );
pagingSelect.append( "select * from (" );
pagingSelect.append( sql );
pagingSelect.append( ") row_ where rownum>?" );
}
else {
pagingSelect = new StringBuilder( sql.length() + forUpdateClauseLength + 37 );
pagingSelect.append( "select * from (" );

View File

@ -8,19 +8,13 @@ package org.hibernate.orm.test.query;
import java.util.List;
import org.hibernate.dialect.AbstractHANADialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SpannerDialect;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -34,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
annotatedClasses = { NativeQueryLimitOffsetTest.Person.class }
)
@SessionFactory
@JiraKey("HHH-16020")
public class NativeQueryLimitOffsetTest {
@BeforeEach
@ -61,7 +56,6 @@ public class NativeQueryLimitOffsetTest {
}
@Test
@TestForIssue( jiraKey = "HHH-16020" )
public void testFullLimitOffsetOnNativeQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
@ -77,11 +71,6 @@ public class NativeQueryLimitOffsetTest {
}
@Test
@TestForIssue( jiraKey = "HHH-16020" )
@SkipForDialect( dialectClass = MySQLDialect.class, matchSubTypes = true)
@SkipForDialect( dialectClass = OracleDialect.class, majorVersion = 12, minorVersion = 2, matchSubTypes = true)
@SkipForDialect( dialectClass = AbstractHANADialect.class, matchSubTypes = true)
@SkipForDialect( dialectClass = SpannerDialect.class, matchSubTypes = true)
public void testPartialLimitOffsetOnNativeQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {