HHH-17639 Make recursive CTE cycle detection emulation independent of collation

This commit is contained in:
Christian Beikov 2024-01-12 12:36:02 +01:00
parent 899bf7b4fb
commit 8250f13d77
8 changed files with 93 additions and 8 deletions

View File

@ -250,4 +250,13 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
super.visitCastTarget( castTarget );
}
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// MariaDB can't cope with NUL characters in the position function, so we use a like predicate instead
haystack.accept( this );
appendSql( " like concat('%',replace(replace(replace(" );
needle.accept( this );
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
}
}

View File

@ -258,4 +258,13 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
super.visitCastTarget( castTarget );
}
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// MySQL can't cope with NUL characters in the position function, so we use a like predicate instead
haystack.accept( this );
appendSql( " like concat('%',replace(replace(replace(" );
needle.accept( this );
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
}
}

View File

@ -470,4 +470,15 @@ public class SQLServerLegacySqlAstTranslator<T extends JdbcOperation> extends Ab
TOP_ONLY,
EMULATED;
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// SQL Server ignores NUL characters in string on case-insensitive collations, so we force a binary collation.
// This is needed for the emulation of cycle detection in recursive queries
appendSql( "charindex(" );
needle.accept( this );
appendSql( " collate Latin1_General_100_BIN2," );
haystack.accept( this );
append( ")>0" );
}
}

View File

@ -240,4 +240,13 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
}
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// MariaDB can't cope with NUL characters in the position function, so we use a like predicate instead
haystack.accept( this );
appendSql( " like concat('%',replace(replace(replace(" );
needle.accept( this );
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
}
}

View File

@ -294,4 +294,13 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
super.visitCastTarget( castTarget );
}
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// MySQL can't cope with NUL characters in the position function, so we use a like predicate instead
haystack.accept( this );
appendSql( " like concat('%',replace(replace(replace(" );
needle.accept( this );
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
}
}

View File

@ -14,6 +14,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.query.sqm.FetchClauseType;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlSelection;
@ -451,4 +453,15 @@ public class SQLServerSqlAstTranslator<T extends JdbcOperation> extends SqlAstTr
super.renderMergeStatement( optionalTableUpdate );
appendSql( ";" );
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// SQL Server ignores NUL characters in string on case-insensitive collations, so we force a binary collation.
// This is needed for the emulation of cycle detection in recursive queries
appendSql( "charindex(" );
needle.accept( this );
appendSql( " collate Latin1_General_100_BIN2," );
haystack.accept( this );
append( ")>0" );
}
}

View File

@ -201,4 +201,13 @@ public class TiDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
super.visitCastTarget( castTarget );
}
}
@Override
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
// TiDB can't cope with NUL characters in the position function, so we use a like predicate instead
haystack.accept( this );
appendSql( " like concat('%',replace(replace(replace(" );
needle.accept( this );
appendSql( ",'~','~~'),'?','~?'),'%','~%'),'%') escape '~'" );
}
}

View File

@ -2660,7 +2660,6 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
null,
stringType
);
arguments.add( new QueryLiteral<>( "%", stringType ) );
for ( CteColumn cycleColumn : currentCteStatement.getCycleColumns() ) {
final int selectionIndex = currentCteStatement.getCteTable()
.getCteColumns()
@ -2683,14 +2682,20 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
arguments.add( nullSeparator );
}
arguments.add( nullSeparator );
arguments.add( new QueryLiteral<>( "%", stringType ) );
if ( !supportsRecursiveCycleClause() ) {
// Cycle mark
appendSql( "case when " );
visitColumnReference( cyclePathColumnReference );
appendSql( " like " );
concat.render( this, arguments, stringType, this );
renderStringContainsExactlyPredicate(
cyclePathColumnReference,
new SelfRenderingFunctionSqlAstExpression(
"concat",
concat,
arguments,
stringType,
stringType
)
);
appendSql( " then " );
currentCteStatement.getCycleValue().accept( this );
appendSql( " else " );
@ -2699,9 +2704,8 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
appendSql( COMMA_SEPARATOR );
}
// Remove the wildcard literals
arguments.remove( arguments.size() - 1 );
arguments.set( 0, cyclePathColumnReference );
// Add the previous path
arguments.add( 0, cyclePathColumnReference );
// Cycle path
concat.render( this, arguments, stringType, this );
}
@ -2750,6 +2754,18 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
protected void renderStringContainsExactlyPredicate(Expression haystack, Expression needle) {
final AbstractSqmSelfRenderingFunctionDescriptor position = findSelfRenderingFunction( "position", 2 );
new SelfRenderingFunctionSqlAstExpression(
"position",
position,
List.of( needle, haystack ),
getStringType(),
getStringType()
).accept( this );
append( ">0" );
}
/**
* Wraps the given expression so that it produces a string, which should have the same ordering as the original value.
* Here are the mappings for various data types: