diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java
index aa30500db527..f5065b51e347 100644
--- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java
+++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenDialect.java
@@ -15,8 +15,11 @@
import org.hibernate.community.dialect.sequence.SequenceInformationExtractorTimesTenDatabaseImpl;
import org.hibernate.community.dialect.sequence.TimesTenSequenceSupport;
import org.hibernate.dialect.Dialect;
+import org.hibernate.dialect.BooleanDecoder;
import org.hibernate.dialect.RowLockStrategy;
import org.hibernate.dialect.function.CommonFunctionFactory;
+import org.hibernate.dialect.function.OracleTruncFunction;
+import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
import org.hibernate.dialect.lock.LockingStrategy;
import org.hibernate.dialect.lock.OptimisticForceIncrementLockingStrategy;
import org.hibernate.dialect.lock.OptimisticLockingStrategy;
@@ -34,6 +37,7 @@
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.Lockable;
+import org.hibernate.query.sqm.CastType;
import org.hibernate.query.sqm.IntervalType;
import org.hibernate.query.sqm.TemporalUnit;
import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy;
@@ -42,6 +46,7 @@
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
+import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
@@ -52,6 +57,15 @@
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration;
+import org.hibernate.type.BasicType;
+import org.hibernate.type.BasicTypeRegistry;
+import org.hibernate.type.StandardBasicTypes;
+import org.hibernate.dialect.function.StandardSQLFunction;
+import org.hibernate.dialect.function.CurrentFunction;
+import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
+import jakarta.persistence.GenerationType;
+import java.util.Date;
+
import jakarta.persistence.TemporalType;
import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION;
@@ -59,14 +73,13 @@
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
/**
- * A SQL dialect for TimesTen 5.1.
+ * A SQL dialect for Oracle TimesTen
*
* Known limitations:
* joined-subclass support because of no CASE support in TimesTen
* No support for subqueries that includes aggregation
* - size() in HQL not supported
* - user queries that does subqueries with aggregation
- * No CLOB/BLOB support
* No cascade delete support.
* No Calendar support
* No support for updating primary keys.
@@ -90,6 +103,7 @@ protected String columnType(int sqlTypeCode) {
// for the default Oracle type mode
// TypeMode=0
case SqlTypes.BOOLEAN:
+ case SqlTypes.BIT:
case SqlTypes.TINYINT:
return "tt_tinyint";
case SqlTypes.SMALLINT:
@@ -101,15 +115,26 @@ protected String columnType(int sqlTypeCode) {
//note that 'binary_float'/'binary_double' might
//be better mappings for Java Float/Double
+ case SqlTypes.VARCHAR:
+ case SqlTypes.LONGVARCHAR:
+ return "varchar2($l)";
+
+ case SqlTypes.LONGVARBINARY:
+ return "varbinary($l)";
+
//'numeric'/'decimal' are synonyms for 'number'
case SqlTypes.NUMERIC:
case SqlTypes.DECIMAL:
return "number($p,$s)";
+ case SqlTypes.FLOAT:
+ return "binary_float";
+ case SqlTypes.DOUBLE:
+ return "binary_double";
+
case SqlTypes.DATE:
return "tt_date";
case SqlTypes.TIME:
return "tt_time";
- //`timestamp` has more precision than `tt_timestamp`
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
return "timestamp($p)";
@@ -157,22 +182,97 @@ public int getDefaultDecimalPrecision() {
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
- CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions);
+ final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration();
+ CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions);
+ final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
+ final BasicType timestampType = basicTypeRegistry.resolve( StandardBasicTypes.TIMESTAMP );
+ final BasicType stringType = basicTypeRegistry.resolve( StandardBasicTypes.STRING );
+ final BasicType longType = basicTypeRegistry.resolve( StandardBasicTypes.LONG );
+ final BasicTypeintType = basicTypeRegistry.resolve( StandardBasicTypes.INTEGER );
+
+ // String Functions
functionFactory.trim2();
- functionFactory.soundex();
- functionFactory.trunc();
+ functionFactory.characterLength_length( SqlAstNodeRenderingMode.DEFAULT );
+ functionFactory.concat_pipeOperator();
functionFactory.toCharNumberDateTimestamp();
- functionFactory.ceiling_ceil();
+ functionFactory.char_chr();
functionFactory.instr();
functionFactory.substr();
functionFactory.substring_substr();
- functionFactory.leftRight_substr();
- functionFactory.char_chr();
- functionFactory.rownumRowid();
- functionFactory.sysdate();
+ functionFactory.soundex();
+
+ // Date/Time Functions
+ functionContributions.getFunctionRegistry().register(
+ "sysdate", new CurrentFunction("sysdate", "sysdate", timestampType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "getdate", new CurrentFunction("getdate", "getdate()", timestampType )
+ );
+
+ // Multi-param date dialect functions
functionFactory.addMonths();
functionFactory.monthsBetween();
+ // Math functions
+ functionFactory.ceiling_ceil();
+ functionFactory.radians_acos();
+ functionFactory.degrees_acos();
+ functionFactory.sinh();
+ functionFactory.tanh();
+ functionContributions.getFunctionRegistry().register(
+ "trunc",
+ new OracleTruncFunction( functionContributions.getTypeConfiguration() )
+ );
+ functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" );
+ functionFactory.round();
+
+ // Bitwise functions
+ functionContributions.getFunctionRegistry()
+ .patternDescriptorBuilder( "bitor", "(?1+?2-bitand(?1,?2))")
+ .setExactArgumentCount( 2 )
+ .setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers
+ .ARGUMENT_OR_IMPLIED_RESULT_TYPE )
+ .register();
+
+ functionContributions.getFunctionRegistry()
+ .patternDescriptorBuilder( "bitxor", "(?1+?2-2*bitand(?1,?2))")
+ .setExactArgumentCount( 2 )
+ .setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers
+ .ARGUMENT_OR_IMPLIED_RESULT_TYPE )
+ .register();
+
+ // Misc. functions
+ functionContributions.getFunctionRegistry().namedDescriptorBuilder( "nvl" )
+ .setMinArgumentCount( 2 )
+ .setArgumentTypeResolver( StandardFunctionArgumentTypeResolvers.ARGUMENT_OR_IMPLIED_RESULT_TYPE )
+ .setReturnTypeResolver( StandardFunctionReturnTypeResolvers.useFirstNonNull() )
+ .register();
+
+ functionContributions.getFunctionRegistry().register(
+ "user", new CurrentFunction("user", "user", stringType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "rowid", new CurrentFunction("rowid", "rowid", stringType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "uid", new CurrentFunction("uid", "uid", intType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "rownum", new CurrentFunction("rownum", "rownum", longType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "vsize", new StandardSQLFunction("vsize", StandardBasicTypes.DOUBLE)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "SESSION_USER", new CurrentFunction("SESSION_USER","SESSION_USER", stringType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "SYSTEM_USER", new CurrentFunction("SYSTEM_USER", "SYSTEM_USER", stringType)
+ );
+ functionContributions.getFunctionRegistry().register(
+ "CURRENT_USER", new CurrentFunction("CURRENT_USER","CURRENT_USER", stringType)
+ );
+
functionContributions.getFunctionRegistry().registerBinaryTernaryPattern(
"locate",
functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.INTEGER ),
@@ -251,9 +351,10 @@ public RowLockStrategy getWriteRowLockStrategy() {
return RowLockStrategy.COLUMN;
}
+
@Override
- public String getForUpdateString(String aliases) {
- return " for update of " + aliases;
+ public String getForUpdateString() {
+ return " for update";
}
@Override
@@ -426,4 +527,104 @@ public String getSelectClauseNullString(int sqlType, TypeConfiguration typeConfi
}
}
+ @Override
+ public String getNativeIdentifierGeneratorStrategy() {
+ return "sequence";
+ }
+
+ @Override
+ public String currentDate() {
+ return "sysdate";
+ }
+
+ @Override
+ public String currentTime() {
+ return "sysdate";
+ }
+
+ @Override
+ public String currentTimestamp() {
+ return "sysdate";
+ }
+
+ @Override
+ public int getMaxVarcharLength() {
+ // 1 to 4,194,304 bytes according to TimesTen Doc
+ return 4194304;
+ }
+
+ @Override
+ public int getMaxVarbinaryLength() {
+ // 1 to 4,194,304 bytes according to TimesTen Doc
+ return 4194304;
+ }
+
+ @Override
+ public boolean isEmptyStringTreatedAsNull() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsTupleDistinctCounts() {
+ return false;
+ }
+
+ @Override
+ public String getDual() {
+ return "dual";
+ }
+
+ @Override
+ public String getFromDualForSelectOnly() {
+ return " from dual";
+ }
+
+ @Override
+ public String castPattern(CastType from, CastType to) {
+ String result;
+ switch ( to ) {
+ case INTEGER:
+ case LONG:
+ result = BooleanDecoder.toInteger( from );
+ if ( result != null ) {
+ return result;
+ }
+ break;
+ case STRING:
+ switch ( from ) {
+ case BOOLEAN:
+ case INTEGER_BOOLEAN:
+ case TF_BOOLEAN:
+ case YN_BOOLEAN:
+ return BooleanDecoder.toString( from );
+ case DATE:
+ return "to_char(?1,'YYYY-MM-DD')";
+ case TIME:
+ return "to_char(?1,'HH24:MI:SS')";
+ case TIMESTAMP:
+ return "to_char(?1,'YYYY-MM-DD HH24:MI:SS.FF9')";
+ }
+ break;
+ case CLOB:
+ return "to_clob(?1)";
+ case DATE:
+ if ( from == CastType.STRING ) {
+ return "to_date(?1,'YYYY-MM-DD')";
+ }
+ break;
+ case TIME:
+ if ( from == CastType.STRING ) {
+ return "to_date(?1,'HH24:MI:SS')";
+ }
+ break;
+ case TIMESTAMP:
+ if ( from == CastType.STRING ) {
+ return "to_timestamp(?1,'YYYY-MM-DD HH24:MI:SS.FF9')";
+ }
+ break;
+ }
+ return super.castPattern(from, to);
+ }
+
+
}
diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenSqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenSqlAstTranslator.java
index b0eadebbfa06..b4ff40f70993 100644
--- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenSqlAstTranslator.java
+++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenSqlAstTranslator.java
@@ -28,6 +28,8 @@
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.exec.spi.JdbcOperation;
+import org.hibernate.internal.util.collections.Stack;
+import org.hibernate.sql.ast.Clause;
/**
* A SQL AST translator for TimesTen.
@@ -143,4 +145,66 @@ protected boolean supportsRowValueConstructorSyntaxInInList() {
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}
+
+ protected void renderRowsToClause(QuerySpec querySpec) {
+ if ( querySpec.isRoot() && hasLimit() ) {
+ prepareLimitOffsetParameters();
+ renderRowsToClause( getOffsetParameter(), getLimitParameter() );
+ }
+ else {
+ assertRowsOnlyFetchClauseType( querySpec );
+ renderRowsToClause( querySpec.getOffsetClauseExpression(), querySpec.getFetchClauseExpression() );
+ }
+ }
+
+ protected void renderRowsToClause(Expression offsetClauseExpression, Expression fetchClauseExpression) {
+ // offsetClauseExpression -> firstRow
+ // fetchClauseExpression -> maxRows
+ final Stack clauseStack = getClauseStack();
+
+ if ( offsetClauseExpression == null && fetchClauseExpression != null ) {
+ // We only have a maxRows/limit. We use 'SELECT FIRST n' syntax
+ appendSql("first ");
+ clauseStack.push( Clause.FETCH );
+ try {
+ renderFetchExpression( fetchClauseExpression );
+ }
+ finally {
+ clauseStack.pop();
+ }
+ }
+ else if ( offsetClauseExpression != null ) {
+ // We have an offset. We use 'SELECT ROWS m TO n' syntax
+ appendSql( "rows " );
+
+ // Render offset parameter
+ clauseStack.push( Clause.OFFSET );
+ try {
+ renderOffsetExpression( offsetClauseExpression );
+ }
+ finally {
+ clauseStack.pop();
+ }
+
+ appendSql( " to " );
+
+ // Render maxRows/limit parameter
+ clauseStack.push( Clause.FETCH );
+ try {
+ if ( fetchClauseExpression != null ) {
+ // We need to substract 1 row to fit maxRows
+ renderFetchPlusOffsetExpressionAsSingleParameter( fetchClauseExpression, offsetClauseExpression, -1 );
+ }
+ else{
+ // We dont have a maxRows param, we will just use a MAX_VALUE
+ appendSql( Integer.MAX_VALUE );
+ }
+ }
+ finally {
+ clauseStack.pop();
+ }
+ }
+
+ appendSql( WHITESPACE );
+ }
}
diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/TimesTenLimitHandler.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/TimesTenLimitHandler.java
index 4d95ef2af0df..9249360ac0ad 100644
--- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/TimesTenLimitHandler.java
+++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/TimesTenLimitHandler.java
@@ -7,22 +7,60 @@
package org.hibernate.community.dialect.pagination;
import org.hibernate.dialect.pagination.LimitHandler;
+import org.hibernate.dialect.pagination.LimitHandler;
+import org.hibernate.dialect.pagination.AbstractLimitHandler;
/**
* A {@link LimitHandler} for TimesTen, which uses {@code ROWS n},
* but at the start of the query instead of at the end.
*/
-public class TimesTenLimitHandler extends RowsLimitHandler {
+public class TimesTenLimitHandler extends AbstractLimitHandler {
public static final TimesTenLimitHandler INSTANCE = new TimesTenLimitHandler();
+ public TimesTenLimitHandler(){
+ }
+
+ @Override
+ public boolean supportsLimit() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsOffset() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsLimitOffset() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsVariableLimit() {
+ // a limit string using literals instead of parameters is
+ // required to translate from Hibernate's 0 based row numbers
+ // to TimesTen 1 based row numbers
+ return false;
+ }
+
@Override
- protected String insert(String rows, String sql) {
- return insertAfterSelect( rows, sql );
+ // TimesTen is 1 based
+ public int convertToFirstRowValue(int zeroBasedFirstResult) {
+ return zeroBasedFirstResult + 1;
+ }
+
+ @Override
+ public boolean useMaxForLimit() {
+ return true;
}
@Override
public boolean bindLimitParametersFirst() {
return true;
}
+
+ protected String limitClause(boolean hasFirstRow) {
+ return hasFirstRow ? " rows ? to ?" : " first ?";
+ }
}
diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/TimesTenSequenceSupport.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/TimesTenSequenceSupport.java
index 802aa1b5801d..72f73050436f 100644
--- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/TimesTenSequenceSupport.java
+++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/sequence/TimesTenSequenceSupport.java
@@ -6,7 +6,6 @@
*/
package org.hibernate.community.dialect.sequence;
-import org.hibernate.dialect.sequence.NextvalSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
/**
@@ -14,13 +13,44 @@
*
* @author Gavin King
*/
-public final class TimesTenSequenceSupport extends NextvalSequenceSupport {
+public final class TimesTenSequenceSupport implements SequenceSupport {
public static final SequenceSupport INSTANCE = new TimesTenSequenceSupport();
+
+
+ @Override
+ public boolean supportsSequences() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsPooledSequences() {
+ return true;
+ }
+
+ @Override
+ public String getSelectSequenceNextValString(String sequenceName) {
+ return sequenceName + ".nextval";
+ }
+
+ @Override
+ public String getSequenceNextValString(String sequenceName) {
+ return "select " + sequenceName + ".nextval from sys.dual";
+ }
+
@Override
public String getFromDual() {
return " from sys.dual";
}
+ @Override
+ public String getCreateSequenceString(String sequenceName) {
+ return "create sequence " + sequenceName;
+ }
+
+ @Override
+ public String getDropSequenceString(String sequenceName) {
+ return "drop sequence " + sequenceName;
+ }
}
diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FetchPlusOffsetParameterTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FetchPlusOffsetParameterTest.java
new file mode 100644
index 000000000000..786a84c20d99
--- /dev/null
+++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/FetchPlusOffsetParameterTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.community.dialect;
+
+import java.util.List;
+
+import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.dialect.H2Dialect;
+import org.hibernate.dialect.H2SqlAstTranslator;
+import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
+import org.hibernate.engine.spi.SessionFactoryImplementor;
+import org.hibernate.query.sqm.FetchClauseType;
+import org.hibernate.sql.ast.Clause;
+import org.hibernate.sql.ast.SqlAstTranslator;
+import org.hibernate.sql.ast.SqlAstTranslatorFactory;
+import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
+import org.hibernate.sql.ast.tree.Statement;
+import org.hibernate.sql.ast.tree.expression.Expression;
+import org.hibernate.sql.ast.tree.select.QueryPart;
+import org.hibernate.sql.exec.spi.JdbcOperation;
+
+import org.hibernate.testing.orm.junit.DomainModel;
+import org.hibernate.testing.orm.junit.Jira;
+import org.hibernate.testing.orm.junit.RequiresDialect;
+import org.hibernate.testing.orm.junit.ServiceRegistry;
+import org.hibernate.testing.orm.junit.SessionFactory;
+import org.hibernate.testing.orm.junit.SessionFactoryScope;
+import org.hibernate.testing.orm.junit.SettingProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@RequiresDialect(H2Dialect.class)
+@DomainModel(annotatedClasses = FetchPlusOffsetParameterTest.Book.class)
+@SessionFactory
+@ServiceRegistry(
+ settingProviders = @SettingProvider(settingName = AvailableSettings.DIALECT, provider = FetchPlusOffsetParameterTest.TestSettingProvider.class)
+)
+@Jira("https://hibernate.atlassian.net/browse/HHH-19888")
+public class FetchPlusOffsetParameterTest {
+
+ @BeforeEach
+ protected void prepareTest(SessionFactoryScope scope) {
+ scope.inTransaction(
+ (session) -> {
+ for ( int i = 1; i <= 3; i++ ) {
+ session.persist( new Book( i, "Book " + i ) );
+ }
+ }
+ );
+ }
+
+ @Test
+ public void testStaticOffset(SessionFactoryScope scope) {
+ scope.inTransaction(
+ (session) -> {
+ final List books = session.createSelectionQuery(
+ "from Book b order by b.id",
+ Book.class
+ )
+ .setFirstResult( 2 )
+ .setMaxResults( 1 ).getResultList();
+ // The custom dialect will fetch offset + limit + staticOffset rows
+ // Since staticOffset is -1, it must yield 2 rows
+ assertEquals( 2, books.size() );
+ }
+ );
+ }
+
+ @Entity(name = "Book")
+ public static class Book {
+ @Id
+ private Integer id;
+ private String title;
+
+ public Book() {
+ }
+
+ public Book(Integer id, String title) {
+ this.id = id;
+ this.title = title;
+ }
+ }
+
+
+ public static class TestSettingProvider implements SettingProvider.Provider {
+
+ @Override
+ public String getSetting() {
+ return TestDialect.class.getName();
+ }
+ }
+
+ public static class TestDialect extends H2Dialect {
+
+ public TestDialect(DialectResolutionInfo info) {
+ super( info );
+ }
+
+ public TestDialect() {
+ }
+
+ @Override
+ public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
+ return new StandardSqlAstTranslatorFactory() {
+ @Override
+ protected SqlAstTranslator buildTranslator(
+ SessionFactoryImplementor sessionFactory, Statement statement) {
+ return new H2SqlAstTranslator<>( sessionFactory, statement ) {
+ @Override
+ public void visitOffsetFetchClause(QueryPart queryPart) {
+ final Expression offsetClauseExpression;
+ final Expression fetchClauseExpression;
+ if ( queryPart.isRoot() && hasLimit() ) {
+ prepareLimitOffsetParameters();
+ offsetClauseExpression = getOffsetParameter();
+ fetchClauseExpression = getLimitParameter();
+ }
+ else {
+ assert queryPart.getFetchClauseType() == FetchClauseType.ROWS_ONLY;
+ offsetClauseExpression = queryPart.getOffsetClauseExpression();
+ fetchClauseExpression = queryPart.getFetchClauseExpression();
+ }
+ if ( offsetClauseExpression != null && fetchClauseExpression != null ) {
+ appendSql( " fetch first " );
+ getClauseStack().push( Clause.FETCH );
+ try {
+ renderFetchPlusOffsetExpressionAsSingleParameter(
+ fetchClauseExpression,
+ offsetClauseExpression,
+ -1
+ );
+ }
+ finally {
+ getClauseStack().pop();
+ }
+ appendSql( " rows only" );
+ }
+ }
+ };
+ }
+ };
+ }
+ }
+}
+
+
diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java
index fa0050b4d72a..26c9fc4665b2 100644
--- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java
+++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java
@@ -4685,43 +4685,6 @@ protected void renderTopStartAtClause(
}
}
- protected void renderRowsToClause(QuerySpec querySpec) {
- if ( querySpec.isRoot() && hasLimit() ) {
- prepareLimitOffsetParameters();
- renderRowsToClause( getOffsetParameter(), getLimitParameter() );
- }
- else {
- assertRowsOnlyFetchClauseType( querySpec );
- renderRowsToClause( querySpec.getOffsetClauseExpression(), querySpec.getFetchClauseExpression() );
- }
- }
-
- protected void renderRowsToClause(Expression offsetClauseExpression, Expression fetchClauseExpression) {
- if ( fetchClauseExpression != null ) {
- appendSql( "rows " );
- final Stack clauseStack = getClauseStack();
- clauseStack.push( Clause.FETCH );
- try {
- renderFetchExpression( fetchClauseExpression );
- }
- finally {
- clauseStack.pop();
- }
- if ( offsetClauseExpression != null ) {
- clauseStack.push( Clause.OFFSET );
- try {
- appendSql( " to " );
- // According to RowsLimitHandler this is 1 based so we need to add 1 to the offset
- renderFetchPlusOffsetExpression( fetchClauseExpression, offsetClauseExpression, 1 );
- }
- finally {
- clauseStack.pop();
- }
- }
- appendSql( WHITESPACE );
- }
- }
-
protected void renderFetchPlusOffsetExpression(
Expression fetchClauseExpression,
Expression offsetClauseExpression,
@@ -4781,38 +4744,23 @@ protected void renderFetchPlusOffsetExpressionAsSingleParameter(
appendSql( PARAM_MARKER );
final JdbcParameter offsetParameter = (JdbcParameter) offsetClauseExpression;
final JdbcParameter fetchParameter = (JdbcParameter) fetchClauseExpression;
- final OffsetReceivingParameterBinder fetchBinder = new OffsetReceivingParameterBinder(
+ final FetchPlusOffsetParameterBinder fetchBinder = new FetchPlusOffsetParameterBinder(
offsetParameter,
fetchParameter,
offset
);
- // We don't register and bind the special OffsetJdbcParameter as that comes from the query options
- // And in this case, we only want to bind a single JDBC parameter
- if ( !( offsetParameter instanceof OffsetJdbcParameter ) ) {
- jdbcParameters.addParameter( offsetParameter );
- parameterBinders.add(
- (statement, startPosition, jdbcParameterBindings, executionContext) -> {
- final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter );
- if ( binding == null ) {
- throw new ExecutionException( "JDBC parameter value not bound - " + offsetParameter );
- }
- fetchBinder.dynamicOffset = (Number) binding.getBindValue();
- }
- );
- }
jdbcParameters.addParameter( fetchParameter );
parameterBinders.add( fetchBinder );
}
}
- private static class OffsetReceivingParameterBinder implements JdbcParameterBinder {
+ private static class FetchPlusOffsetParameterBinder implements JdbcParameterBinder {
private final JdbcParameter offsetParameter;
private final JdbcParameter fetchParameter;
private final int staticOffset;
- private Number dynamicOffset;
- public OffsetReceivingParameterBinder(
+ public FetchPlusOffsetParameterBinder(
JdbcParameter offsetParameter,
JdbcParameter fetchParameter,
int staticOffset) {
@@ -4843,13 +4791,16 @@ public void bindParameterValue(
offsetValue = executionContext.getQueryOptions().getEffectiveLimit().getFirstRow();
}
else {
- offsetValue = dynamicOffset.intValue() + staticOffset;
- dynamicOffset = null;
+ final JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter );
+ if ( binding == null ) {
+ throw new ExecutionException( "JDBC parameter value not bound - " + offsetParameter );
+ }
+ offsetValue = ((Number) binding.getBindValue()).intValue();
}
//noinspection unchecked
fetchParameter.getExpressionType().getSingleJdbcMapping().getJdbcValueBinder().bind(
statement,
- bindValue.intValue() + offsetValue,
+ bindValue.intValue() + offsetValue + staticOffset,
startPosition,
executionContext.getSession()
);