This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit c63b7be93d4a80d35eeee95e410e21f7390cda85 Author: Walter B Duque de Estrada <[email protected]> AuthorDate: Wed Jan 28 12:38:35 2026 -0600 Rlike added --- .../query/GrailsRLikeFunctionContributor.java | 27 +++++++++++++++ .../grails/orm/hibernate/query/HibernateQuery.java | 3 +- .../orm/hibernate/query/PredicateGenerator.java | 9 ++++- .../orm/hibernate/query/RegexDialectPattern.java | 38 ++++++++++++++++++++++ .../org.hibernate.boot.model.FunctionContributor | 1 + .../test/groovy/grails/gorm/specs/RLikeSpec.groovy | 2 -- .../specs/hibernatequery/HibernateQuerySpec.groovy | 3 +- .../hibernate/query/RegexDialectPatternSpec.groovy | 28 ++++++++++++++++ 8 files changed, 105 insertions(+), 6 deletions(-) diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsRLikeFunctionContributor.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsRLikeFunctionContributor.java new file mode 100644 index 0000000000..a0c85d6595 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/GrailsRLikeFunctionContributor.java @@ -0,0 +1,27 @@ +package org.grails.orm.hibernate.query; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.dialect.Dialect; +import org.hibernate.type.StandardBasicTypes; + +public class GrailsRLikeFunctionContributor implements FunctionContributor { + + public static final String RLIKE = "rlike"; + + @Override + public void contributeFunctions(FunctionContributions functionContributions) { + Dialect dialect = functionContributions.getDialect(); + + // Use the Enum to resolve the pattern + String pattern = RegexDialectPattern.findPatternForDialect(dialect); + + functionContributions.getFunctionRegistry().registerPattern( + RLIKE, + pattern, + functionContributions.getTypeConfiguration() + .getBasicTypeRegistry() + .resolve(StandardBasicTypes.BOOLEAN) + ); + } +} diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java index 423f26ad6f..0dd747c2de 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java @@ -316,7 +316,8 @@ public class HibernateQuery extends Query { @Override public Query rlike(String property, String expr) { - throw new UnsupportedOperationException("Needs RLIKE extension"); + detachedCriteria.rlike(property,expr); + return this; } @Override diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java index 93e4c1312a..12ff7965ff 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java @@ -19,6 +19,7 @@ import org.grails.datastore.mapping.model.types.Association; import org.grails.datastore.mapping.query.Query; import org.grails.datastore.mapping.query.api.QueryableCriteria; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaFunction; import org.hibernate.query.criteria.JpaInPredicate; import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.sqm.tree.domain.SqmPath; @@ -131,7 +132,13 @@ public class PredicateGenerator { } else if (criterion instanceof Query.ILike c) { return cb.ilike(fullyQualifiedPath, c.getValue().toString()); } else if (criterion instanceof Query.RLike c) { - return cb.like(fullyQualifiedPath, c.getPattern(), '\\'); + String pattern = c.getPattern(); + pattern = pattern.replaceAll("^/|/$", ""); + return cb.equal(cb.function( + GrailsRLikeFunctionContributor.RLIKE, // The name we registered + Boolean.class, // Expected return type + fullyQualifiedPath, // The property path + cb.literal(pattern)), true); } else if (criterion instanceof Query.Like c) { return cb.like(fullyQualifiedPath, c.getValue().toString()); } else if (criterion instanceof Query.In c) { diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/RegexDialectPattern.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/RegexDialectPattern.java new file mode 100644 index 0000000000..1d4025e70d --- /dev/null +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/RegexDialectPattern.java @@ -0,0 +1,38 @@ +package org.grails.orm.hibernate.query; + +import org.hibernate.dialect.*; +import java.util.Arrays; + +public enum RegexDialectPattern { + MYSQL(MySQLDialect.class, "?1 RLIKE ?2"), + MARIADB(MariaDBDialect.class, "?1 REGEXP ?2"), + POSTGRES(PostgreSQLDialect.class, "?1 ~ ?2"), + ORACLE(OracleDialect.class, "REGEXP_LIKE(?1, ?2)"), + H2(H2Dialect.class, "REGEXP_LIKE(?1, ?2)"), + // Default fallback + DEFAULT(Dialect.class, "?1 LIKE ?2"); + + private final Class<? extends Dialect> dialectClass; + private final String sqlPattern; + + RegexDialectPattern(Class<? extends Dialect> dialectClass, String sqlPattern) { + this.dialectClass = dialectClass; + this.sqlPattern = sqlPattern; + } + + public String getSqlPattern() { + return sqlPattern; + } + + /** + * Resolves the pattern by checking if the runtime dialect + * is an instance of the supported dialect class. + */ + public static String findPatternForDialect(Dialect runtimeDialect) { + return Arrays.stream(values()) + .filter(p -> p != DEFAULT && p.dialectClass.isInstance(runtimeDialect)) + .findFirst() + .map(RegexDialectPattern::getSqlPattern) + .orElse(DEFAULT.sqlPattern); + } +} diff --git a/grails-data-hibernate7/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor b/grails-data-hibernate7/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor new file mode 100644 index 0000000000..5f64497633 --- /dev/null +++ b/grails-data-hibernate7/core/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor @@ -0,0 +1 @@ +org.grails.orm.hibernate.query.GrailsRLikeFunctionContributor diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/RLikeSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/RLikeSpec.groovy index b5d5f04e2b..07dc293ae0 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/RLikeSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/RLikeSpec.groovy @@ -23,13 +23,11 @@ import org.apache.grails.data.hibernate7.core.GrailsDataHibernate7TckManager import org.apache.grails.data.testing.tck.base.GrailsDataTckSpec import spock.lang.Ignore -//TODO Rlike Needs to be implemented for Hibernate 6 class RLikeSpec extends GrailsDataTckSpec<GrailsDataHibernate7TckManager> { void setupSpec() { manager.addAllDomainClasses([RlikeFoo]) } - @Ignore void "test rlike works with H2"() { given: new RlikeFoo(name: "ABC").save(flush: true) diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy index 139dd564d2..b4b7059c35 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy @@ -207,11 +207,10 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { oldBob == newBob } - @Ignore("Must add custom functionality") def rlike() { given: new Person(firstName: "Fred", lastName: "Rogers", age: 52).save(flush: true) - hibernateQuery.rlike("firstName", "/Bob*/") + hibernateQuery.rlike("firstName", "Bob.*") when: def newBob = hibernateQuery.singleResult() then: diff --git a/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/query/RegexDialectPatternSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/query/RegexDialectPatternSpec.groovy new file mode 100644 index 0000000000..25a6b1b51d --- /dev/null +++ b/grails-data-hibernate7/core/src/test/groovy/org/grails/orm/hibernate/query/RegexDialectPatternSpec.groovy @@ -0,0 +1,28 @@ +package org.grails.orm.hibernate.query + +import org.hibernate.dialect.H2Dialect +import org.hibernate.dialect.MySQLDialect +import org.hibernate.dialect.MariaDBDialect +import org.hibernate.dialect.PostgreSQLDialect +import org.hibernate.dialect.OracleDialect +import org.hibernate.dialect.SQLServerDialect +import spock.lang.Specification +import spock.lang.Unroll + +class RegexDialectPatternSpec extends Specification { + + @Unroll + void "test findPatternForDialect for #dialect.class.simpleName"() { + expect: + RegexDialectPattern.findPatternForDialect(dialect) == expectedPattern + + where: + dialect | expectedPattern + new MySQLDialect() | "?1 RLIKE ?2" + new MariaDBDialect() | "?1 REGEXP ?2" + new PostgreSQLDialect() | "?1 ~ ?2" + new OracleDialect() | "REGEXP_LIKE(?1, ?2)" + new H2Dialect() | "REGEXP_LIKE(?1, ?2)" + new SQLServerDialect() | "?1 LIKE ?2" // Fallback + } +}
