Skip to content

Commit ea7c8c6

Browse files
committed
HHH-17639 Make recursive CTE cycle detection emulation independent of collation
1 parent dfa9cd5 commit ea7c8c6

File tree

8 files changed

+95
-8
lines changed

8 files changed

+95
-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
@@ -395,4 +395,13 @@ public void visitCastTarget(CastTarget castTarget) {
395395
super.visitCastTarget( castTarget );
396396
}
397397
}
398+
399+
@Override
400+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
401+
// MariaDB can't cope with NUL characters in the position function, so we use a like predicate instead
402+
haystack.accept( this );
403+
appendSql( " like concat('%',replace(replace(replace(" );
404+
needle.accept( this );
405+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
406+
}
398407
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import org.hibernate.engine.spi.SessionFactoryImplementor;
1616
import org.hibernate.internal.util.collections.Stack;
1717
import org.hibernate.query.sqm.ComparisonOperator;
18+
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
19+
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
1820
import org.hibernate.sql.ast.Clause;
1921
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
2022
import org.hibernate.sql.ast.tree.MutationStatement;
@@ -414,4 +416,13 @@ public void visitCastTarget(CastTarget castTarget) {
414416
super.visitCastTarget( castTarget );
415417
}
416418
}
419+
420+
@Override
421+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
422+
// MySQL can't cope with NUL characters in the position function, so we use a like predicate instead
423+
haystack.accept( this );
424+
appendSql( " like concat('%',replace(replace(replace(" );
425+
needle.accept( this );
426+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
427+
}
417428
}

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
@@ -549,4 +549,15 @@ enum OffsetFetchClauseMode {
549549
TOP_ONLY,
550550
EMULATED;
551551
}
552+
553+
@Override
554+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
555+
// SQL Server ignores NUL characters in string on case-insensitive collations, so we force a binary collation.
556+
// This is needed for the emulation of cycle detection in recursive queries
557+
appendSql( "charindex(" );
558+
needle.accept( this );
559+
appendSql( " collate Latin1_General_100_BIN2," );
560+
haystack.accept( this );
561+
append( ")>0" );
562+
}
552563
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,13 @@ public void visitCastTarget(CastTarget castTarget) {
384384
}
385385
}
386386

387+
@Override
388+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
389+
// MariaDB can't cope with NUL characters in the position function, so we use a like predicate instead
390+
haystack.accept( this );
391+
appendSql( " like concat('%',replace(replace(replace(" );
392+
needle.accept( this );
393+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
394+
}
395+
387396
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,4 +448,13 @@ public void visitCastTarget(CastTarget castTarget) {
448448
super.visitCastTarget( castTarget );
449449
}
450450
}
451+
452+
@Override
453+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
454+
// MySQL can't cope with NUL characters in the position function, so we use a like predicate instead
455+
haystack.accept( this );
456+
appendSql( " like concat('%',replace(replace(replace(" );
457+
needle.accept( this );
458+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
459+
}
451460
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import org.hibernate.query.IllegalQueryOperationException;
1717
import org.hibernate.query.sqm.ComparisonOperator;
1818
import org.hibernate.query.sqm.FetchClauseType;
19+
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
20+
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
1921
import org.hibernate.sql.ast.Clause;
2022
import org.hibernate.sql.ast.SqlAstJoinType;
2123
import org.hibernate.sql.ast.spi.SqlSelection;
@@ -531,4 +533,15 @@ protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) {
531533
super.renderMergeStatement( optionalTableUpdate );
532534
appendSql( ";" );
533535
}
536+
537+
@Override
538+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
539+
// SQL Server ignores NUL characters in string on case-insensitive collations, so we force a binary collation.
540+
// This is needed for the emulation of cycle detection in recursive queries
541+
appendSql( "charindex(" );
542+
needle.accept( this );
543+
appendSql( " collate Latin1_General_100_BIN2," );
544+
haystack.accept( this );
545+
append( ")>0" );
546+
}
534547
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,13 @@ public void visitCastTarget(CastTarget castTarget) {
345345
super.visitCastTarget( castTarget );
346346
}
347347
}
348+
349+
@Override
350+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
351+
// TiDB can't cope with NUL characters in the position function, so we use a like predicate instead
352+
haystack.accept( this );
353+
appendSql( " like concat('%',replace(replace(replace(" );
354+
needle.accept( this );
355+
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
356+
}
348357
}

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
@@ -3167,7 +3167,6 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
31673167
null,
31683168
stringType
31693169
);
3170-
arguments.add( new QueryLiteral<>( "%", stringType ) );
31713170
for ( CteColumn cycleColumn : currentCteStatement.getCycleColumns() ) {
31723171
final int selectionIndex = currentCteStatement.getCteTable()
31733172
.getCteColumns()
@@ -3190,14 +3189,20 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
31903189
arguments.add( nullSeparator );
31913190
}
31923191
arguments.add( nullSeparator );
3193-
arguments.add( new QueryLiteral<>( "%", stringType ) );
31943192

31953193
if ( !supportsRecursiveCycleClause() ) {
31963194
// Cycle mark
31973195
appendSql( "case when " );
3198-
visitColumnReference( cyclePathColumnReference );
3199-
appendSql( " like " );
3200-
concat.render( this, arguments, stringType, this );
3196+
renderStringContainsExactlyPredicate(
3197+
cyclePathColumnReference,
3198+
new SelfRenderingFunctionSqlAstExpression(
3199+
"concat",
3200+
concat,
3201+
arguments,
3202+
stringType,
3203+
stringType
3204+
)
3205+
);
32013206
appendSql( " then " );
32023207
currentCteStatement.getCycleValue().accept( this );
32033208
appendSql( " else " );
@@ -3206,9 +3211,8 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
32063211
appendSql( COMMA_SEPARATOR );
32073212
}
32083213

3209-
// Remove the wildcard literals
3210-
arguments.remove( arguments.size() - 1 );
3211-
arguments.set( 0, cyclePathColumnReference );
3214+
// Add the previous path
3215+
arguments.add( 0, cyclePathColumnReference );
32123216
// Cycle path
32133217
concat.render( this, arguments, stringType, this );
32143218
}
@@ -3257,6 +3261,18 @@ private void emulateCycleClauseWithString(SelectClause selectClause) {
32573261
}
32583262
}
32593263

3264+
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
3265+
final AbstractSqmSelfRenderingFunctionDescriptor position = findSelfRenderingFunction( "position", 2 );
3266+
new SelfRenderingFunctionSqlAstExpression(
3267+
"position",
3268+
position,
3269+
List.of( needle, haystack ),
3270+
getStringType(),
3271+
getStringType()
3272+
).accept( this );
3273+
append( ">0" );
3274+
}
3275+
32603276
/**
32613277
* Wraps the given expression so that it produces a string, which should have the same ordering as the original value.
32623278
* Here are the mappings for various data types:

0 commit comments

Comments
 (0)