From 5d09ca16d6798fb4c6254e8825ea4219a266abdd Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 8 Oct 2014 21:42:39 +1300 Subject: [PATCH] Fix for #192 - Backslash literal in LIKE expression - Postgres (and H2, MySql) behaviour --- .../config/dbplatform/DatabasePlatform.java | 60 +++++++++++++++---- .../ebean/config/dbplatform/H2Platform.java | 1 + .../config/dbplatform/MySqlPlatform.java | 1 + .../config/dbplatform/PostgresPlatform.java | 1 + .../api/SpiExpressionRequest.java | 15 +++-- .../server/core/OrmQueryRequest.java | 9 +++ .../server/core/SpiOrmQueryRequest.java | 5 ++ .../server/deploy/DeployParser.java | 3 + .../server/expression/LikeExpression.java | 3 +- .../util/DefaultExpressionRequest.java | 10 ++++ .../deploy/DeployPropertyParserMapTests.java | 27 +++++++++ .../query/TestExprNestedDisjunction.java | 6 +- .../TestQueryManyToOneWhereClauseJoin.java | 2 +- .../query/other/TestWhereLikeWithSlash.java | 32 ++++++++++ 14 files changed, 152 insertions(+), 23 deletions(-) create mode 100644 src/test/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMapTests.java create mode 100644 src/test/java/com/avaje/tests/query/other/TestWhereLikeWithSlash.java diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java index 54073bd84..bf13fbed5 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java @@ -6,6 +6,7 @@ import javax.sql.DataSource; import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.Query; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,40 +15,61 @@ import org.slf4j.LoggerFactory; */ public class DatabasePlatform { - /** The Constant logger. */ private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class); - /** The open quote used by quoted identifiers. */ + /** + * The open quote used by quoted identifiers. + */ protected String openQuote = "\""; - /** The close quote used by quoted identifiers. */ + /** + * The close quote used by quoted identifiers. + */ protected String closeQuote = "\""; - /** For limit/offset, row_number etc limiting of SQL queries. */ + /** + * For limit/offset, row_number etc limiting of SQL queries. + */ protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter(); - /** Mapping of JDBC to Database types. */ + /** + * Mapping of JDBC to Database types. + */ protected DbTypeMap dbTypeMap = new DbTypeMap(); - /** DB specific DDL syntax. */ + /** + * DB specific DDL syntax. + */ protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax(); - /** Defines DB identity/sequence features. */ + /** + * Defines DB identity/sequence features. + */ protected DbIdentity dbIdentity = new DbIdentity(); - /** The JDBC type to map booleans to (by default). */ + /** + * The JDBC type to map booleans to (by default). + */ protected int booleanDbType = Types.BOOLEAN; - /** The JDBC type to map Blob to. */ + /** + * The JDBC type to map Blob to. + */ protected int blobDbType = Types.BLOB; - /** The JDBC type to map Clob to. */ + /** + * The JDBC type to map Clob to. + */ protected int clobDbType = Types.CLOB; - /** For Oracle treat empty strings as null. */ + /** + * For Oracle treat empty strings as null. + */ protected boolean treatEmptyStringsAsNull; - /** The name. */ + /** + * The database platform name. + */ protected String name = "generic"; /** @@ -57,6 +79,11 @@ public class DatabasePlatform { */ private static final char BACK_TICK = '`'; + /** + * The like clause. Can be overridden to disable default escape character. + */ + protected String likeClause = "like ?"; + protected DbEncrypt dbEncrypt; protected boolean idInExpandedForm; @@ -299,4 +326,13 @@ public class DatabasePlatform { return sql; } + + /** + * Returns the like clause used by this database platform. + *

+ * This may include an escape clause to disable a default escape character. + */ + public String getLikeClause() { + return likeClause; + } } diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/H2Platform.java b/src/main/java/com/avaje/ebean/config/dbplatform/H2Platform.java index 62572ad77..7adb7e27e 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/H2Platform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/H2Platform.java @@ -14,6 +14,7 @@ public class H2Platform extends DatabasePlatform { super(); this.name = "h2"; this.dbEncrypt = new H2DbEncrypt(); + this.likeClause = "like ? escape''"; // only support getGeneratedKeys with non-batch JDBC // so generally use SEQUENCE instead of IDENTITY for H2 diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/MySqlPlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/MySqlPlatform.java index c89f06a6e..ae9a633b1 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/MySqlPlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/MySqlPlatform.java @@ -20,6 +20,7 @@ public class MySqlPlatform extends DatabasePlatform { public MySqlPlatform() { super(); this.name = "mysql"; + this.likeClause = "like ? escape''"; this.selectCountWithAlias = true; this.dbEncrypt = new MySqlDbEncrypt(); diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java index 5639894a9..a6d6bf1f9 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java @@ -23,6 +23,7 @@ public class PostgresPlatform extends DatabasePlatform { public PostgresPlatform() { super(); this.name = "postgres"; + this.likeClause = "like ? escape''"; this.dbDdlSyntax = new PostgresDdlSyntax(); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java index e38a7e872..0e11a5f82 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java @@ -44,9 +44,14 @@ public interface SpiExpressionRequest { * Return the ordered list of bind values for all expressions in this request. */ public ArrayList getBindValues(); - - /** - * Increments the parameter index and returns that value. - */ - public int nextParameter(); + + /** + * Increments the parameter index and returns that value. + */ + public int nextParameter(); + + /** + * Append a DB Like clause. + */ + public void appendLike(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index dee215f35..e64eacd8d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -71,6 +71,15 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe this.readOnly = query.isReadOnly(); } + + /** + * Return the database platform like clause. + */ + @Override + public String getDBLikeClause() { + return ebeanServer.getDatabasePlatform().getLikeClause(); + } + public void executeSecondaryQueries(int defaultQueryBatch) { loadContext.executeSecondaryQueries(this, defaultQueryBatch); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java index 54dc360d3..8837a516d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/SpiOrmQueryRequest.java @@ -95,4 +95,9 @@ public interface SpiOrmQueryRequest { */ public BeanCollection getFromQueryCache(); + /** + * Return the Database platform like clause. + */ + public String getDBLikeClause(); + } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java index 0101c0087..e4d1bdd6a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/DeployParser.java @@ -71,6 +71,9 @@ public abstract class DeployParser { sb.append(deployWord); if (pos < sourceLength) { sb.append(wordTerminator); + if (wordTerminator == SINGLE_QUOTE) { + readLiteral(); + } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java index ccd4628f5..bcfa8ffd7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LikeExpression.java @@ -53,7 +53,8 @@ class LikeExpression extends AbstractExpression { if (type.equals(LikeType.EQUAL_TO)) { request.append(" = ? "); } else { - request.append(" like ? "); + // append db platform like clause + request.appendLike(); } } diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java index d9126cfaa..7322ba60a 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java @@ -39,6 +39,16 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { return s == null ? logicalProp : s; } + /** + * Append the database platform like clause. + */ + @Override + public void appendLike() { + sb.append(" "); + sb.append(queryRequest.getDBLikeClause()); + sb.append(" "); + } + /** * Increments the parameter index and returns that value. */ diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMapTests.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMapTests.java new file mode 100644 index 000000000..ff9dd416c --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/DeployPropertyParserMapTests.java @@ -0,0 +1,27 @@ +package com.avaje.ebeaninternal.server.deploy; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +public class DeployPropertyParserMapTests { + + @Test + public void test_like_escape() { + + + Map map = new HashMap(); + map.put("customer.name", "t1.name"); + map.put("id", "t0.id"); + + DeployPropertyParserMap parser = new DeployPropertyParserMap(map); + + String output = parser.parse("(lower(customer.name) like ? escape'' or id > ?)"); + + System.out.println(output); + Assert.assertEquals("(lower(t1.name) like ? escape'' or t0.id > ?)", output); + } + +} diff --git a/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java b/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java index 2c6f0e764..c9e0cc733 100644 --- a/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java +++ b/src/test/java/com/avaje/tests/query/TestExprNestedDisjunction.java @@ -16,8 +16,6 @@ public class TestExprNestedDisjunction extends BaseTestCase { ResetBasicData.reset(); - // java.sql.Date dateAfter = java.sql.Date.valueOf("2010-01-01"); - java.sql.Date onAfter = java.sql.Date.valueOf("2009-08-31"); Query q = Ebean.find(Customer.class).where().disjunction() @@ -28,8 +26,8 @@ public class TestExprNestedDisjunction extends BaseTestCase { q.findList(); String s = q.getGeneratedSql(); - Assert.assertTrue(s - .contains("(t0.name like ? and t0.anniversary = ? ) or (t0.status = ? and t0.id > ? )")); + Assert.assertTrue(s.contains("(t0.name like ? ")); + Assert.assertTrue(s.contains(" and t0.anniversary = ? ) or (t0.status = ? and t0.id > ? )")); } } diff --git a/src/test/java/com/avaje/tests/query/joins/TestQueryManyToOneWhereClauseJoin.java b/src/test/java/com/avaje/tests/query/joins/TestQueryManyToOneWhereClauseJoin.java index 237ada6ae..00cc967d7 100644 --- a/src/test/java/com/avaje/tests/query/joins/TestQueryManyToOneWhereClauseJoin.java +++ b/src/test/java/com/avaje/tests/query/joins/TestQueryManyToOneWhereClauseJoin.java @@ -51,7 +51,7 @@ public class TestQueryManyToOneWhereClauseJoin extends BaseTestCase { query.findList(); //select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6, t0.kcustomer_id c7 - String expectedSql = "from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id where (lower(t1.name) like ? or t0.id > ? ) "; + String expectedSql = "from o_order t0 join o_customer t1 on t1.id = t0.kcustomer_id where (lower(t1.name) like ? "; Assert.assertTrue(query.getGeneratedSql().contains(expectedSql)); // select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6, t0.kcustomer_id c7 diff --git a/src/test/java/com/avaje/tests/query/other/TestWhereLikeWithSlash.java b/src/test/java/com/avaje/tests/query/other/TestWhereLikeWithSlash.java new file mode 100644 index 000000000..b1f0fec7b --- /dev/null +++ b/src/test/java/com/avaje/tests/query/other/TestWhereLikeWithSlash.java @@ -0,0 +1,32 @@ +package com.avaje.tests.query.other; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.tests.model.basic.EBasic; + +public class TestWhereLikeWithSlash extends BaseTestCase { + + @Test + public void test() { + + EBasic basic = new EBasic(); + basic.setName("slash\\monkey"); + + Ebean.save(basic); + + + Query query = Ebean.find(EBasic.class).where().like("name", "slash\\mon%").query(); + + List list = query.findList(); + + Assert.assertEquals(1, list.size()); + + } + +}