HHH-11503 - Fix SQL Server 2012 offset/fetch parameter binding.

This commit is contained in:
Chris Cranford 2017-02-20 11:11:03 -05:00
parent 2b79644b63
commit 652aa43427
3 changed files with 70 additions and 8 deletions

View File

@ -89,6 +89,6 @@ public class SQLServer2012Dialect extends SQLServer2008Dialect {
@Override @Override
protected LimitHandler getDefaultLimitHandler() { protected LimitHandler getDefaultLimitHandler() {
return SQLServer2012LimitHandler.INSTANCE; return new SQLServer2012LimitHandler();
} }
} }

View File

@ -6,6 +6,9 @@
*/ */
package org.hibernate.dialect.pagination; package org.hibernate.dialect.pagination;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.hibernate.engine.spi.RowSelection; import org.hibernate.engine.spi.RowSelection;
/** /**
@ -14,9 +17,10 @@ import org.hibernate.engine.spi.RowSelection;
* @author Chris Cranford * @author Chris Cranford
*/ */
public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler { 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 @Override
public boolean supportsVariableLimit() { public boolean supportsVariableLimit() {
return false; return true;
} }
@Override @Override
@ -40,12 +44,70 @@ public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler {
if ( !LimitHelper.useLimit( this, selection ) ) { if ( !LimitHelper.useLimit( this, selection ) ) {
return sql; return sql;
} }
int firstRow = LimitHelper.hasFirstRow( selection ) ? selection.getFirstRow() : 0; return applyOffsetFetch( selection, sql, getInsertPosition( sql ) );
return sql + String.format( " offset %d rows fetch next %d rows only", firstRow, selection.getMaxRows() );
} }
return super.processSql( sql, selection ); 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) { private boolean hasOrderBy(String sql) {
int depth = 0; int depth = 0;
for ( int i = 0; i < sql.length(); ++i ) { for ( int i = 0; i < sql.length(); ++i ) {

View File

@ -41,7 +41,7 @@ public class SQLServer2012DialectTestCase extends BaseUnitTestCase {
public void testGetLimitStringMaxRowsOnly() { public void testGetLimitStringMaxRowsOnly() {
final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc"; final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc";
assertEquals( 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 ) dialect.getLimitHandler().processSql( input, toRowSelection( 0, 10 ) ).toLowerCase( Locale.ROOT )
); );
} }
@ -51,7 +51,7 @@ public class SQLServer2012DialectTestCase extends BaseUnitTestCase {
public void testGetLimitStringWithOffsetAndMaxRows() { public void testGetLimitStringWithOffsetAndMaxRows() {
final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc"; final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc";
assertEquals( 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 ) dialect.getLimitHandler().processSql( input, toRowSelection( 5, 25 ) ).toLowerCase( Locale.ROOT )
); );
} }