Skip to content

Commit 8250f13

Browse files
committed
HHH-17639 Make recursive CTE cycle detection emulation independent of collation
1 parent 899bf7b commit 8250f13

File tree

8 files changed

+93
-8
lines changed

8 files changed

+93
-8
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacySqlAstTranslator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,13 @@ public void visitCastTarget(CastTarget castTarget) {
250250
super.visitCastTarget( castTarget );
251251
}
252252
}
253+
254+
@Override
255+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
256+
// MariaDB can't cope with NUL characters in the position function, so we use a like predicate instead
257+
haystack.accept( this );
258+
appendSql( " like concat('%',replace(replace(replace(" );
259+
needle.accept( this );
260+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
261+
}
253262
}

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacySqlAstTranslator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,13 @@ public void visitCastTarget(CastTarget castTarget) {
258258
super.visitCastTarget( castTarget );
259259
}
260260
}
261+
262+
@Override
263+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
264+
// MySQL can't cope with NUL characters in the position function, so we use a like predicate instead
265+
haystack.accept( this );
266+
appendSql( " like concat('%',replace(replace(replace(" );
267+
needle.accept( this );
268+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
269+
}
261270
}

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacySqlAstTranslator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,4 +470,15 @@ enum OffsetFetchClauseMode {
470470
TOP_ONLY,
471471
EMULATED;
472472
}
473+
474+
@Override
475+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
476+
// SQL Server ignores NUL characters in string on case-insensitive collations, so we force a binary collation.
477+
// This is needed for the emulation of cycle detection in recursive queries
478+
appendSql( "charindex(" );
479+
needle.accept( this );
480+
appendSql( " collate Latin1_General_100_BIN2," );
481+
haystack.accept( this );
482+
append( ")>0" );
483+
}
473484
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,13 @@ public void visitCastTarget(CastTarget castTarget) {
240240
}
241241
}
242242

243+
@Override
244+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
245+
// MariaDB can't cope with NUL characters in the position function, so we use a like predicate instead
246+
haystack.accept( this );
247+
appendSql( " like concat('%',replace(replace(replace(" );
248+
needle.accept( this );
249+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
250+
}
251+
243252
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,13 @@ public void visitCastTarget(CastTarget castTarget) {
294294
super.visitCastTarget( castTarget );
295295
}
296296
}
297+
298+
@Override
299+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
300+
// MySQL can't cope with NUL characters in the position function, so we use a like predicate instead
301+
haystack.accept( this );
302+
appendSql( " like concat('%',replace(replace(replace(" );
303+
needle.accept( this );
304+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
305+
}
297306
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
1515
import org.hibernate.query.sqm.ComparisonOperator;
1616
import org.hibernate.query.sqm.FetchClauseType;
17+
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
18+
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
1719
import org.hibernate.sql.ast.Clause;
1820
import org.hibernate.sql.ast.SqlAstJoinType;
1921
import org.hibernate.sql.ast.spi.SqlSelection;
@@ -451,4 +453,15 @@ protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) {
451453
super.renderMergeStatement( optionalTableUpdate );
452454
appendSql( ";" );
453455
}
456+
457+
@Override
458+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
459+
// SQL Server ignores NUL characters in string on case-insensitive collations, so we force a binary collation.
460+
// This is needed for the emulation of cycle detection in recursive queries
461+
appendSql( "charindex(" );
462+
needle.accept( this );
463+
appendSql( " collate Latin1_General_100_BIN2," );
464+
haystack.accept( this );
465+
append( ")>0" );
466+
}
454467
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,13 @@ public void visitCastTarget(CastTarget castTarget) {
201201
super.visitCastTarget( castTarget );
202202
}
203203
}
204+
205+
@Override
206+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
207+
// TiDB can't cope with NUL characters in the position function, so we use a like predicate instead
208+
haystack.accept( this );
209+
appendSql( " like concat('%',replace(replace(replace(" );
210+
needle.accept( this );
211+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
212+
}
204213
}

hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,7 +2660,6 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
26602660
null,
26612661
stringType
26622662
);
2663-
arguments.add( new QueryLiteral<>( "%", stringType ) );
26642663
for ( CteColumn cycleColumn : currentCteStatement.getCycleColumns() ) {
26652664
final int selectionIndex = currentCteStatement.getCteTable()
26662665
.getCteColumns()
@@ -2683,14 +2682,20 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
26832682
arguments.add( nullSeparator );
26842683
}
26852684
arguments.add( nullSeparator );
2686-
arguments.add( new QueryLiteral<>( "%", stringType ) );
26872685

26882686
if ( !supportsRecursiveCycleClause() ) {
26892687
// Cycle mark
26902688
appendSql( "case when " );
2691-
visitColumnReference( cyclePathColumnReference );
2692-
appendSql( " like " );
2693-
concat.render( this, arguments, stringType, this );
2689+
renderStringContainsExactlyPredicate(
2690+
cyclePathColumnReference,
2691+
new SelfRenderingFunctionSqlAstExpression(
2692+
"concat",
2693+
concat,
2694+
arguments,
2695+
stringType,
2696+
stringType
2697+
)
2698+
);
26942699
appendSql( " then " );
26952700
currentCteStatement.getCycleValue().accept( this );
26962701
appendSql( " else " );
@@ -2699,9 +2704,8 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
26992704
appendSql( COMMA_SEPARATOR );
27002705
}
27012706

2702-
// Remove the wildcard literals
2703-
arguments.remove( arguments.size() - 1 );
2704-
arguments.set( 0, cyclePathColumnReference );
2707+
// Add the previous path
2708+
arguments.add( 0, cyclePathColumnReference );
27052709
// Cycle path
27062710
concat.render( this, arguments, stringType, this );
27072711
}
@@ -2750,6 +2754,18 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
27502754
}
27512755
}
27522756

2757+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
2758+
final AbstractSqmSelfRenderingFunctionDescriptor position = findSelfRenderingFunction( "position", 2 );
2759+
new SelfRenderingFunctionSqlAstExpression(
2760+
"position",
2761+
position,
2762+
List.of( needle, haystack ),
2763+
getStringType(),
2764+
getStringType()
2765+
).accept( this );
2766+
append( ">0" );
2767+
}
2768+
27532769
/**
27542770
* Wraps the given expression so that it produces a string, which should have the same ordering as the original value.
27552771
* Here are the mappings for various data types:

0 commit comments

Comments
 (0)