Skip to content

Commit 457a181

Browse files
Narosvladmihalcea
authored andcommitted
HHH-8768 - Add support for OFFSET/FETCH syntax for SQL Server 2012 LimitHandler/pagination.
1 parent 68e1dff commit 457a181

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/SQLServer2012Dialect.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import java.util.List;
1010

11+
import org.hibernate.dialect.pagination.LimitHandler;
12+
import org.hibernate.dialect.pagination.SQLServer2012LimitHandler;
1113
import org.hibernate.internal.util.StringHelper;
1214

1315
/**
@@ -79,4 +81,14 @@ public String getQueryHintString(String sql, List<String> hints) {
7981

8082
return sql;
8183
}
84+
85+
@Override
86+
public boolean supportsLimitOffset() {
87+
return true;
88+
}
89+
90+
@Override
91+
protected LimitHandler getDefaultLimitHandler() {
92+
return SQLServer2012LimitHandler.INSTANCE;
93+
}
8294
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.dialect.pagination;
8+
9+
import org.hibernate.engine.spi.RowSelection;
10+
11+
/**
12+
* LIMIT clause handler compatible with SQL Server 2012 and later.
13+
*
14+
* @author Chris Cranford
15+
*/
16+
public class SQLServer2012LimitHandler extends SQLServer2005LimitHandler {
17+
public static final SQLServer2012LimitHandler INSTANCE = new SQLServer2012LimitHandler();
18+
19+
private SQLServer2012LimitHandler() {
20+
21+
}
22+
23+
@Override
24+
public boolean supportsLimit() {
25+
return true;
26+
}
27+
28+
@Override
29+
public boolean supportsVariableLimit() {
30+
return false;
31+
}
32+
33+
@Override
34+
public String processSql(String sql, RowSelection selection) {
35+
// SQLServer mandates the following rules to use OFFSET/LIMIT
36+
// * An 'ORDER BY' is required
37+
// * The 'OFFSET ...' clause is mandatory, cannot use 'FETCH ...' by itself.
38+
// * The 'TOP' clause isn't permitted with LIMIT/OFFSET.
39+
if ( hasOrderBy( sql ) ) {
40+
if ( !LimitHelper.useLimit( this, selection ) ) {
41+
return sql;
42+
}
43+
int firstRow = LimitHelper.hasFirstRow( selection ) ? selection.getFirstRow() : 0;
44+
return sql + String.format( " offset %d rows fetch next %d rows only", firstRow, selection.getMaxRows() );
45+
}
46+
return super.processSql( sql, selection );
47+
}
48+
49+
private boolean hasOrderBy(String sql) {
50+
int depth = 0;
51+
for ( int i = 0; i < sql.length(); ++i ) {
52+
char ch = sql.charAt( i );
53+
if ( ch == '(' ) {
54+
depth++;
55+
}
56+
else if ( ch == ')' ) {
57+
depth--;
58+
}
59+
if ( depth == 0 ) {
60+
if ( sql.substring( i ).toLowerCase().startsWith( "order by " ) ) {
61+
return true;
62+
}
63+
}
64+
}
65+
return false;
66+
}
67+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.dialect;
8+
9+
import java.util.Locale;
10+
11+
import org.hibernate.engine.spi.RowSelection;
12+
import org.junit.After;
13+
import org.junit.Before;
14+
import org.junit.Test;
15+
16+
import org.hibernate.testing.TestForIssue;
17+
import org.hibernate.testing.junit4.BaseUnitTestCase;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
/**
22+
* Tests the Limit/Offset handler for {@link SQLServer2012Dialect}.
23+
*
24+
* @author Chris Cranford
25+
*/
26+
public class SQLServer2012DialectTestCase extends BaseUnitTestCase {
27+
private SQLServer2012Dialect dialect;
28+
29+
@Before
30+
public void setup() {
31+
dialect = new SQLServer2012Dialect();
32+
}
33+
34+
@After
35+
public void tearDown() {
36+
dialect = null;
37+
}
38+
39+
@Test
40+
@TestForIssue(jiraKey = "HHH-8768")
41+
public void testGetLimitStringMaxRowsOnly() {
42+
final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc";
43+
assertEquals(
44+
input + " offset 0 rows fetch next 10 rows only",
45+
dialect.getLimitHandler().processSql( input, toRowSelection( 0, 10 ) ).toLowerCase( Locale.ROOT )
46+
);
47+
}
48+
49+
@Test
50+
@TestForIssue(jiraKey = "HHH-8768")
51+
public void testGetLimitStringWithOffsetAndMaxRows() {
52+
final String input = "select distinct f1 as f53245 from table846752 order by f234, f67 desc";
53+
assertEquals(
54+
input + " offset 5 rows fetch next 25 rows only",
55+
dialect.getLimitHandler().processSql( input, toRowSelection( 5, 25 ) ).toLowerCase( Locale.ROOT )
56+
);
57+
}
58+
59+
@Test
60+
@TestForIssue(jiraKey = "HHH-8768")
61+
public void testGetLimitStringMaxRowsOnlyNoOrderBy() {
62+
// this test defaults back to validating result matches that from SQLServer2005LimitHandler
63+
// See SQLServer2012LimitHandler for why this falls back
64+
final String input = "select f1 from table";
65+
assertEquals(
66+
"select top(?) f1 from table",
67+
dialect.getLimitHandler().processSql( input, toRowSelection( 0, 10 ) ).toLowerCase( Locale.ROOT )
68+
);
69+
}
70+
71+
@Test
72+
@TestForIssue(jiraKey = "HHH-8768")
73+
public void testGetLimitStringWithOffsetAndMaxRowsNoOrderBy() {
74+
// this test defaults back to validating result matches that from SQLServer2005LimitHandler
75+
// See SQLServer2012LimitHandler for why this falls back
76+
final String input = "select f1 from table";
77+
assertEquals(
78+
"with query as (select inner_query.*, row_number() over (order by current_timestamp) as __hibernate_row_nr__ " +
79+
"from ( select f1 as page0_ from table ) inner_query ) select page0_ from query where " +
80+
"__hibernate_row_nr__ >= ? and __hibernate_row_nr__ < ?",
81+
dialect.getLimitHandler().processSql( input, toRowSelection( 5, 10 ) ).toLowerCase( Locale.ROOT )
82+
);
83+
}
84+
85+
private RowSelection toRowSelection(int firstRow, int maxRows) {
86+
final RowSelection selection = new RowSelection();
87+
selection.setFirstRow( firstRow );
88+
selection.setMaxRows( maxRows );
89+
return selection;
90+
}
91+
}

0 commit comments

Comments
 (0)