HHH-11194 - Add setting to allow enabling legacy 4.x LimitHandler behavior (removed delegation).

Fix broken test on SQL Server and propagate the legacy behavior even when we extend the SQL Server of the Ingres base Dialects
This commit is contained in:
Chris Cranford 2016-11-23 13:06:37 -05:00 committed by Vlad Mihalcea
parent 9219c67da6
commit aa3f913857
14 changed files with 277 additions and 36 deletions

View File

@ -343,6 +343,12 @@ This configuration property is used by the https://docs.jboss.org/hibernate/orm/
It follows a pattern similar to the ANSI SQL definition of global temporary table using a "session id" column to segment rows from the various sessions.
This configuration property defines the database catalog used for storing the temporary tables used for bulk HQL operations.
|`hibernate.legacy_limit_handler` | `true` or `false` (default value) |
Setting which indicates whether or not to use `org.hibernate.dialect.pagination.LimitHandler`
implementations that sacrifices performance optimizations to allow legacy 4.x limit behavior.
Legacy 4.x behavior favored performing pagination in-memory by avoiding the use of the offset value, which is overall poor performance.
In 5.x, the limit handler behavior favors performance, thus, if the dialect doesn't support offsets, an exception is thrown instead.
|===================================================================================================================================================================================================================================
[[configurations-batch]]

View File

@ -1604,8 +1604,17 @@ public interface AvailableSettings {
*/
String MERGE_ENTITY_COPY_OBSERVER = "hibernate.event.merge.entity_copy_observer";
/**
* Setting which indicates whether or not to use {@link org.hibernate.dialect.pagination.LimitHandler}
* implementations that sacrifices performance optimizations to allow legacy 4.x limit behavior.
* </p>
* Legacy 4.x behavior favored performing pagination in-memory by avoiding the use of the offset
* value, which is overall poor performance. In 5.x, the limit handler behavior favors performance
* thus if the dialect doesn't support offsets, an exception is thrown instead.
* </p>
* Default is {@code false}.
*
* @since 5.2.5
*/
String USE_LEGACY_LIMIT_HANDLERS = "hibernate.legacy_limit_handler";
}

View File

@ -198,7 +198,7 @@ import org.hibernate.type.StandardBasicTypes;
public class Cache71Dialect extends Dialect {
private final TopLimitHandler limitHandler;
private LimitHandler limitHandler;
/**
* Creates new <code>Cache71Dialect</code> instance. Sets up the JDBC /
@ -208,7 +208,7 @@ public class Cache71Dialect extends Dialect {
super();
commonRegistration();
register71Functions();
this.limitHandler = new TopLimitHandler(true, true);
this.limitHandler = new TopLimitHandler( true, true );
}
protected final void commonRegistration() {
@ -544,6 +544,9 @@ public class Cache71Dialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return super.getLimitHandler();
}
return limitHandler;
}

View File

@ -47,6 +47,28 @@ public class DB2390Dialect extends DB2Dialect {
}
};
private static final AbstractLimitHandler LEGACY_LIMIT_HANDLER = new AbstractLimitHandler() {
@Override
public String processSql(String sql, RowSelection selection) {
return sql + " fetch first " + getMaxOrLimit( selection ) + " rows only";
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean useMaxForLimit() {
return true;
}
@Override
public boolean supportsVariableLimit() {
return false;
}
};
@Override
public boolean supportsSequences() {
return false;
@ -86,12 +108,16 @@ public class DB2390Dialect extends DB2Dialect {
@Override
public LimitHandler getLimitHandler() {
return LIMIT_HANDLER;
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return LEGACY_LIMIT_HANDLER;
}
else {
return LIMIT_HANDLER;
}
}
@Override
public IdentityColumnSupport getIdentityColumnSupport() {
return new DB2390IdentityColumnSupport();
}
}

View File

@ -34,6 +34,7 @@ import org.hibernate.ScrollMode;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CastFunction;
import org.hibernate.dialect.function.SQLFunction;
@ -53,6 +54,8 @@ import org.hibernate.dialect.pagination.LegacyLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.unique.DefaultUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.LobCreator;
import org.hibernate.engine.jdbc.env.internal.DefaultSchemaNameResolver;
import org.hibernate.engine.jdbc.env.spi.AnsiSqlKeywords;
@ -146,6 +149,7 @@ public abstract class Dialect implements ConversionContext {
private final UniqueDelegate uniqueDelegate;
private boolean legacyLimitHandlerBehavior;
// constructors and factory methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -307,7 +311,7 @@ public abstract class Dialect implements ConversionContext {
* @param serviceRegistry The service registry
*/
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
// by default, nothing to do
resolveLegacyLimitHandlerBehavior( serviceRegistry );
}
/**
@ -2859,4 +2863,19 @@ public abstract class Dialect implements ConversionContext {
public boolean supportsNationalizedTypes() {
return true;
}
public boolean isLegacyLimitHandlerBehaviorEnabled() {
return legacyLimitHandlerBehavior;
}
private void resolveLegacyLimitHandlerBehavior(ServiceRegistry serviceRegistry) {
// HHH-11194
// Temporary solution to set whether legacy limit handler behavior should be used.
final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class );
legacyLimitHandlerBehavior = configurationService.getSetting(
AvailableSettings.USE_LEGACY_LIMIT_HANDLERS,
StandardConverters.BOOLEAN,
false
);
}
}

View File

@ -17,6 +17,7 @@ import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.InformixIdentityColumnSupport;
import org.hibernate.dialect.pagination.FirstLimitHandler;
import org.hibernate.dialect.pagination.LegacyFirstLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.unique.InformixUniqueDelegate;
import org.hibernate.dialect.unique.UniqueDelegate;
@ -178,6 +179,9 @@ public class InformixDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return LegacyFirstLimitHandler.INSTANCE;
}
return FirstLimitHandler.INSTANCE;
}

View File

@ -14,6 +14,7 @@ import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.dialect.pagination.FirstLimitHandler;
import org.hibernate.dialect.pagination.LegacyFirstLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.hql.spi.id.IdTableSupportStandardImpl;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
@ -47,6 +48,7 @@ import org.hibernate.type.StandardBasicTypes;
*/
@SuppressWarnings("deprecation")
public class IngresDialect extends Dialect {
/**
* Constructs a IngresDialect
*/
@ -220,6 +222,9 @@ public class IngresDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return LegacyFirstLimitHandler.INSTANCE;
}
return FirstLimitHandler.INSTANCE;
}

View File

@ -77,6 +77,28 @@ public class RDMSOS2200Dialect extends Dialect {
}
};
private static final AbstractLimitHandler LEGACY_LIMIT_HANDLER = new AbstractLimitHandler() {
@Override
public String processSql(String sql, RowSelection selection) {
return sql + " fetch first " + getMaxOrLimit( selection ) + " rows only ";
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean supportsLimitOffset() {
return false;
}
@Override
public boolean supportsVariableLimit() {
return false;
}
};
/**
* Constructs a RDMSOS2200Dialect
*/
@ -343,6 +365,9 @@ public class RDMSOS2200Dialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return LEGACY_LIMIT_HANDLER;
}
return LIMIT_HANDLER;
}

View File

@ -16,6 +16,7 @@ import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.SQLServerIdentityColumnSupport;
import org.hibernate.dialect.pagination.LegacyLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.TopLimitHandler;
import org.hibernate.type.StandardBasicTypes;
@ -84,6 +85,9 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
@Override
public LimitHandler getLimitHandler() {
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return new LegacyLimitHandler( this );
}
return limitHandler;
}

View File

@ -21,6 +21,7 @@ import org.hibernate.dialect.lock.PessimisticWriteUpdateLockingStrategy;
import org.hibernate.dialect.lock.SelectLockingStrategy;
import org.hibernate.dialect.lock.UpdateLockingStrategy;
import org.hibernate.dialect.pagination.FirstLimitHandler;
import org.hibernate.dialect.pagination.LegacyFirstLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.hql.spi.id.IdTableSupportStandardImpl;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
@ -84,7 +85,6 @@ public class TimesTenDialect extends Dialect {
registerFunction( "sysdate", new NoArgSQLFunction( "sysdate", StandardBasicTypes.TIMESTAMP, false ) );
registerFunction( "getdate", new NoArgSQLFunction( "getdate", StandardBasicTypes.TIMESTAMP, false ) );
registerFunction( "nvl", new StandardSQLFunction( "nvl" ) );
}
@Override
@ -159,6 +159,9 @@ public class TimesTenDialect extends Dialect {
@Override
public LimitHandler getLimitHandler() {
if ( isLegacyLimitHandlerBehaviorEnabled() ) {
return LegacyFirstLimitHandler.INSTANCE;
}
return FirstLimitHandler.INSTANCE;
}

View File

@ -6,14 +6,13 @@
*/
package org.hibernate.dialect.pagination;
import java.util.Locale;
import org.hibernate.engine.spi.RowSelection;
/**
* @author Brett Meyer
*/
public class FirstLimitHandler extends AbstractLimitHandler {
public class FirstLimitHandler extends LegacyFirstLimitHandler {
public static final FirstLimitHandler INSTANCE = new FirstLimitHandler();
@ -27,29 +26,6 @@ public class FirstLimitHandler extends AbstractLimitHandler {
if ( hasOffset ) {
throw new UnsupportedOperationException( "query result offset is not supported" );
}
return new StringBuilder( sql.length() + 16 )
.append( sql )
.insert( sql.toLowerCase(Locale.ROOT).indexOf( "select" ) + 6, " first " + getMaxOrLimit( selection ) )
.toString();
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean useMaxForLimit() {
return true;
}
@Override
public boolean supportsLimitOffset() {
return false;
}
@Override
public boolean supportsVariableLimit() {
return false;
return super.processSql( sql, selection );
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.engine.spi.RowSelection;
/**
* @author Chris Cranford
*/
public class LegacyFirstLimitHandler extends AbstractLimitHandler {
public static final LegacyFirstLimitHandler INSTANCE = new LegacyFirstLimitHandler();
LegacyFirstLimitHandler() {
// NOP
}
@Override
public String processSql(String sql, RowSelection selection) {
return new StringBuilder( sql.length() + 16 )
.append( sql )
.insert( sql.toLowerCase( Locale.ROOT).indexOf( "select" ) + 6, " first " + getMaxOrLimit( selection ) )
.toString();
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean useMaxForLimit() {
return true;
}
@Override
public boolean supportsLimitOffset() {
return false;
}
@Override
public boolean supportsVariableLimit() {
return false;
}
}

View File

@ -0,0 +1,87 @@
/*
* 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;
import java.util.Map;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.RequiresDialects;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Chris Cranford
*/
public class LegacyLimitHandlerTestCase extends
BaseNonConfigCoreFunctionalTestCase {
private final String TEST_SQL = "SELECT field FROM table";
@Override
protected void addSettings(Map settings) {
settings.put( AvailableSettings.USE_LEGACY_LIMIT_HANDLERS, "true" );
}
@Test
@TestForIssue(jiraKey = "HHH-11194")
@RequiresDialect(Cache71Dialect.class)
public void testCache71DialectLegacyLimitHandler() {
assertLimitHandlerEquals( "SELECT TOP ? field FROM table" );
}
@Test
@TestForIssue(jiraKey = "HHH-11194")
@RequiresDialect(DB2390Dialect.class)
public void testDB2390DialectLegacyLimitHandler() {
assertLimitHandlerEquals( "SELECT field FROM table fetch first 6 rows only" );
}
@Test
@TestForIssue(jiraKey = "HHH-11194")
@RequiresDialects({ @RequiresDialect(InformixDialect.class), @RequiresDialect(IngresDialect.class)})
public void testInformixDialectOrIngresDialectLegacyLimitHandler() {
assertLimitHandlerEquals( "SELECT first 6 field FROM table" );
}
@Test
@TestForIssue(jiraKey = "HHH-11194")
@RequiresDialect(RDMSOS2200Dialect.class)
public void testRDMSOS2200DialectLegacyLimitHandler() {
assertLimitHandlerEquals( "SELECT field FROM table fetch first 5 rows only" );
}
@Test
@TestForIssue(jiraKey = "HHH-11194")
@RequiresDialect(value = SQLServerDialect.class, strictMatching = true)
public void testSQLServerDialectLegacyLimitHandler() {
assertLimitHandlerEquals( "SELECT top 6 field FROM table" );
}
@Test
@TestForIssue(jiraKey = "HHH-11194")
@RequiresDialect(TimesTenDialect.class)
public void testTimesTenDialectLegacyLimitHandler() {
assertLimitHandlerEquals( "SELECT first 6 field FROM table" );
}
private void assertLimitHandlerEquals(String sql) {
assertEquals( sql, getDialect().getLimitHandler().processSql( TEST_SQL, toRowSelection( 1, 5 ) ) );
}
private RowSelection toRowSelection(int firstRow, int maxRows) {
RowSelection selection = new RowSelection();
selection.setFirstRow( firstRow );
selection.setMaxRows( maxRows );
return selection;
}
}

View File

@ -60,6 +60,29 @@ implement JPA methods now in core I decided to implement more of a composition a
has been moved into the `org.hibernate.Cache` and `org.hibernate.engine.spi.CacheImplementor` contracts
helping implement JPA's `javax.persistence.Cache` role.
== LimitHandler changes
In Hibernate 4.3, dialect implementations that did not support a limit offset would fetch all rows for a query and
perform pagination in-memory. This solution, while functional, could have severe performance penalties. In 5.x,
we prefered to favor performance optimizations which meant dialect implementations would throw an exception if a
limit offset was specified but the dialect didn't support such syntax.
As of 5.2.5.Final, we have introduced a new setting, `hibernate.legacy_limit_handler`, that is designed to allow
users to enable the legacy 4.3 limit handler behavior. By default, this setting is _false_.
The specific dialects impacted by this change are restricted to the following.
* Cache71Dialect
* DB2390Dialect
* InformixDialect
* IngresDialect
* RDMSOS2200Dialect
* SQLServerDialect
* TimesTenDialect
NOTE: If a dialect that extends any in the above list but overrides the limit handler implementation, then those
dialects remain unchanged, e.g. SQLServer2005Dialect.
== Misc