HHH-15736 Handle backslash escapes in like patterns
This commit is contained in:
parent
1140f6072e
commit
f1b9909fb6
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 '\\':
|
||||
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 );
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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() );
|
||||
} );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue