mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-24 01:44:38 +00:00
FIX: correct behavior of "startsWith, "contains" etc (#1031)
* FIX: correct behavior of "startsWith" if string contains "like"-wildcards * Removed likeClasues with disabled escaping in database platforms as bind values are properly escaped now.
This commit is contained in:
committed by
Rob Bygrave
parent
a1f95bf25e
commit
bc74d35651
@@ -6,22 +6,22 @@ package io.ebean;
|
||||
public enum LikeType {
|
||||
|
||||
/**
|
||||
* You need to put in your own wildcards.
|
||||
* You need to put in your own wildcards - no escaping is performed.
|
||||
*/
|
||||
RAW,
|
||||
|
||||
/**
|
||||
* The % wildcard is added to the end of the search word.
|
||||
* The % wildcard is added to the end of the search word and search word is escaped.
|
||||
*/
|
||||
STARTS_WITH,
|
||||
|
||||
/**
|
||||
* The % wildcard is added to the beginning of the search word.
|
||||
* The % wildcard is added to the beginning of the search word and search word is escaped.
|
||||
*/
|
||||
ENDS_WITH,
|
||||
|
||||
/**
|
||||
* The % wildcard is added to the beginning and end of the search word.
|
||||
* The % wildcard is added to the beginning and end of the search word and search word is escaped.
|
||||
*/
|
||||
CONTAINS,
|
||||
|
||||
|
||||
@@ -174,6 +174,8 @@ public class DatabasePlatform {
|
||||
protected boolean supportsNativeIlike;
|
||||
|
||||
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
|
||||
|
||||
protected char[] specialLikeCharacters = { '%', '_' };
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
@@ -617,4 +619,37 @@ public class DatabasePlatform {
|
||||
logger.error("Error closing resultSet", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the like string for this DB-Platform
|
||||
*/
|
||||
public String escapeLikeString(String value) {
|
||||
StringBuilder sb = null;
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
boolean escaped = false;
|
||||
for (char escapeChar: specialLikeCharacters) {
|
||||
if (ch == escapeChar) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(value.substring(0, i));
|
||||
}
|
||||
escapeLikeCharacter(escapeChar, sb);
|
||||
escaped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!escaped && sb != null) {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
if (sb == null) {
|
||||
return value;
|
||||
} else {
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('\\').append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,6 @@ public class H2Platform extends DatabasePlatform {
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
// like ? escape'' not working in the latest version H2 so just using no
|
||||
// escape clause for now noting that backslash is an escape char for like in H2
|
||||
this.likeClause = "like ?";
|
||||
|
||||
dbTypeMap.put(DbType.UUID, new DbPlatformType("uuid", false));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
super();
|
||||
this.platform = Platform.MYSQL;
|
||||
this.useExtraTransactionOnIterateSecondaryQueries = true;
|
||||
this.likeClause = "like ? escape''";
|
||||
this.selectCountWithAlias = true;
|
||||
this.dbEncrypt = new MySqlDbEncrypt();
|
||||
this.platformDdl = new MySqlDdl(this);
|
||||
|
||||
@@ -28,7 +28,6 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
super();
|
||||
this.platform = Platform.POSTGRES;
|
||||
this.supportsNativeIlike = true;
|
||||
this.likeClause = "like ? escape''";
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
@@ -41,6 +41,8 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
|
||||
this.openQuote = "[";
|
||||
this.closeQuote = "]";
|
||||
this.specialLikeCharacters = new char[] { '%', '_', '[' };
|
||||
this.likeClause = "like ? COLLATE Latin1_General_BIN";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
|
||||
@@ -63,4 +65,9 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('[').append(ch).append(']');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,4 +65,9 @@ public interface SpiExpressionRequest {
|
||||
* Append a DB Like clause.
|
||||
*/
|
||||
void appendLike();
|
||||
|
||||
/**
|
||||
* Escapes a string to use it as exact match in Like clause.
|
||||
*/
|
||||
String escapeLikeString(String value);
|
||||
}
|
||||
|
||||
@@ -96,6 +96,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
public String getDBLikeClause() {
|
||||
return ebeanServer.getDatabasePlatform().getLikeClause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database platform escaped like string.
|
||||
*/
|
||||
@Override
|
||||
public String escapeLikeString(String value) {
|
||||
return ebeanServer.getDatabasePlatform().escapeLikeString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeSecondaryQueries(boolean forEach) {
|
||||
|
||||
@@ -123,6 +123,11 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
|
||||
* Return the Database platform like clause.
|
||||
*/
|
||||
String getDBLikeClause();
|
||||
|
||||
/**
|
||||
* Escapes a string to use it as exact match in Like clause.
|
||||
*/
|
||||
String escapeLikeString(String value);
|
||||
|
||||
/**
|
||||
* Mark the underlying transaction as not being query only.
|
||||
|
||||
@@ -93,6 +93,11 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
sql.append(" ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeLikeString(String value) {
|
||||
return queryRequest.escapeLikeString(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the parameter index and returns that value.
|
||||
*/
|
||||
|
||||
@@ -34,8 +34,7 @@ class LikeExpression extends AbstractValueExpression {
|
||||
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
|
||||
request.addBindEncryptKey(encryptKey);
|
||||
}
|
||||
|
||||
String bindValue = getValue(strValue(), caseInsensitive, type);
|
||||
String bindValue = getValue(strValue(), caseInsensitive, type, request);
|
||||
request.addBindValue(bindValue);
|
||||
}
|
||||
|
||||
@@ -92,13 +91,15 @@ class LikeExpression extends AbstractValueExpression {
|
||||
return strValue().equals(that.strValue());
|
||||
}
|
||||
|
||||
private static String getValue(String value, boolean caseInsensitive, LikeType type) {
|
||||
private static String getValue(String value, boolean caseInsensitive, LikeType type, SpiExpressionRequest request) {
|
||||
if (caseInsensitive) {
|
||||
value = value.toLowerCase();
|
||||
}
|
||||
if (type == LikeType.RAW) {
|
||||
return value;
|
||||
}
|
||||
value = request.escapeLikeString(value);
|
||||
switch (type) {
|
||||
case RAW:
|
||||
return value;
|
||||
case STARTS_WITH:
|
||||
return value + "%";
|
||||
case ENDS_WITH:
|
||||
|
||||
@@ -78,4 +78,9 @@ public class TDSpiExpressionRequest implements SpiExpressionRequest {
|
||||
public void appendLike() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String escapeLikeString(String value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.tests.query.other;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestLikeEscaping extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void testLikeEscaping() {
|
||||
ResetBasicData.reset();
|
||||
// Mysql, PgSql, H2 special chars: "_" "%"
|
||||
// MsSql special chars: "_" "%" "[" AND different Quoting!
|
||||
Ebean.save(ResetBasicData.createCustomer("Paul % Percentage", "*Star", "[none]", 0, null));
|
||||
Ebean.save(ResetBasicData.createCustomer("(none)", "More * Star", "[none]", 0, null));
|
||||
|
||||
Ebean.save(ResetBasicData.createCustomer("Paul %% Doublepercentage", "|Pipeway", "[other]", 1, null));
|
||||
Ebean.save(ResetBasicData.createCustomer("_Udo Underscore", "|Pipeway", "[other]", 1, null));
|
||||
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().contains("name", "Paul %%").findCount()
|
||||
).isEqualTo(1);
|
||||
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().startsWith("name", "_").findCount()
|
||||
).isEqualTo(1);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().startsWith("name", "_u").findCount()
|
||||
).isEqualTo(0);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().istartsWith("name", "_U").findCount()
|
||||
).isEqualTo(1);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().startsWith("shippingAddress.line1", "|p").findCount()
|
||||
).isEqualTo(0);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().startsWith("shippingAddress.line1", "|P").findCount()
|
||||
).isEqualTo(2);
|
||||
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().endsWith("billingAddress.line1", "]").findCount()
|
||||
).isEqualTo(4);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().endsWith("billingAddress.line1", "[none]").findCount()
|
||||
).isEqualTo(2);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().startsWith("billingAddress.line1", "[none]").findCount()
|
||||
).isEqualTo(2);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().contains("billingAddress.line1", "[none]").findCount()
|
||||
).isEqualTo(2);
|
||||
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().contains("shippingAddress.line1", "*").findCount()
|
||||
).isEqualTo(2);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().startsWith("shippingAddress.line1", "*").findCount()
|
||||
).isEqualTo(1);
|
||||
|
||||
assertThat(Ebean.find(Customer.class)
|
||||
.where().endsWith("shippingAddress.line1", "*").findCount()
|
||||
).isEqualTo(0);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user