From 1e70c51b5656e86cc826ade8c130cd43b6b78978 Mon Sep 17 00:00:00 2001 From: Mark Rotteveel Date: Sat, 1 Jun 2024 12:22:57 +0200 Subject: [PATCH] HHH-18213 Fix LimitHandler for Firebird 2.5 and older --- .../community/dialect/FirebirdDialect.java | 5 +- .../pagination/FirstSkipLimitHandler.java | 62 +++++++++++++++++++ .../dialect/FirebirdDialectTest.java | 37 +++++++++++ 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/FirstSkipLimitHandler.java create mode 100644 hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FirebirdDialectTest.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java index 6e8899581f..6f47f288fa 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/FirebirdDialect.java @@ -28,7 +28,7 @@ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.community.dialect.identity.FirebirdIdentityColumnSupport; -import org.hibernate.community.dialect.pagination.SkipFirstLimitHandler; +import org.hibernate.community.dialect.pagination.FirstSkipLimitHandler; import org.hibernate.community.dialect.sequence.FirebirdSequenceSupport; import org.hibernate.community.dialect.sequence.InterbaseSequenceSupport; import org.hibernate.community.dialect.sequence.SequenceInformationExtractorFirebirdDatabaseImpl; @@ -36,7 +36,6 @@ import org.hibernate.dialect.BooleanDecoder; import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.NationalizationSupport; -import org.hibernate.dialect.SimpleDatabaseVersion; import org.hibernate.dialect.TimeZoneSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.identity.IdentityColumnSupport; @@ -674,7 +673,7 @@ public class FirebirdDialect extends Dialect { @Override public LimitHandler getLimitHandler() { return getVersion().isBefore( 3, 0 ) - ? SkipFirstLimitHandler.INSTANCE + ? FirstSkipLimitHandler.INSTANCE : OffsetFetchLimitHandler.INSTANCE; } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/FirstSkipLimitHandler.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/FirstSkipLimitHandler.java new file mode 100644 index 0000000000..9fd45c98d2 --- /dev/null +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/FirstSkipLimitHandler.java @@ -0,0 +1,62 @@ +/* + * 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.community.dialect.pagination; + +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.query.spi.Limit; + +/** + * A {@link LimitHandler} for Firebird 2.5 and older which supports the syntax + * {@code FIRST n SKIP m}. + */ +public class FirstSkipLimitHandler extends AbstractLimitHandler { + + public static final FirstSkipLimitHandler INSTANCE = new FirstSkipLimitHandler(); + + @Override + public String processSql(String sql, Limit limit) { + boolean hasFirstRow = hasFirstRow( limit ); + boolean hasMaxRows = hasMaxRows( limit ); + + if ( !hasFirstRow && !hasMaxRows ) { + return sql; + } + + StringBuilder skipFirst = new StringBuilder(); + + if ( hasMaxRows ) { + skipFirst.append( " first ?" ); + } + if ( hasFirstRow ) { + skipFirst.append( " skip ?" ); + } + + return insertAfterSelect( skipFirst.toString(), sql ); + } + + @Override + public final boolean supportsLimit() { + return true; + } + + @Override + public boolean supportsOffset() { + return true; + } + + @Override + public boolean bindLimitParametersInReverseOrder() { + return true; + } + + @Override + public final boolean bindLimitParametersFirst() { + return true; + } + +} diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FirebirdDialectTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FirebirdDialectTest.java new file mode 100644 index 0000000000..54855d3b92 --- /dev/null +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FirebirdDialectTest.java @@ -0,0 +1,37 @@ +/* + * 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.community.dialect; + +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.query.spi.Limit; + +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class FirebirdDialectTest { + + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, value = { + "major, minor, offset, limit, expectedSQL", + "2, 5, 0, 10, select first ? * from tablename t where t.cat = 5", + "2, 5, 10, 0, select skip ? * from tablename t where t.cat = 5", + "2, 5, 5, 10, select first ? skip ? * from tablename t where t.cat = 5", + "3, 0, 0, 10, select * from tablename t where t.cat = 5 fetch first ? rows only", + "3, 0, 10, 0, select * from tablename t where t.cat = 5 offset ? rows", + "3, 0, 5, 10, select * from tablename t where t.cat = 5 offset ? rows fetch next ? rows only" + }) + @JiraKey( "HHH-18213" ) + void insertOffsetLimitClause(int major, int minor, int offset, int limit, String expectedSql) { + String input = "select * from tablename t where t.cat = 5"; + FirebirdDialect dialect = new FirebirdDialect( DatabaseVersion.make( major, minor ) ); + String actual = dialect.getLimitHandler().processSql( input, new Limit( offset, limit ) ); + assertEquals( expectedSql, actual ); + } +}