From 652aa43427810d1a5319ccb0b08a1582c28a888b Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 20 Feb 2017 11:11:03 -0500 Subject: [PATCH] HHH-11503 - Fix SQL Server 2012 offset/fetch parameter binding. --- .../dialect/SQLServer2012Dialect.java | 2 +- .../pagination/SQLServer2012LimitHandler.java | 72 +++++++++++++++++-- .../dialect/SQLServer2012DialectTestCase.java | 4 +- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java index 9b12df37d6..64a303e3d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java @@ -89,6 +89,6 @@ public class SQLServer2012Dialect extends SQLServer2008Dialect { @Override protected LimitHandler getDefaultLimitHandler() { - return SQLServer2012LimitHandler.INSTANCE; + return new SQLServer2012LimitHandler(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java index 5a6bafd51d..a7d3411f17 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SQLServer2012LimitHandler.java @@ -6,6 +6,9 @@ */ package org.hibernate.dialect.pagination; +import java.sql.PreparedStatement; +import java.sql.SQLException; + import org.hibernate.engine.spi.RowSelection; /** @@ -14,9 +17,10 @@ import org.hibernate.engine.spi.RowSelection; * @author Chris Cranford */ public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler { - public static final SQLServer2012LimitHandler INSTANCE = new SQLServer2012LimitHandler(); + // determines whether the limit handler used offset/fetch or 2005 behavior. + private boolean usedOffsetFetch; - private SQLServer2012LimitHandler() { + public SQLServer2012LimitHandler() { } @@ -27,7 +31,7 @@ public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler { @Override public boolean supportsVariableLimit() { - return false; + return true; } @Override @@ -40,12 +44,70 @@ public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler { if ( !LimitHelper.useLimit( this, selection ) ) { return sql; } - int firstRow = LimitHelper.hasFirstRow( selection ) ? selection.getFirstRow() : 0; - return sql + String.format( " offset %d rows fetch next %d rows only", firstRow, selection.getMaxRows() ); + return applyOffsetFetch( selection, sql, getInsertPosition( sql ) ); } return super.processSql( sql, selection ); } + @Override + public boolean useMaxForLimit() { + // when using the offset fetch clause, the max value is passed as-is. + // SQLServer2005LimitHandler uses start + max values. + return usedOffsetFetch ? false : super.useMaxForLimit(); + } + + @Override + public int convertToFirstRowValue(int zeroBasedFirstResult) { + // When using the offset/fetch clause, the first row is passed as-is + // SQLServer2005LimitHandler uses zeroBasedFirstResult + 1 + if ( usedOffsetFetch ) { + return zeroBasedFirstResult; + } + return super.convertToFirstRowValue( zeroBasedFirstResult ); + } + + @Override + public int bindLimitParametersAtEndOfQuery(RowSelection selection, PreparedStatement statement, int index) + throws SQLException { + if ( usedOffsetFetch && !LimitHelper.hasFirstRow( selection ) ) { + // apply just the max value when offset fetch applied + statement.setInt( index, getMaxOrLimit( selection ) ); + return 1; + } + return super.bindLimitParametersAtEndOfQuery( selection, statement, index ); + } + + private String getOffsetFetch(RowSelection selection) { + if ( !LimitHelper.hasFirstRow( selection ) ) { + return " offset 0 rows fetch next ? rows only"; + } + return " offset ? rows fetch next ? rows only"; + } + + private int getInsertPosition(String sql) { + int position = sql.length() - 1; + for ( ; position > 0; --position ) { + char ch = sql.charAt( position ); + if ( ch != ';' && ch != ' ' && ch != '\r' && ch != '\n' ) { + break; + } + } + return position + 1; + } + + private String applyOffsetFetch(RowSelection selection, String sql, int position) { + usedOffsetFetch = true; + + StringBuilder sb = new StringBuilder(); + sb.append( sql.substring( 0, position ) ); + sb.append( getOffsetFetch( selection ) ); + if ( position > sql.length() ) { + sb.append( sql.substring( position - 1 ) ); + } + + return sb.toString(); + } + private boolean hasOrderBy(String sql) { int depth = 0; for ( int i = 0; i < sql.length(); ++i ) { diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java index 592d1c3f8f..468daf591f 100644 --- a/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SQLServer2012DialectTestCase.java @@ -41,7 +41,7 @@ public class SQLServer2012DialectTestCase extends BaseUnitTestCase { public void testGetLimitStringMaxRowsOnly() { final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc"; assertEquals( - input + " offset 0 rows fetch next 10 rows only", + input + " offset 0 rows fetch next ? rows only", dialect.getLimitHandler().processSql( input, toRowSelection( 0, 10 ) ).toLowerCase( Locale.ROOT ) ); } @@ -51,7 +51,7 @@ public class SQLServer2012DialectTestCase extends BaseUnitTestCase { public void testGetLimitStringWithOffsetAndMaxRows() { final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc"; assertEquals( - input + " offset 5 rows fetch next 25 rows only", + input + " offset ? rows fetch next ? rows only", dialect.getLimitHandler().processSql( input, toRowSelection( 5, 25 ) ).toLowerCase( Locale.ROOT ) ); }