From f4fbad4f02d58a95fe34d5785252bfdea325689b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Thu, 21 Aug 2025 16:45:07 +1000 Subject: [PATCH] HHH-3404 regexp support in criteria API --- .../criteria/HibernateCriteriaBuilder.java | 8 +++ .../spi/HibernateCriteriaBuilderDelegate.java | 20 +++++++ .../hql/internal/SemanticQueryBuilder.java | 12 ++-- .../sqm/internal/SqmCriteriaNodeBuilder.java | 60 +++++++++++++++++++ .../orm/test/query/hql/RegexTest.java | 29 ++++++++- 5 files changed, 122 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java index 81ee863617fb..d3e94bfc0715 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/HibernateCriteriaBuilder.java @@ -998,6 +998,14 @@ > JpaPredicate between( JpaPredicate notIlike(Expression x, String pattern, char escapeChar); + JpaPredicate likeRegexp(Expression x, String pattern); + + JpaPredicate ilikeRegexp(Expression x, String pattern); + + JpaPredicate notLikeRegexp(Expression x, String pattern); + + JpaPredicate notIlikeRegexp(Expression x, String pattern); + @Override JpaInPredicate in(Expression expression); diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java index 12ff27e443e6..f7118e2280fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/spi/HibernateCriteriaBuilderDelegate.java @@ -1255,6 +1255,26 @@ public JpaPredicate notIlike(Expression x, String pattern, char escapeCh return criteriaBuilder.notIlike( x, pattern, escapeChar ); } + @Override + public JpaPredicate likeRegexp(Expression x, String pattern) { + return criteriaBuilder.likeRegexp( x, pattern ); + } + + @Override + public JpaPredicate ilikeRegexp(Expression x, String pattern) { + return criteriaBuilder.ilikeRegexp( x, pattern ); + } + + @Override + public JpaPredicate notLikeRegexp(Expression x, String pattern) { + return criteriaBuilder.notLikeRegexp( x, pattern ); + } + + @Override + public JpaPredicate notIlikeRegexp(Expression x, String pattern) { + return criteriaBuilder.notIlikeRegexp( x, pattern ); + } + @Override public JpaInPredicate in(Expression expression) { return criteriaBuilder.in( expression ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 299bee0ef876..53d709bd94d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -3210,11 +3210,13 @@ public SqmPredicate visitIntersectsPredicate(HqlParser.IntersectsPredicateContex query ); } - final SelfRenderingSqmFunction contains = getFunctionDescriptor( "array_intersects" ).generateSqmExpression( - asList( lhs, rhs ), - null, - queryEngine() - ); + final SelfRenderingSqmFunction contains = + getFunctionDescriptor( "array_intersects" ) + .generateSqmExpression( + asList( lhs, rhs ), + null, + queryEngine() + ); return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java index 070e2d5a0874..5d5abf49f4ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmCriteriaNodeBuilder.java @@ -2953,6 +2953,66 @@ public SqmPredicate notIlike(Expression x, String pattern, char escapeCh return not( ilike( x, pattern, escapeChar ) ); } + @Override + public JpaPredicate likeRegexp(Expression x, String pattern) { + return new SqmBooleanExpressionPredicate( + getFunctionDescriptor( "regexp_like" ) + .generateSqmExpression( + asList( (SqmExpression) x, + literal( pattern ) ), + null, + getQueryEngine() + ), + this + ); + } + + @Override + public JpaPredicate ilikeRegexp(Expression x, String pattern) { + return new SqmBooleanExpressionPredicate( + getFunctionDescriptor( "regexp_like" ) + .generateSqmExpression( + asList( (SqmExpression) x, + literal( pattern ), + literal( "i" ) ), + null, + getQueryEngine() + ), + this + ); + } + + @Override + public JpaPredicate notLikeRegexp(Expression x, String pattern) { + return new SqmBooleanExpressionPredicate( + getFunctionDescriptor( "regexp_like" ) + .generateSqmExpression( + asList( (SqmExpression) x, + literal( pattern ) ), + null, + getQueryEngine() + ), + true, + this + ); + } + + @Override + public JpaPredicate notIlikeRegexp(Expression x, String pattern) { + return new SqmBooleanExpressionPredicate( + getFunctionDescriptor( "regexp_like" ) + .generateSqmExpression( + asList( (SqmExpression) x, + literal( pattern ), + literal( "i" ) ), + null, + getQueryEngine() + ), + true, + this + ); + } + @Override @SuppressWarnings("unchecked") public SqmInPredicate in(Expression expression) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/RegexTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/RegexTest.java index 9867c2fd42ee..c113b20ff308 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/RegexTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/RegexTest.java @@ -10,12 +10,14 @@ import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SQLServerDialect; import org.hibernate.dialect.SybaseASEDialect; +import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.Jpa; import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @Jpa @@ -31,6 +33,18 @@ void testInSelect(EntityManagerFactoryScope scope) { scope.inEntityManager( em -> { assertTrue( em.createQuery( "select regexp_like('abcdef', 'ab.*')", Boolean.class ).getSingleResult() ); assertTrue( em.createQuery( "select 'abcdef' like regexp 'ab.*'", Boolean.class ).getSingleResult() ); + var builder = (HibernateCriteriaBuilder) em.getCriteriaBuilder(); + var query = builder.createQuery( Boolean.class ); + query.select( builder.likeRegexp( builder.literal( "abcdef" ), "ab.*" ) ); + assertTrue( em.createQuery( query ).getSingleResult() ); + } ); + scope.inEntityManager( em -> { + assertFalse( em.createQuery( "select not regexp_like('abcdef', 'ab.*')", Boolean.class ).getSingleResult() ); + assertFalse( em.createQuery( "select 'abcdef' not like regexp 'ab.*'", Boolean.class ).getSingleResult() ); + var builder = (HibernateCriteriaBuilder) em.getCriteriaBuilder(); + var query = builder.createQuery( Boolean.class ); + query.select( builder.notLikeRegexp( builder.literal( "abcdef" ), "ab.*" ) ); + assertFalse( em.createQuery( query ).getSingleResult() ); } ); } } @@ -48,9 +62,20 @@ void testInSelectCaseInsensitive(EntityManagerFactoryScope scope) { if ( !( scope.getDialect() instanceof OracleDialect dialect && ( dialect.isAutonomous() || dialect.getVersion().isBefore( 23 ) ) ) ) { scope.inEntityManager( em -> { - assertTrue( em.createQuery( "select regexp_like('ABCDEF', 'ab.*', 'i')", Boolean.class ) - .getSingleResult() ); + assertTrue( em.createQuery( "select regexp_like('ABCDEF', 'ab.*', 'i')", Boolean.class ).getSingleResult() ); assertTrue( em.createQuery( "select 'abcdef' ilike regexp 'ab.*'", Boolean.class ).getSingleResult() ); + var builder = (HibernateCriteriaBuilder) em.getCriteriaBuilder(); + var query = builder.createQuery( Boolean.class ); + query.select( builder.ilikeRegexp( builder.literal( "ABCDEF" ), "ab.*" ) ); + assertTrue( em.createQuery( query ).getSingleResult() ); + } ); + scope.inEntityManager( em -> { + assertFalse( em.createQuery( "select not regexp_like('ABCDEF', 'ab.*', 'i')", Boolean.class ).getSingleResult() ); + assertFalse( em.createQuery( "select 'abcdef' not ilike regexp 'ab.*'", Boolean.class ).getSingleResult() ); + var builder = (HibernateCriteriaBuilder) em.getCriteriaBuilder(); + var query = builder.createQuery( Boolean.class ); + query.select( builder.notIlikeRegexp( builder.literal( "ABCDEF" ), "ab.*" ) ); + assertFalse( em.createQuery( query ).getSingleResult() ); } ); } }