diff --git a/documentation/src/main/asciidoc/querylanguage/Expressions.adoc b/documentation/src/main/asciidoc/querylanguage/Expressions.adoc index a4c28ec7a7cc..00f46ab5ebb0 100644 --- a/documentation/src/main/asciidoc/querylanguage/Expressions.adoc +++ b/documentation/src/main/asciidoc/querylanguage/Expressions.adoc @@ -1495,7 +1495,7 @@ Their syntax is defined by: include::{extrasdir}/predicate_like_bnf.txt[] ---- -The expression on the right is a pattern, where: +The expression on the right is usually a SQL-style pattern, where: * `_` matches any single character, * `%` matches any number of characters, and @@ -1509,6 +1509,14 @@ from Book where title not like '% for Dummies' The optional `escape` character allows a pattern to include a literal `_` or `%` character. +Alternatively, the `regexp` keyword specifies that the pattern should be interpreted as a regular expression: + +[[like-regexp-predicate-example]] +[source, hql] +---- +from Book where title not like regexp '.+ for Dummies' +---- + As you can guess, `not like` and `not ilike` are the enemies of `like` and `ilike`, and evaluate to the exact opposite boolean values. [[in-predicate]] diff --git a/documentation/src/main/asciidoc/querylanguage/extras/predicate_like_bnf.txt b/documentation/src/main/asciidoc/querylanguage/extras/predicate_like_bnf.txt index c9e676602390..e1633af799f3 100644 --- a/documentation/src/main/asciidoc/querylanguage/extras/predicate_like_bnf.txt +++ b/documentation/src/main/asciidoc/querylanguage/extras/predicate_like_bnf.txt @@ -1 +1 @@ -expression "NOT"? ("LIKE" | "ILIKE") expression ("ESCAPE" character)? +expression "NOT"? ("LIKE" | "ILIKE") REGEXP? expression ("ESCAPE" character)? diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index a0e1d938b254..d9bad62595ed 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -292,6 +292,7 @@ POSITION : [pP] [oO] [sS] [iI] [tT] [iI] [oO] [nN]; PRECEDING : [pP] [rR] [eE] [cC] [eE] [dD] [iI] [nN] [gG]; QUARTER : [qQ] [uU] [aA] [rR] [tT] [eE] [rR]; RANGE : [rR] [aA] [nN] [gG] [eE]; +REGEXP : [rR] [eE] [gG] [eE] [xX] [pP]; RESPECT : [rR] [eE] [sS] [pP] [eE] [cC] [tT]; RETURNING : [rR] [eE] [tT] [uU] [rR] [nN] [iI] [nN] [gG]; RIGHT : [rR] [iI] [gG] [hH] [tT]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index 5db8339e9a72..9c413db73e21 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -669,7 +669,7 @@ predicate | expression NOT? MEMBER OF? path # MemberOfPredicate | expression NOT? IN inList # InPredicate | expression NOT? BETWEEN expression AND expression # BetweenPredicate - | expression NOT? (LIKE | ILIKE) expression likeEscape? # LikePredicate + | expression NOT? (LIKE | ILIKE) REGEXP? expression likeEscape? # LikePredicate | expression NOT? CONTAINS expression # ContainsPredicate | expression NOT? INCLUDES expression # IncludesPredicate | expression NOT? INTERSECTS expression # IntersectsPredicate @@ -1998,6 +1998,7 @@ xmltableDefaultClause | PRECEDING | QUARTER | RANGE + | REGEXP | RESPECT | RETURNING // | RIGHT diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index cc682349c41a..dd1224ea07d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -149,7 +149,6 @@ import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.hibernate.tool.schema.spi.TableMigrator; import org.hibernate.type.BasicType; -import org.hibernate.type.BasicTypeRegistry; import org.hibernate.type.SqlTypes; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.WrapperOptions; @@ -1103,8 +1102,8 @@ public int ordinal() { * functions with the same names. */ public void initializeFunctionRegistry(FunctionContributions functionContributions) { - final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration(); - final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); + final var typeConfiguration = functionContributions.getTypeConfiguration(); + final var basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); final BasicType timestampType = basicTypeRegistry.resolve( StandardBasicTypes.TIMESTAMP ); final BasicType dateType = basicTypeRegistry.resolve( StandardBasicTypes.DATE ); final BasicType timeType = basicTypeRegistry.resolve( StandardBasicTypes.TIME ); @@ -1114,6 +1113,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio final BasicType localTimeType = basicTypeRegistry.resolve( StandardBasicTypes.LOCAL_TIME ); final BasicType localDateType = basicTypeRegistry.resolve( StandardBasicTypes.LOCAL_DATE ); + final var functionRegistry = functionContributions.getFunctionRegistry(); final var functionFactory = new CommonFunctionFactory( functionContributions ); //standard aggregate functions count(), sum(), max(), min(), avg(), @@ -1196,20 +1196,20 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio //only some databases support the ANSI SQL-style position() function, so //define it here as an alias for locate() - functionContributions.getFunctionRegistry().register( "position", + functionRegistry.register( "position", new LocatePositionEmulation( typeConfiguration ) ); //very few databases support ANSI-style overlay() function, so emulate //it here in terms of either insert() or concat()/substring() - functionContributions.getFunctionRegistry().register( "overlay", + functionRegistry.register( "overlay", new InsertSubstringOverlayEmulation( typeConfiguration, false ) ); //ANSI SQL trim() function is supported on almost all of the databases //we care about, but on some it must be emulated using ltrim(), rtrim(), //and replace() - functionContributions.getFunctionRegistry().register( "trim", + functionRegistry.register( "trim", new TrimFunction( this, typeConfiguration ) ); //ANSI SQL cast() function is supported on the databases we care most @@ -1220,7 +1220,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio // - casts to and from Boolean, and // - casting Double or Float to String. - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "cast", new CastFunction( this, @@ -1239,7 +1239,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio //additional non-standard temporal field types, which must be emulated in //a very dialect-specific way - functionContributions.getFunctionRegistry().register( "extract", + functionRegistry.register( "extract", new ExtractFunction( this, typeConfiguration ) ); //comparison functions supported on most databases, emulated on others @@ -1250,7 +1250,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio //two-argument synonym for coalesce() supported on most but not every //database, so define it here as an alias for coalesce(arg1,arg2) - functionContributions.getFunctionRegistry().register( "ifnull", + functionRegistry.register( "ifnull", new CoalesceIfnullEmulation() ); //rpad() and pad() are supported on almost every database, and emulated @@ -1261,23 +1261,23 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio //pad() is a function we've designed to look like ANSI trim() - functionContributions.getFunctionRegistry().register( "pad", + functionRegistry.register( "pad", new LpadRpadPadEmulation( typeConfiguration ) ); //legacy Hibernate convenience function for casting to string, defined //here as an alias for cast(arg as String) - functionContributions.getFunctionRegistry().register( "str", + functionRegistry.register( "str", new CastStrEmulation( typeConfiguration ) ); // Function to convert enum mapped as Ordinal to their ordinal value - functionContributions.getFunctionRegistry().register( "ordinal", + functionRegistry.register( "ordinal", new OrdinalFunction( typeConfiguration ) ); // Function to convert enum mapped as String to their string value - functionContributions.getFunctionRegistry().register( "string", + functionRegistry.register( "string", new StringFunction( typeConfiguration ) ); //format() function for datetimes, emulated on many databases using the @@ -1290,13 +1290,13 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio //since there is a great variety of different ways to emulate them //by default, we don't allow plain parameters for the timestamp argument as most database don't support this functionFactory.timestampaddAndDiff( this, SqlAstNodeRenderingMode.NO_PLAIN_PARAMETER ); - functionContributions.getFunctionRegistry().registerAlternateKey( "dateadd", "timestampadd" ); - functionContributions.getFunctionRegistry().registerAlternateKey( "datediff", "timestampdiff" ); + functionRegistry.registerAlternateKey( "dateadd", "timestampadd" ); + functionRegistry.registerAlternateKey( "datediff", "timestampdiff" ); //ANSI SQL (and JPA) current date/time/timestamp functions, supported //natively on almost every database, delegated back to the Dialect - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "current_date", new CurrentFunction( "current_date", @@ -1304,7 +1304,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio dateType ) ); - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "current_time", new CurrentFunction( "current_time", @@ -1312,7 +1312,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio timeType ) ); - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "current_timestamp", new CurrentFunction( "current_timestamp", @@ -1320,12 +1320,12 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio timestampType ) ); - functionContributions.getFunctionRegistry().registerAlternateKey( "current date", "current_date" ); - functionContributions.getFunctionRegistry().registerAlternateKey( "current time", "current_time" ); - functionContributions.getFunctionRegistry().registerAlternateKey( "current timestamp", "current_timestamp" ); + functionRegistry.registerAlternateKey( "current date", "current_date" ); + functionRegistry.registerAlternateKey( "current time", "current_time" ); + functionRegistry.registerAlternateKey( "current timestamp", "current_timestamp" ); //HQL current instant/date/time/datetime functions, delegated back to the Dialect - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "local_date", new CurrentFunction( "local_date", @@ -1333,7 +1333,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio localDateType ) ); - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "local_time", new CurrentFunction( "local_time", @@ -1341,7 +1341,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio localTimeType ) ); - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "local_datetime", new CurrentFunction( "local_datetime", @@ -1349,7 +1349,7 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio localDateTimeType ) ); - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "offset_datetime", new CurrentFunction( "offset_datetime", @@ -1357,12 +1357,12 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio offsetDateTimeType ) ); - functionContributions.getFunctionRegistry().registerAlternateKey( "local date", "local_date" ); - functionContributions.getFunctionRegistry().registerAlternateKey( "local time", "local_time" ); - functionContributions.getFunctionRegistry().registerAlternateKey( "local datetime", "local_datetime" ); - functionContributions.getFunctionRegistry().registerAlternateKey( "offset datetime", "offset_datetime" ); + functionRegistry.registerAlternateKey( "local date", "local_date" ); + functionRegistry.registerAlternateKey( "local time", "local_time" ); + functionRegistry.registerAlternateKey( "local datetime", "local_datetime" ); + functionRegistry.registerAlternateKey( "offset datetime", "offset_datetime" ); - functionContributions.getFunctionRegistry().register( + functionRegistry.register( "instant", new CurrentFunction( "instant", @@ -1370,9 +1370,11 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio instantType ) ); - functionContributions.getFunctionRegistry().registerAlternateKey( "current_instant", "instant" ); //deprecated legacy! + functionRegistry.registerAlternateKey( "current_instant", "instant" ); //deprecated legacy! - functionContributions.getFunctionRegistry().register( "sql", new SqlFunction() ); + functionRegistry.register( "sql", new SqlFunction() ); + + functionFactory.regexpLike(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java index e2527fa8193e..850056742042 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java @@ -45,7 +45,6 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.SqlTypes; -import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.VarcharUUIDJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; @@ -59,11 +58,11 @@ import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState; -import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC; import static org.hibernate.type.SqlTypes.GEOMETRY; import static org.hibernate.type.SqlTypes.OTHER; import static org.hibernate.type.SqlTypes.UUID; import static org.hibernate.type.SqlTypes.VARBINARY; +import static org.hibernate.type.StandardBasicTypes.BOOLEAN; /** * A {@linkplain Dialect SQL dialect} for MariaDB 10.6 and above. @@ -121,15 +120,20 @@ public NationalizationSupport getNationalizationSupport() { public void initializeFunctionRegistry(FunctionContributions functionContributions) { super.initializeFunctionRegistry( functionContributions ); + final var functionRegistry = functionContributions.getFunctionRegistry(); final var commonFunctionFactory = new CommonFunctionFactory( functionContributions ); + final var basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry(); commonFunctionFactory.windowFunctions(); commonFunctionFactory.hypotheticalOrderedSetAggregates_windowEmulation(); - functionContributions.getFunctionRegistry().registerNamed( + commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation(); + commonFunctionFactory.median_medianOver(); + + commonFunctionFactory.regexpLike_regexp(); + + functionRegistry.registerNamed( "json_valid", - functionContributions.getTypeConfiguration() - .getBasicTypeRegistry() - .resolve( StandardBasicTypes.BOOLEAN ) + basicTypeRegistry.resolve( BOOLEAN ) ); commonFunctionFactory.jsonValue_mariadb(); commonFunctionFactory.jsonArray_mariadb(); @@ -139,13 +143,6 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio commonFunctionFactory.jsonArrayAppend_mariadb(); commonFunctionFactory.unnest_emulated(); commonFunctionFactory.jsonTable_mysql(); - - commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation(); - functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" ) - .setInvariantType( functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) ) - .setExactArgumentCount( 1 ) - .setParameterTypes(NUMERIC) - .register(); } @Override @@ -332,11 +329,6 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, D return super.buildIdentifierHelper( builder, metadata ); } - @Override - public String getDual() { - return "dual"; - } - public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() { return EXTRACTOR; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index e1ed5a09f6d8..4f0b939a6af6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -403,6 +403,20 @@ public void median_percentileCont_castDouble() { .register(); } + /** + * For MariaDB + */ + public void median_medianOver() { + functionRegistry.patternDescriptorBuilder( + "median", + "median(?1) over ()" + ) + .setInvariantType(doubleType) + .setExactArgumentCount( 1 ) + .setParameterTypes(NUMERIC) + .register(); + } + /** * Warning: the semantics of this function are inconsistent between DBs. *
    @@ -2651,6 +2665,25 @@ public void dateTrunc_datetrunc() { .register(); } + public void regexpLike() { + functionRegistry.namedDescriptorBuilder( "regexp_like" ) + .setArgumentCountBetween( 2, 3 ) + .setParameterTypes( STRING, STRING, STRING ) + .setInvariantType( booleanType ) + .register(); + + } + + /** + * For MariaDB + */ + public void regexpLike_regexp() { + functionRegistry.patternDescriptorBuilder( "regexp_like", "?1 regexp ?2" ) + .setParameterTypes( STRING, STRING ) + .setInvariantType( booleanType ) + .register(); + } + /** * H2, HSQL array() constructor function */ 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 be7845bc1ab4..299bee0ef876 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 @@ -3222,10 +3222,34 @@ public SqmPredicate visitIntersectsPredicate(HqlParser.IntersectsPredicateContex public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) { final boolean negated = ctx.NOT() != null; final boolean caseSensitive = ctx.LIKE() != null; - if ( ctx.likeEscape() == null ) { + final SqmExpression expression = (SqmExpression) ctx.expression( 0 ).accept( this ); + final SqmExpression pattern = (SqmExpression) ctx.expression( 1 ).accept( this ); + if ( ctx.REGEXP() != null ) { + if ( ctx.likeEscape() != null ) { + throw new SemanticException( "'ESCAPE' may not be used with 'LIKE REGEXP'", query ); + } + return new SqmBooleanExpressionPredicate( + getFunctionDescriptor( "regexp_like" ) + .generateSqmExpression( + caseSensitive + ? asList( expression, pattern ) + : asList( expression, pattern, + new SqmLiteral<>( "i", + resolveExpressibleTypeBasic( String.class ), + nodeBuilder() + ) + ), + null, + queryEngine() + ), + negated, + nodeBuilder() + ); + } + else if ( ctx.likeEscape() == null ) { return new SqmLikePredicate( - (SqmExpression) ctx.expression(0).accept( this ), - (SqmExpression) ctx.expression(1).accept( this ), + expression, + pattern, negated, caseSensitive, nodeBuilder() @@ -3233,8 +3257,8 @@ public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) { } else { return new SqmLikePredicate( - (SqmExpression) ctx.expression(0).accept( this ), - (SqmExpression) ctx.expression(1).accept( this ), + expression, + pattern, (SqmExpression) ctx.likeEscape().accept( this ), negated, caseSensitive, @@ -4338,9 +4362,8 @@ private SqmLiteral stringLiteral(String text) { } private SqmLiteral javaStringLiteral(String text) { - String unquoted = unquoteJavaStringLiteral( text ); return new SqmLiteral<>( - unquoted, + unquoteJavaStringLiteral( text ), resolveExpressibleTypeBasic( String.class ), nodeBuilder() ); 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 new file mode 100644 index 000000000000..faa9ff706e36 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/RegexTest.java @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.query.hql; + +import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASEDialect; +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.assertTrue; + +@Jpa +class RegexTest { + @Test + @SkipForDialect(dialectClass = SQLServerDialect.class, + reason = "regexp_like coming in 2025") + @SkipForDialect(dialectClass = SybaseASEDialect.class, + reason = "no regex support in Sybase ASE") + void testInSelect(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.*')", Boolean.class ).getSingleResult() ); + assertTrue( em.createQuery( "select 'abcdef' like regexp 'ab.*'", Boolean.class ).getSingleResult() ); + } ); + } + } + + @Test + @SkipForDialect(dialectClass = MariaDBDialect.class) + @SkipForDialect(dialectClass = HSQLDialect.class) + @SkipForDialect(dialectClass = SQLServerDialect.class, + reason = "regexp_like coming in 2025") + @SkipForDialect(dialectClass = SybaseASEDialect.class, + reason = "no regex support in Sybase ASE") + 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 'abcdef' ilike regexp 'ab.*'", Boolean.class ).getSingleResult() ); + } ); + } + } + + @Test + @SkipForDialect(dialectClass = SQLServerDialect.class, + reason = "regexp_like coming in 2025") + @SkipForDialect(dialectClass = SybaseASEDialect.class, + reason = "no regex support in Sybase ASE") + void testInWhere(EntityManagerFactoryScope scope) { + scope.inEntityManager( em -> { + assertEquals( 1, em.createQuery( "select 1 where regexp_like('abcdef', 'ab.*')", Integer.class ).getSingleResult() ); + assertEquals( 1, em.createQuery( "select 1 where 'abcdef' like regexp 'ab.*'", Integer.class ).getSingleResult() ); + } ); + } +}