From a6c773317913f41c2634b1c420c32d0ddafc1ce3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 1 Aug 2018 17:37:29 +0200 Subject: [PATCH] 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. --- .../dialect/SybaseASE157Dialect.java | 19 +++++ .../pagination/SybaseASE157LimitHandler.java | 83 +++++++++++++++++++ .../dialect/SybaseASE157LimitHandlerTest.java | 54 ++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java create mode 100644 hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java index 5b8d17073e..01155cd994 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE157Dialect.java @@ -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; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java new file mode 100644 index 0000000000..5137385a05 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/SybaseASE157LimitHandler.java @@ -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 . + */ +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. + *

+ * 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(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java new file mode 100644 index 0000000000..ceb764d59b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/dialect/SybaseASE157LimitHandlerTest.java @@ -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 . + */ +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 ); + } +}