Skip to content

Commit e0ba29b

Browse files
committed
HHH-3404 support for regular expressions in HQL LIKE operator
1 parent e0d8ed7 commit e0ba29b

File tree

4 files changed

+105
-19
lines changed

4 files changed

+105
-19
lines changed

hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@
149149
import org.hibernate.tool.schema.spi.SchemaManagementTool;
150150
import org.hibernate.tool.schema.spi.TableMigrator;
151151
import org.hibernate.type.BasicType;
152-
import org.hibernate.type.BasicTypeRegistry;
153152
import org.hibernate.type.SqlTypes;
154153
import org.hibernate.type.StandardBasicTypes;
155154
import org.hibernate.type.descriptor.WrapperOptions;
@@ -1374,6 +1373,8 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
13741373
functionRegistry.registerAlternateKey( "current_instant", "instant" ); //deprecated legacy!
13751374

13761375
functionRegistry.register( "sql", new SqlFunction() );
1376+
1377+
functionFactory.regexpLike();
13771378
}
13781379

13791380
/**

hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl;
4646
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
4747
import org.hibernate.type.SqlTypes;
48-
import org.hibernate.type.StandardBasicTypes;
4948
import org.hibernate.type.descriptor.jdbc.JdbcType;
5049
import org.hibernate.type.descriptor.jdbc.VarcharUUIDJdbcType;
5150
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
@@ -59,11 +58,11 @@
5958

6059
import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate;
6160
import static org.hibernate.internal.util.JdbcExceptionHelper.extractSqlState;
62-
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC;
6361
import static org.hibernate.type.SqlTypes.GEOMETRY;
6462
import static org.hibernate.type.SqlTypes.OTHER;
6563
import static org.hibernate.type.SqlTypes.UUID;
6664
import static org.hibernate.type.SqlTypes.VARBINARY;
65+
import static org.hibernate.type.StandardBasicTypes.BOOLEAN;
6766

6867
/**
6968
* A {@linkplain Dialect SQL dialect} for MariaDB 10.6 and above.
@@ -121,15 +120,20 @@ public NationalizationSupport getNationalizationSupport() {
121120
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
122121
super.initializeFunctionRegistry( functionContributions );
123122

123+
final var functionRegistry = functionContributions.getFunctionRegistry();
124124
final var commonFunctionFactory = new CommonFunctionFactory( functionContributions );
125+
final var basicTypeRegistry = functionContributions.getTypeConfiguration().getBasicTypeRegistry();
125126

126127
commonFunctionFactory.windowFunctions();
127128
commonFunctionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
128-
functionContributions.getFunctionRegistry().registerNamed(
129+
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
130+
commonFunctionFactory.median_medianOver();
131+
132+
commonFunctionFactory.regexpLike_regexp();
133+
134+
functionRegistry.registerNamed(
129135
"json_valid",
130-
functionContributions.getTypeConfiguration()
131-
.getBasicTypeRegistry()
132-
.resolve( StandardBasicTypes.BOOLEAN )
136+
basicTypeRegistry.resolve( BOOLEAN )
133137
);
134138
commonFunctionFactory.jsonValue_mariadb();
135139
commonFunctionFactory.jsonArray_mariadb();
@@ -139,13 +143,6 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio
139143
commonFunctionFactory.jsonArrayAppend_mariadb();
140144
commonFunctionFactory.unnest_emulated();
141145
commonFunctionFactory.jsonTable_mysql();
142-
143-
commonFunctionFactory.inverseDistributionOrderedSetAggregates_windowEmulation();
144-
functionContributions.getFunctionRegistry().patternDescriptorBuilder( "median", "median(?1) over ()" )
145-
.setInvariantType( functionContributions.getTypeConfiguration().getBasicTypeRegistry().resolve( StandardBasicTypes.DOUBLE ) )
146-
.setExactArgumentCount( 1 )
147-
.setParameterTypes(NUMERIC)
148-
.register();
149146
}
150147

151148
@Override
@@ -332,11 +329,6 @@ public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, D
332329
return super.buildIdentifierHelper( builder, metadata );
333330
}
334331

335-
@Override
336-
public String getDual() {
337-
return "dual";
338-
}
339-
340332
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
341333
return EXTRACTOR;
342334
}

hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,20 @@ public void median_percentileCont_castDouble() {
403403
.register();
404404
}
405405

406+
/**
407+
* For MariaDB
408+
*/
409+
public void median_medianOver() {
410+
functionRegistry.patternDescriptorBuilder(
411+
"median",
412+
"median(?1) over ()"
413+
)
414+
.setInvariantType(doubleType)
415+
.setExactArgumentCount( 1 )
416+
.setParameterTypes(NUMERIC)
417+
.register();
418+
}
419+
406420
/**
407421
* Warning: the semantics of this function are inconsistent between DBs.
408422
* <ul>
@@ -2651,6 +2665,25 @@ public void dateTrunc_datetrunc() {
26512665
.register();
26522666
}
26532667

2668+
public void regexpLike() {
2669+
functionRegistry.namedDescriptorBuilder( "regexp_like" )
2670+
.setArgumentCountBetween( 2, 3 )
2671+
.setParameterTypes( STRING, STRING, STRING )
2672+
.setInvariantType( booleanType )
2673+
.register();
2674+
2675+
}
2676+
2677+
/**
2678+
* For MariaDB
2679+
*/
2680+
public void regexpLike_regexp() {
2681+
functionRegistry.patternDescriptorBuilder( "regexp_like", "?1 regexp ?2" )
2682+
.setParameterTypes( STRING, STRING )
2683+
.setInvariantType( booleanType )
2684+
.register();
2685+
}
2686+
26542687
/**
26552688
* H2, HSQL array() constructor function
26562689
*/
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.query.hql;
6+
7+
import org.hibernate.dialect.HSQLDialect;
8+
import org.hibernate.dialect.MariaDBDialect;
9+
import org.hibernate.dialect.OracleDialect;
10+
import org.hibernate.dialect.SQLServerDialect;
11+
import org.hibernate.dialect.SybaseASEDialect;
12+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
13+
import org.hibernate.testing.orm.junit.Jpa;
14+
import org.hibernate.testing.orm.junit.SkipForDialect;
15+
import org.junit.jupiter.api.Test;
16+
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
19+
20+
@Jpa
21+
class RegexTest {
22+
@Test
23+
@SkipForDialect(dialectClass = SQLServerDialect.class,
24+
reason = "regexp_like coming in 2025")
25+
@SkipForDialect(dialectClass = SybaseASEDialect.class,
26+
reason = "no regex support in Sybase ASE")
27+
void testInSelect(EntityManagerFactoryScope scope) {
28+
if ( !( scope.getDialect() instanceof OracleDialect dialect
29+
&& ( dialect.isAutonomous() || dialect.getVersion().isBefore( 23 ) ) ) ) {
30+
scope.inEntityManager( em -> {
31+
assertTrue( em.createQuery( "select regexp_like('abcdef', 'ab.*')", Boolean.class ).getSingleResult() );
32+
assertTrue( em.createQuery( "select 'abcdef' like regexp 'ab.*'", Boolean.class ).getSingleResult() );
33+
} );
34+
}
35+
}
36+
37+
@Test
38+
@SkipForDialect(dialectClass = MariaDBDialect.class)
39+
@SkipForDialect(dialectClass = HSQLDialect.class)
40+
@SkipForDialect(dialectClass = SQLServerDialect.class,
41+
reason = "regexp_like coming in 2025")
42+
@SkipForDialect(dialectClass = SybaseASEDialect.class,
43+
reason = "no regex support in Sybase ASE")
44+
void testInSelectCaseInsensitive(EntityManagerFactoryScope scope) {
45+
scope.inEntityManager( em -> {
46+
assertTrue( em.createQuery( "select regexp_like('ABCDEF', 'ab.*', 'i')", Boolean.class ).getSingleResult() );
47+
} );
48+
}
49+
50+
@Test
51+
@SkipForDialect(dialectClass = SQLServerDialect.class,
52+
reason = "regexp_like coming in 2025")
53+
@SkipForDialect(dialectClass = SybaseASEDialect.class,
54+
reason = "no regex support in Sybase ASE")
55+
void testInWhere(EntityManagerFactoryScope scope) {
56+
scope.inEntityManager( em -> {
57+
assertEquals( 1, em.createQuery( "select 1 where regexp_like('abcdef', 'ab.*')", Integer.class ).getSingleResult() );
58+
} );
59+
}
60+
}

0 commit comments

Comments
 (0)