HHH-12196 Implement a naive limit handler for Sybase

It doesn't manage all the corner cases but it should be safe enough as
only triggered in the simple cases.
This commit is contained in:
Guillaume Smet 2018-08-01 17:37:29 +02:00
parent 5d965f8e15
commit b66df9a352
3 changed files with 156 additions and 0 deletions

View File

@ -12,6 +12,8 @@ import java.util.Map;
import org.hibernate.JDBCException;
import org.hibernate.LockOptions;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.SybaseASE157LimitHandler;
import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
@ -27,6 +29,8 @@ import org.hibernate.type.StandardBasicTypes;
*/
public class SybaseASE157Dialect extends SybaseASE15Dialect {
private static final SybaseASE157LimitHandler LIMIT_HANDLER = new SybaseASE157LimitHandler();
/**
* Constructs a SybaseASE157Dialect
*/
@ -102,4 +106,19 @@ public class SybaseASE157Dialect extends SybaseASE15Dialect {
}
};
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean supportsLimitOffset() {
return false;
}
@Override
public LimitHandler getLimitHandler() {
return LIMIT_HANDLER;
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.dialect.pagination;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.engine.spi.RowSelection;
/**
* This limit handler is very conservative and is only triggered in simple cases involving a select or select distinct.
* <p>
* Note that if the query already contains "top" just after the select or select distinct, we don't add anything to the
* query. It might just be a column name but, in any case, we just don't add the top clause and default to the previous
* behavior so it's not an issue.
*/
public class SybaseASE157LimitHandler extends AbstractLimitHandler {
private static final Pattern SELECT_DISTINCT_PATTERN = Pattern.compile( "^(\\s*select\\s+distinct\\s+).*",
Pattern.CASE_INSENSITIVE );
private static final Pattern SELECT_PATTERN = Pattern.compile( "^(\\s*select\\s+).*", Pattern.CASE_INSENSITIVE );
private static final Pattern TOP_PATTERN = Pattern.compile( "^\\s*top\\s+.*", Pattern.CASE_INSENSITIVE );
@Override
public String processSql(String sql, RowSelection selection) {
if ( selection.getMaxRows() == null ) {
return sql;
}
int top = getMaxOrLimit( selection );
if ( top == Integer.MAX_VALUE ) {
return sql;
}
Matcher selectDistinctMatcher = SELECT_DISTINCT_PATTERN.matcher( sql );
if ( selectDistinctMatcher.matches() ) {
return insertTop( selectDistinctMatcher, sql, top );
}
Matcher selectMatcher = SELECT_PATTERN.matcher( sql );
if ( selectMatcher.matches() ) {
return insertTop( selectMatcher, sql, top );
}
return sql;
}
@Override
public boolean supportsLimit() {
return true;
}
@Override
public boolean supportsLimitOffset() {
return false;
}
@Override
public boolean useMaxForLimit() {
return true;
}
@Override
public boolean supportsVariableLimit() {
return false;
}
private static String insertTop(Matcher matcher, String sql, int top) {
int end = matcher.end( 1 );
if ( TOP_PATTERN.matcher( sql.substring( end ) ).matches() ) {
return sql;
}
StringBuilder sb = new StringBuilder( sql );
sb.insert( end, "top " + top + " " );
return sb.toString();
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.dialect;
import static org.junit.Assert.assertEquals;
import org.hibernate.dialect.pagination.SybaseASE157LimitHandler;
import org.hibernate.engine.spi.RowSelection;
import org.junit.Test;
public class SybaseASE157LimitHandlerTest {
@Test
public void testLimitHandler() {
assertEquals( "select * from entity", processSql( "select * from entity", null, null ) );
assertEquals( "select * from entity", processSql( "select * from entity", 15, null ) );
assertEquals( "select top 15 * from entity", processSql( "select * from entity", null, 15 ) );
assertEquals( "select top 18 * from entity", processSql( "select * from entity", 3, 15 ) );
assertEquals( "SELECT top 18 * FROM entity", processSql( "SELECT * FROM entity", 3, 15 ) );
assertEquals( " select top 18 * from entity", processSql( " select * from entity", 3, 15 ) );
assertEquals( "selectand", processSql( "selectand", 3, 15 ) );
assertEquals( "select distinct top 15 id from entity",
processSql( "select distinct id from entity", null, 15 ) );
assertEquals( "select distinct top 18 id from entity", processSql( "select distinct id from entity", 3, 15 ) );
assertEquals( " select distinct top 18 id from entity",
processSql( " select distinct id from entity", 3, 15 ) );
assertEquals(
"WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'",
processSql(
"WITH employee AS (SELECT * FROM Employees) SELECT * FROM employee WHERE ID < 20 UNION ALL SELECT * FROM employee WHERE Sex = 'M'",
3, 15 ) );
assertEquals( "select top 5 * from entity", processSql( "select top 5 * from entity", 3, 15 ) );
assertEquals( "select distinct top 7 * from entity", processSql( "select distinct top 7 * from entity", 3, 15 ) );
assertEquals( "select distinct top 18 top_column from entity", processSql( "select distinct top_column from entity", 3, 15 ) );
}
private String processSql(String sql, Integer offset, Integer limit) {
RowSelection rowSelection = new RowSelection();
if ( offset != null ) {
rowSelection.setFirstRow( offset );
}
if (limit != null) {
rowSelection.setMaxRows( limit );
}
SybaseASE157LimitHandler limitHandler = new SybaseASE157LimitHandler();
return limitHandler.processSql( sql, rowSelection );
}
}