HHH-15736 Handle backslash escapes in like patterns

This commit is contained in:
Marco Belladelli 2022-12-12 13:05:39 +01:00 committed by Christian Beikov
parent 1140f6072e
commit f1b9909fb6
15 changed files with 486 additions and 8 deletions

View File

@ -31,6 +31,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.exec.spi.JdbcOperation;
@ -209,7 +210,17 @@ public class H2LegacySqlAstTranslator<T extends JdbcOperation> extends AbstractS
}
}
return super.renderPrimaryTableReference( tableGroup, lockMode );
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
super.visitLikePredicate( likePredicate );
// Custom implementation because H2 uses backslash as the default escape character
// We can override this by specifying an empty escape character
// See http://www.h2database.com/html/grammar.html#like_predicate_right_hand_side
if ( likePredicate.getEscapeCharacter() == null ) {
appendSql( " escape ''" );
}
}
@Override

View File

@ -12,6 +12,7 @@ import java.sql.SQLException;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.InnoDBStorageEngine;
import org.hibernate.dialect.MySQLServerConfiguration;
import org.hibernate.dialect.MySQLStorageEngine;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
@ -25,6 +26,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
@ -51,7 +53,7 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
}
public MariaDBLegacyDialect(DialectResolutionInfo info) {
super( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
super( createVersion( info ), MySQLServerConfiguration.fromDatabaseMetadata( info.getDatabaseMetadata() ) );
registerKeywords( info );
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.community.dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@ -16,6 +18,7 @@ import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -157,6 +160,44 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
}
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
if ( likePredicate.isCaseSensitive() ) {
likePredicate.getMatchExpression().accept( this );
if ( likePredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " like " );
renderBackslashEscapedLikePattern(
likePredicate.getPattern(),
likePredicate.getEscapeCharacter(),
getDialect().isNoBackslashEscapesEnabled()
);
}
else {
appendSql( getDialect().getLowercaseFunction() );
appendSql( OPEN_PARENTHESIS );
likePredicate.getMatchExpression().accept( this );
appendSql( CLOSE_PARENTHESIS );
if ( likePredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " like " );
appendSql( getDialect().getLowercaseFunction() );
appendSql( OPEN_PARENTHESIS );
renderBackslashEscapedLikePattern(
likePredicate.getPattern(),
likePredicate.getEscapeCharacter(),
getDialect().isNoBackslashEscapesEnabled()
);
appendSql( CLOSE_PARENTHESIS );
}
if ( likePredicate.getEscapeCharacter() != null ) {
appendSql( " escape " );
likePredicate.getEscapeCharacter().accept( this );
}
}
@Override
public boolean supportsRowValueConstructorSyntaxInSet() {
return false;
@ -178,6 +219,11 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
return true;
}
@Override
public MariaDBLegacyDialect getDialect() {
return (MariaDBLegacyDialect) super.getDialect();
}
private boolean supportsWindowFunctions() {
return getDialect().getVersion().isSameOrAfter( 10, 2 );
}

View File

@ -11,6 +11,7 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import org.hibernate.LockOptions;
import org.hibernate.PessimisticLockException;
@ -20,6 +21,7 @@ import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.InnoDBStorageEngine;
import org.hibernate.dialect.MyISAMStorageEngine;
import org.hibernate.dialect.MySQLServerConfiguration;
import org.hibernate.dialect.MySQLStorageEngine;
import org.hibernate.dialect.Replacer;
import org.hibernate.dialect.RowLockStrategy;
@ -141,6 +143,8 @@ public class MySQLLegacyDialect extends Dialect {
private final int maxVarcharLength;
private final int maxVarbinaryLength;
private final boolean noBackslashEscapesEnabled;
public MySQLLegacyDialect() {
this( DatabaseVersion.make( 5, 0 ) );
}
@ -150,13 +154,22 @@ public class MySQLLegacyDialect extends Dialect {
}
public MySQLLegacyDialect(DatabaseVersion version, int bytesPerCharacter) {
this( version, bytesPerCharacter, false );
}
public MySQLLegacyDialect(DatabaseVersion version, MySQLServerConfiguration serverConfiguration) {
this( version, serverConfiguration.getBytesPerCharacter(), serverConfiguration.isNoBackslashEscapesEnabled() );
}
public MySQLLegacyDialect(DatabaseVersion version, int bytesPerCharacter, boolean noBackslashEscapes) {
super( version );
maxVarcharLength = maxVarcharLength( getMySQLVersion(), bytesPerCharacter ); //conservative assumption
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
noBackslashEscapesEnabled = noBackslashEscapes;
}
public MySQLLegacyDialect(DialectResolutionInfo info) {
this( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
this( createVersion( info ), MySQLServerConfiguration.fromDatabaseMetadata( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
@ -356,6 +369,7 @@ public class MySQLLegacyDialect extends Dialect {
);
}
@Deprecated
protected static int getCharacterSetBytesPerCharacter(DatabaseMetaData databaseMetaData) {
if ( databaseMetaData != null ) {
try (java.sql.Statement s = databaseMetaData.getConnection().createStatement() ) {
@ -430,6 +444,10 @@ public class MySQLLegacyDialect extends Dialect {
return maxVarbinaryLength;
}
public boolean isNoBackslashEscapesEnabled() {
return noBackslashEscapesEnabled;
}
@Override
public String getNullColumnString(String columnType) {
// Good job MySQL https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.community.dialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@ -16,6 +17,7 @@ import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -139,6 +141,17 @@ public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends Abstra
}
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
super.visitLikePredicate( likePredicate );
// Custom implementation because MySQL uses backslash as the default escape character
// We can override this by specifying an empty escape character
// See https://dev.mysql.com/doc/refman/8.0/en/string-comparison-functions.html#operator_like
if ( !( (MySQLLegacyDialect) getDialect() ).isNoBackslashEscapesEnabled() && likePredicate.getEscapeCharacter() == null ) {
appendSql( " escape ''" );
}
}
@Override
public boolean supportsRowValueConstructorSyntaxInSet() {
return false;

View File

@ -32,6 +32,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.SelectClause;
import org.hibernate.sql.exec.spi.JdbcOperation;
@ -244,7 +245,17 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
}
}
return super.renderPrimaryTableReference( tableGroup, lockMode );
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
super.visitLikePredicate( likePredicate );
// Custom implementation because H2 uses backslash as the default escape character
// We can override this by specifying an empty escape character
// See http://www.h2database.com/html/grammar.html#like_predicate_right_hand_side
if ( likePredicate.getEscapeCharacter() == null ) {
appendSql( " escape ''" );
}
}
@Override

View File

@ -22,6 +22,7 @@ import org.hibernate.query.spi.QueryEngine;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
@ -56,7 +57,7 @@ public class MariaDBDialect extends MySQLDialect {
}
public MariaDBDialect(DialectResolutionInfo info) {
super( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
super( createVersion( info ), MySQLServerConfiguration.fromDatabaseMetadata( info.getDatabaseMetadata() ) );
registerKeywords( info );
}

View File

@ -10,12 +10,12 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -152,6 +152,44 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
}
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
if ( likePredicate.isCaseSensitive() ) {
likePredicate.getMatchExpression().accept( this );
if ( likePredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " like " );
renderBackslashEscapedLikePattern(
likePredicate.getPattern(),
likePredicate.getEscapeCharacter(),
getDialect().isNoBackslashEscapesEnabled()
);
}
else {
appendSql( getDialect().getLowercaseFunction() );
appendSql( OPEN_PARENTHESIS );
likePredicate.getMatchExpression().accept( this );
appendSql( CLOSE_PARENTHESIS );
if ( likePredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " like " );
appendSql( getDialect().getLowercaseFunction() );
appendSql( OPEN_PARENTHESIS );
renderBackslashEscapedLikePattern(
likePredicate.getPattern(),
likePredicate.getEscapeCharacter(),
getDialect().isNoBackslashEscapesEnabled()
);
appendSql( CLOSE_PARENTHESIS );
}
if ( likePredicate.getEscapeCharacter() != null ) {
appendSql( " escape " );
likePredicate.getEscapeCharacter().accept( this );
}
}
@Override
public boolean supportsRowValueConstructorSyntaxInSet() {
return false;
@ -173,6 +211,11 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
return true;
}
@Override
public MariaDBDialect getDialect() {
return (MariaDBDialect) super.getDialect();
}
private boolean supportsWindowFunctions() {
return true;
}

View File

@ -11,6 +11,7 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import org.hibernate.LockOptions;
import org.hibernate.PessimisticLockException;
@ -137,6 +138,8 @@ public class MySQLDialect extends Dialect {
private final int maxVarcharLength;
private final int maxVarbinaryLength;
private final boolean noBackslashEscapesEnabled;
public MySQLDialect() {
this( MINIMUM_VERSION );
}
@ -146,13 +149,22 @@ public class MySQLDialect extends Dialect {
}
public MySQLDialect(DatabaseVersion version, int bytesPerCharacter) {
this( version, bytesPerCharacter, false );
}
public MySQLDialect(DatabaseVersion version, MySQLServerConfiguration serverConfiguration) {
this( version, serverConfiguration.getBytesPerCharacter(), serverConfiguration.isNoBackslashEscapesEnabled() );
}
public MySQLDialect(DatabaseVersion version, int bytesPerCharacter, boolean noBackslashEscapes) {
super( version );
maxVarcharLength = maxVarcharLength( getMySQLVersion(), bytesPerCharacter ); //conservative assumption
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
noBackslashEscapesEnabled = noBackslashEscapes;
}
public MySQLDialect(DialectResolutionInfo info) {
this( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
this( createVersion( info ), MySQLServerConfiguration.fromDatabaseMetadata( info.getDatabaseMetadata() ) );
registerKeywords( info );
}
@ -355,6 +367,7 @@ public class MySQLDialect extends Dialect {
);
}
@Deprecated
protected static int getCharacterSetBytesPerCharacter(DatabaseMetaData databaseMetaData) {
if ( databaseMetaData != null ) {
try (java.sql.Statement s = databaseMetaData.getConnection().createStatement() ) {
@ -423,6 +436,10 @@ public class MySQLDialect extends Dialect {
return maxVarbinaryLength;
}
public boolean isNoBackslashEscapesEnabled() {
return noBackslashEscapesEnabled;
}
@Override
public String getNullColumnString(String columnType) {
// Good job MySQL https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html
@ -1114,7 +1131,10 @@ public class MySQLDialect extends Dialect {
appender.appendSql( '\'' );
break;
case '\\':
appender.appendSql( '\\' );
if ( !noBackslashEscapesEnabled ) {
// See https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_no_backslash_escapes
appender.appendSql( '\\' );
}
break;
}
appender.appendSql( c );

View File

@ -0,0 +1,80 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.dialect;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
public class MySQLServerConfiguration {
private final int bytesPerCharacter;
private final boolean noBackslashEscapesEnabled;
public MySQLServerConfiguration(int bytesPerCharacter, boolean noBackslashEscapesEnabled) {
this.bytesPerCharacter = bytesPerCharacter;
this.noBackslashEscapesEnabled = noBackslashEscapesEnabled;
}
public int getBytesPerCharacter() {
return bytesPerCharacter;
}
public boolean isNoBackslashEscapesEnabled() {
return noBackslashEscapesEnabled;
}
public static MySQLServerConfiguration fromDatabaseMetadata(DatabaseMetaData databaseMetaData) {
int bytesPerCharacter = 4;
boolean noBackslashEscapes = false;
if ( databaseMetaData != null ) {
try (java.sql.Statement s = databaseMetaData.getConnection().createStatement()) {
final ResultSet rs = s.executeQuery( "SELECT @@character_set_database, @@sql_mode" );
if ( rs.next() ) {
final String characterSet = rs.getString( 1 );
final int collationIndex = characterSet.indexOf( '_' );
// According to https://dev.mysql.com/doc/refman/8.0/en/charset-charsets.html
switch ( collationIndex == -1 ? characterSet : characterSet.substring( 0, collationIndex ) ) {
case "utf16":
case "utf16le":
case "utf32":
case "utf8mb4":
case "gb18030":
break;
case "utf8":
case "utf8mb3":
case "eucjpms":
case "ujis":
bytesPerCharacter = 3;
break;
case "ucs2":
case "cp932":
case "big5":
case "euckr":
case "gb2312":
case "gbk":
case "sjis":
bytesPerCharacter = 2;
break;
default:
bytesPerCharacter = 1;
}
// NO_BACKSLASH_ESCAPES
final String sqlMode = rs.getString( 2 );
if ( sqlMode.toLowerCase().contains( "no_backslash_escapes" ) ) {
noBackslashEscapes = true;
}
}
}
catch (SQLException ex) {
// Ignore
}
}
return new MySQLServerConfiguration(bytesPerCharacter, noBackslashEscapes);
}
}

View File

@ -16,6 +16,7 @@ import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -139,6 +140,17 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
}
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
super.visitLikePredicate( likePredicate );
// Custom implementation because MySQL uses backslash as the default escape character
// We can override this by specifying an empty escape character
// See https://dev.mysql.com/doc/refman/8.0/en/string-comparison-functions.html#operator_like
if ( !( (MySQLDialect) getDialect() ).isNoBackslashEscapesEnabled() && likePredicate.getEscapeCharacter() == null ) {
appendSql( " escape ''" );
}
}
@Override
public boolean supportsRowValueConstructorSyntaxInSet() {
return false;

View File

@ -15,6 +15,7 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.exec.spi.JdbcOperation;
@ -39,7 +40,7 @@ public class TiDBDialect extends MySQLDialect {
}
public TiDBDialect(DialectResolutionInfo info) {
super( createVersion( info ), getCharacterSetBytesPerCharacter( info.getDatabaseMetadata() ) );
super(createVersion( info ), MySQLServerConfiguration.fromDatabaseMetadata( info.getDatabaseMetadata() ));
registerKeywords( info );
}
@ -157,5 +158,4 @@ public class TiDBDialect extends MySQLDialect {
Duration duration = Duration.ofMillis( timeoutInMilliseconds );
return duration.getSeconds();
}
}

View File

@ -18,6 +18,7 @@ import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.LikePredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
@ -116,6 +117,44 @@ public class TiDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
}
}
@Override
public void visitLikePredicate(LikePredicate likePredicate) {
if ( likePredicate.isCaseSensitive() ) {
likePredicate.getMatchExpression().accept( this );
if ( likePredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " like " );
renderBackslashEscapedLikePattern(
likePredicate.getPattern(),
likePredicate.getEscapeCharacter(),
getDialect().isNoBackslashEscapesEnabled()
);
}
else {
appendSql( getDialect().getLowercaseFunction() );
appendSql( OPEN_PARENTHESIS );
likePredicate.getMatchExpression().accept( this );
appendSql( CLOSE_PARENTHESIS );
if ( likePredicate.isNegated() ) {
appendSql( " not" );
}
appendSql( " like " );
appendSql( getDialect().getLowercaseFunction() );
appendSql( OPEN_PARENTHESIS );
renderBackslashEscapedLikePattern(
likePredicate.getPattern(),
likePredicate.getEscapeCharacter(),
getDialect().isNoBackslashEscapesEnabled()
);
appendSql( CLOSE_PARENTHESIS );
}
if ( likePredicate.getEscapeCharacter() != null ) {
appendSql( " escape " );
likePredicate.getEscapeCharacter().accept( this );
}
}
@Override
public boolean supportsRowValueConstructorSyntaxInSet() {
return false;

View File

@ -7087,6 +7087,70 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
protected void renderBackslashEscapedLikePattern(
Expression pattern,
Expression escapeCharacter,
boolean noBackslashEscapes) {
// Check if escapeCharacter was explicitly set and do nothing in that case
// Note: this does not cover cases where it's set via parameter binding
boolean isExplicitEscape = false;
if ( escapeCharacter instanceof Literal ) {
Object literalValue = ( (Literal) escapeCharacter ).getLiteralValue();
isExplicitEscape = literalValue != null && !literalValue.toString().equals( "" );
}
if ( isExplicitEscape ) {
pattern.accept( this );
}
else {
// Since escape with empty or null character is ignored we need
// four backslashes to render a single one in a like pattern
if ( pattern instanceof Literal ) {
Object literalValue = ( (Literal) pattern ).getLiteralValue();
if ( literalValue == null ) {
pattern.accept( this );
}
else {
appendBackslashEscapedLikeLiteral( this, literalValue.toString(), noBackslashEscapes );
}
}
else {
// replace(<pattern>,'\\','\\\\')
appendSql( "replace" );
appendSql( OPEN_PARENTHESIS );
pattern.accept( this );
if ( noBackslashEscapes ) {
appendSql( ",'\\','\\\\'" );
}
else {
appendSql( ",'\\\\','\\\\\\\\'" );
}
appendSql( CLOSE_PARENTHESIS );
}
}
}
protected void appendBackslashEscapedLikeLiteral(SqlAppender appender, String literal, boolean noBackslashEscapes) {
appender.appendSql( '\'' );
for ( int i = 0; i < literal.length(); i++ ) {
final char c = literal.charAt( i );
switch ( c ) {
case '\'':
appender.appendSql( '\'' );
break;
case '\\':
if ( noBackslashEscapes ) {
appender.appendSql( '\\' );
}
else {
appender.appendSql( "\\\\\\" );
}
break;
}
appender.appendSql( c );
}
appender.appendSql( '\'' );
}
@Override
public void visitNegatedPredicate(NegatedPredicate negatedPredicate) {
if ( negatedPredicate.isEmpty() ) {

View File

@ -0,0 +1,118 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.query.hql;
import java.util.List;
import org.hibernate.query.Query;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.gambit.BasicEntity;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* @author Marco Belladelli
*/
@ServiceRegistry
@DomainModel(
standardModels = StandardDomainModel.GAMBIT
)
@SessionFactory
public class LikeEscapeDefaultTest {
@BeforeEach
public void prepareData(SessionFactoryScope scope) {
scope.inTransaction(
em -> {
BasicEntity be1 = new BasicEntity( 1, "Product\\one" );
em.persist( be1 );
BasicEntity be2 = new BasicEntity( 2, "Product%two" );
em.persist( be2 );
BasicEntity be3 = new BasicEntity( 3, "Product\"three" );
em.persist( be3 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createMutationQuery( "delete from BasicEntity" ).executeUpdate()
);
}
@Test
public void testDefaultEscapeBackslash(SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<BasicEntity> q = session.createQuery(
"from BasicEntity be where be.data like ?1",
BasicEntity.class
).setParameter( 1, "%\\%" );
List<BasicEntity> l = q.getResultList();
assertEquals( 1, l.size() );
assertEquals( 1, l.get( 0 ).getId() );
} );
}
@Test
public void testDefaultEscapeBackslashLiteral(SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<BasicEntity> q = session.createQuery(
"from BasicEntity be where be.data like '%\\%'",
BasicEntity.class
);
List<BasicEntity> l = q.getResultList();
assertEquals( 1, l.size() );
assertEquals( 1, l.get( 0 ).getId() );
} );
}
@Test
public void testDefaultEscapeNoResults(SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<BasicEntity> q = session.createQuery(
"from BasicEntity be where be.data like ?1",
BasicEntity.class
).setParameter( 1, "%\\\"%" );
List<BasicEntity> l = q.getResultList();
assertEquals( 0, l.size() );
} );
}
@Test
public void testExplicitEscapeLiteralBackslash(SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<BasicEntity> q = session.createQuery(
"from BasicEntity be where be.data like ?1 escape '\\'",
BasicEntity.class
).setParameter( 1, "%\\%%" );
List<BasicEntity> l = q.getResultList();
assertEquals( 1, l.size() );
assertEquals( 2, l.get( 0 ).getId() );
} );
}
@Test
public void testExplicitEscapeLiteralOtherChar(SessionFactoryScope scope) {
scope.inTransaction( session -> {
Query<BasicEntity> q = session.createQuery(
"from BasicEntity be where be.data like ?1 escape '#'",
BasicEntity.class
).setParameter( 1, "%#%%" );
List<BasicEntity> l = q.getResultList();
assertEquals( 1, l.size() );
assertEquals( 2, l.get( 0 ).getId() );
} );
}
}