diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/IngresLimitHandler.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/IngresLimitHandler.java index 0778d1c63c3f..332c3dd427b8 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/IngresLimitHandler.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/IngresLimitHandler.java @@ -66,7 +66,7 @@ protected boolean renderOffsetRowsKeyword() { }; private static final Pattern WITH_OPTION_PATTERN = - Pattern.compile("\\s+with\\s+(" + String.join("|", WITH_OPTIONS) + ")\\b|\\s*(;|$)"); + Pattern.compile("\\s+with\\s+(" + String.join("|", WITH_OPTIONS) + ")\\b|\\s*;?\\s*$"); /** * The offset/fetch clauses must come before diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/RowsLimitHandler.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/RowsLimitHandler.java index 8c57f5e69d1f..9c3c86a06316 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/RowsLimitHandler.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/pagination/RowsLimitHandler.java @@ -47,7 +47,7 @@ public int convertToFirstRowValue(int zeroBasedFirstResult) { } private static final Pattern FOR_UPDATE_PATTERN = - compile("\\s+for\\s+update\\b|\\s+with\\s+lock\\b|\\s*(;|$)", CASE_INSENSITIVE); + compile("\\s+for\\s+update\\b|\\s+with\\s+lock\\b|\\s*;?\\s*$", CASE_INSENSITIVE); @Override protected Pattern getForUpdatePattern() { diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/IngresLimitHandlerTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/IngresLimitHandlerTest.java new file mode 100644 index 000000000000..af8b1aebbc19 --- /dev/null +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/IngresLimitHandlerTest.java @@ -0,0 +1,22 @@ +/* + * 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.community.dialect; + +import org.hibernate.community.dialect.pagination.IngresLimitHandler; +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.orm.test.dialect.AbstractLimitHandlerTest; + +/** + * @author Yanming Zhou + */ +public class IngresLimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return IngresLimitHandler.INSTANCE; + } +} diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/RowsLimitHandlerTest.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/RowsLimitHandlerTest.java new file mode 100644 index 000000000000..26ef52d81516 --- /dev/null +++ b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/RowsLimitHandlerTest.java @@ -0,0 +1,27 @@ +/* + * 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.community.dialect; + +import org.hibernate.community.dialect.pagination.RowsLimitHandler; +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.orm.test.dialect.AbstractLimitHandlerTest; + +/** + * @author Yanming Zhou + */ +public class RowsLimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return RowsLimitHandler.INSTANCE; + } + + @Override + protected String getLimitClause() { + return " rows ?"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java index 4930146d8800..820ea337c0be 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/AbstractLimitHandler.java @@ -33,10 +33,10 @@ public abstract class AbstractLimitHandler implements LimitHandler { compile( "^\\s*select(\\s+(distinct|all))?\\b", CASE_INSENSITIVE ); private static final Pattern END_PATTERN = - compile("\\s*(;|$)", CASE_INSENSITIVE); + compile("\\s*;?\\s*$", CASE_INSENSITIVE); private static final Pattern FOR_UPDATE_PATTERN = - compile("\\s+for\\s+update\\b|\\s*(;|$)", CASE_INSENSITIVE); + compile("\\s+for\\s+update\\b|\\s*;?\\s*$", CASE_INSENSITIVE); @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/DerbyLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/DerbyLimitHandler.java index 3a847fbc40bc..0ef07404afa0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/DerbyLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/DerbyLimitHandler.java @@ -29,7 +29,7 @@ public DerbyLimitHandler(boolean variableLimit) { } private static final Pattern FOR_UPDATE_WITH_LOCK_PATTERN = - Pattern.compile("\\s+for\\s+(update|read|fetch)\\b|\\s+with\\s+(rr|rs|cs|ur)\\b|\\s*(;|$)"); + Pattern.compile("\\s+for\\s+(update|read|fetch)\\b|\\s+with\\s+(rr|rs|cs|ur)\\b|\\s*;?\\s*$"); /** * The offset/fetch clauses must come before the diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitLimitHandler.java index 03fdf42b62ad..c57154a2f4d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LimitLimitHandler.java @@ -34,7 +34,7 @@ protected String offsetOnlyClause() { } private static final Pattern FOR_UPDATE_PATTERN = - compile("\\s+for\\s+update\\b|\\s+lock\\s+in\\s+shared\\s+mode\\b|\\s*(;|$)", CASE_INSENSITIVE); + compile("\\s+for\\s+update\\b|\\s+lock\\s+in\\s+shared\\s+mode\\b|\\s*;?\\s*$", CASE_INSENSITIVE); @Override protected Pattern getForUpdatePattern() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java index 717d07317d11..017e42366243 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/Oracle12LimitHandler.java @@ -99,7 +99,7 @@ else if ( hasFirstRow ) { offsetFetchString = " fetch first ? rows only"; } - return sql + offsetFetchString; + return insertAtEnd(offsetFetchString, sql); } /** diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/AbstractLimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/AbstractLimitHandlerTest.java new file mode 100644 index 000000000000..58fb19ea61c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/AbstractLimitHandlerTest.java @@ -0,0 +1,73 @@ +/* + * 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.orm.test.dialect; + +import org.hibernate.dialect.pagination.LimitHandler; +import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; +import org.hibernate.query.spi.Limit; +import org.hibernate.query.spi.QueryOptions; +import org.junit.jupiter.api.Test; + +import static org.hibernate.dialect.pagination.AbstractLimitHandler.hasFirstRow; +import static org.hibernate.dialect.pagination.AbstractLimitHandler.hasMaxRows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Yanming Zhou + */ +public abstract class AbstractLimitHandlerTest { + + @Test + public void testSqlWithSemicolonInsideQuotedString() { + String sql = "select * from Person p where p.name like ';'"; + String expected = "select * from Person p where p.name like ';'" + getLimitClause(); + assertGenerateExpectedSql(expected, sql); + + sql = "select * from Person p where p.name like ';' "; + expected = "select * from Person p where p.name like ';'" + getLimitClause() + " "; + assertGenerateExpectedSql(expected, sql); + } + + @Test + public void testSqlWithSemicolonInsideQuotedStringAndEndsWithSemicolon() { + String sql = "select * from Person p where p.name like ';';"; + String expected = "select * from Person p where p.name like ';'" + getLimitClause() + ";"; + assertGenerateExpectedSql(expected, sql); + + sql = "select * from Person p where p.name like ';' ; "; + expected = "select * from Person p where p.name like ';'" + getLimitClause() + " ; "; + assertGenerateExpectedSql(expected, sql); + } + + protected void assertGenerateExpectedSql(String expected, String sql) { + assertEquals(expected, getLimitHandler().processSql(sql, getLimit(), QueryOptions.NONE)); + } + + protected abstract LimitHandler getLimitHandler(); + + protected Limit getLimit() { + return new Limit(0, 10); + } + + protected String getLimitClause() { + LimitHandler handler = getLimitHandler(); + if (handler instanceof OffsetFetchLimitHandler) { + OffsetFetchLimitHandler oflh = (OffsetFetchLimitHandler) handler; + Limit limit = getLimit(); + if (hasFirstRow(limit) && hasMaxRows(limit)) { + return " offset " + (oflh.supportsVariableLimit() ? "?" : String.valueOf(limit.getFirstRow())) + + " rows fetch next " + (oflh.supportsVariableLimit() ? "?" : String.valueOf(limit.getMaxRows())) + " rows only"; + } + else if (hasFirstRow(limit)) { + return " offset " + (oflh.supportsVariableLimit() ? "?" : String.valueOf(limit.getFirstRow())) + " rows"; + } else { + return " fetch first " + (oflh.supportsVariableLimit() ? "?" : String.valueOf(limit.getMaxRows())) + " rows only"; + } + } + return " limit ?"; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2LimitHandlerTest.java new file mode 100644 index 000000000000..497fe42dbb06 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DB2LimitHandlerTest.java @@ -0,0 +1,21 @@ +/* + * 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.orm.test.dialect; + +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.dialect.pagination.DB2LimitHandler; + +/** + * @author Yanming Zhou + */ +public class DB2LimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return DB2LimitHandler.INSTANCE; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyLimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyLimitHandlerTest.java new file mode 100644 index 000000000000..453bd1bd769b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/DerbyLimitHandlerTest.java @@ -0,0 +1,21 @@ +/* + * 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.orm.test.dialect; + +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.dialect.pagination.DerbyLimitHandler; + +/** + * @author Yanming Zhou + */ +public class DerbyLimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return DerbyLimitHandler.INSTANCE; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/LimitLimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/LimitLimitHandlerTest.java new file mode 100644 index 000000000000..c1eb39b6822f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/LimitLimitHandlerTest.java @@ -0,0 +1,21 @@ +/* + * 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.orm.test.dialect; + +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.dialect.pagination.LimitLimitHandler; + +/** + * @author Yanming Zhou + */ +public class LimitLimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return LimitLimitHandler.INSTANCE; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/OffsetFetchLimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/OffsetFetchLimitHandlerTest.java new file mode 100644 index 000000000000..c4b717b7fcc8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/OffsetFetchLimitHandlerTest.java @@ -0,0 +1,21 @@ +/* + * 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.orm.test.dialect; + +import org.hibernate.dialect.pagination.AbstractLimitHandler; +import org.hibernate.dialect.pagination.OffsetFetchLimitHandler; + +/** + * @author Yanming Zhou + */ +public class OffsetFetchLimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return OffsetFetchLimitHandler.INSTANCE; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/Oracle12LimitHandlerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/Oracle12LimitHandlerTest.java index b5bad4a1098b..85c250d64ce4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/Oracle12LimitHandlerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/Oracle12LimitHandlerTest.java @@ -6,27 +6,45 @@ */ package org.hibernate.orm.test.dialect; +import org.hibernate.dialect.pagination.AbstractLimitHandler; + import org.hibernate.dialect.pagination.Oracle12LimitHandler; import org.hibernate.query.spi.Limit; -import org.hibernate.query.spi.QueryOptions; - import org.hibernate.testing.TestForIssue; import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.hibernate.dialect.pagination.AbstractLimitHandler.hasFirstRow; +import static org.hibernate.dialect.pagination.AbstractLimitHandler.hasMaxRows; @TestForIssue( jiraKey = "HHH-14649") -public class Oracle12LimitHandlerTest { +public class Oracle12LimitHandlerTest extends AbstractLimitHandlerTest { + + @Override + protected AbstractLimitHandler getLimitHandler() { + return Oracle12LimitHandler.INSTANCE; + } + + @Override + protected String getLimitClause() { + Limit limit = getLimit(); + if ( hasFirstRow(limit) && hasMaxRows(limit) ) { + return " offset ? rows fetch next ? rows only"; + } + else if ( hasFirstRow(limit) ) { + return " offset ? rows"; + } + else { + return " fetch first ? rows only"; + } + } @Test public void testSqlWithSpace() { final String sql = "select p.name from Person p where p.id = 1 for update"; final String expected = "select * from (select p.name from Person p where p.id = 1) where rownum<=? for update"; - final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, new Limit( 0, 5 ), QueryOptions.NONE ); - - assertEquals( expected, processedSql ); + assertGenerateExpectedSql(expected, sql); } @Test @@ -34,9 +52,7 @@ public void testSqlWithSpaceInsideQuotedString() { final String sql = "select p.name from Person p where p.name = ' this is a string with spaces ' for update"; final String expected = "select * from (select p.name from Person p where p.name = ' this is a string with spaces ') where rownum<=? for update"; - final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, new Limit( 0, 5 ), QueryOptions.NONE ); - - assertEquals( expected, processedSql ); + assertGenerateExpectedSql(expected, sql); } @Test @@ -44,9 +60,7 @@ public void testSqlWithForUpdateInsideQuotedString() { final String sql = "select a.prop from A a where a.name = 'this is for update '"; final String expected = "select a.prop from A a where a.name = 'this is for update ' fetch first ? rows only"; - final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, new Limit( 0, 5 ), QueryOptions.NONE ); - - assertEquals( expected, processedSql ); + assertGenerateExpectedSql(expected, sql); } @Test @@ -54,9 +68,7 @@ public void testSqlWithForUpdateInsideAndOutsideQuotedStringA() { final String sql = "select a.prop from A a where a.name = 'this is for update ' for update"; final String expected = "select * from (select a.prop from A a where a.name = 'this is for update ') where rownum<=? for update"; - final String processedSql = Oracle12LimitHandler.INSTANCE.processSql( sql, new Limit( 0, 5 ), QueryOptions.NONE ); - - assertEquals( expected, processedSql ); + assertGenerateExpectedSql(expected, sql); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java index 44e3b0e0699d..7282501bfa05 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/query/QueryTest.java @@ -28,8 +28,11 @@ import org.hibernate.QueryException; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.CockroachDialect; +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.PostgresPlusDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.Distributor; @@ -40,6 +43,7 @@ import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.JiraKey; import org.junit.Test; import junit.framework.Assert; @@ -55,6 +59,7 @@ * @author Emmanuel Bernard * @author Steve Ebersole * @author Chris Cranford + * @author Yanming Zhou */ public class QueryTest extends BaseEntityManagerFunctionalTestCase { @Override @@ -546,6 +551,74 @@ public Class getParameterType() { } } + @Test + @JiraKey("HHH-18033") + public void testQueryContainsQuotedSemicolonWithLimit() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + try { + em.persist( new Item( "Mouse;", "Micro$oft mouse" ) ); + + Query q = em.createQuery( "from Item where name like '%;%'" ).setMaxResults(10); + assertEquals( 1, q.getResultList().size() ); + + q = em.createQuery( "from Item where name like '%;%' " ).setMaxResults(10); + assertEquals( 1, q.getResultList().size() ); + } + finally { + if ( em.getTransaction() != null && em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + em.close(); + } + } + + @Test + @JiraKey("HHH-18033") + public void testNativeQueryContainsQuotedSemicolonWithLimit() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + try { + em.persist( new Item( "Mouse;", "Micro$oft mouse" ) ); + + Query q = em.createNativeQuery( "select * from Item where name like '%;%'" ).setMaxResults(10); + assertEquals( 1, q.getResultList().size() ); + + q = em.createNativeQuery( "select * from Item where name like '%;%' " ).setMaxResults(10); + assertEquals( 1, q.getResultList().size() ); + } + finally { + if ( em.getTransaction() != null && em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + em.close(); + } + } + + @Test + @SkipForDialect(value = OracleDialect.class, jiraKey = "HHH-18033", comment = "Doesn't support semicolon as ending of statement") + @SkipForDialect(value = SybaseDialect.class, jiraKey = "HHH-18033", comment = "Doesn't support semicolon as ending of statement") + @SkipForDialect(value = DerbyDialect.class, jiraKey = "HHH-18033", comment = "Doesn't support semicolon as ending of statement") + public void testNativeQueryContainsQuotedSemicolonAndEndsWithSemicolonWithLimit() { + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + try { + em.persist( new Item( "Mouse;", "Micro$oft mouse" ) ); + + Query q = em.createNativeQuery( "select * from Item where name like '%;%';" ).setMaxResults(10); + assertEquals( 1, q.getResultList().size() ); + + q = em.createNativeQuery( "select * from Item where name like '%;%' ; " ).setMaxResults(10); + assertEquals( 1, q.getResultList().size() ); + } + finally { + if ( em.getTransaction() != null && em.getTransaction().isActive() ) { + em.getTransaction().rollback(); + } + em.close(); + } + } + @Test public void testAggregationReturnType() throws Exception { EntityManager em = getOrCreateEntityManager();