diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index f3d4ab5c9..32bf47913 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -409,6 +409,11 @@ public class ServerConfig {
*/
private boolean expressionEqualsWithNullAsNoop;
+ /**
+ * Set to true to use native ILIKE expression (if support by datasbase platform / like Postgres).
+ */
+ private boolean expressionNativeIlike;
+
private String jodaLocalTimeMode;
/**
@@ -2391,6 +2396,7 @@ public class ServerConfig {
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
+ expressionNativeIlike = p.getBoolean("expressionNativeIlike", expressionNativeIlike);
dataTimeZone = p.get("dataTimeZone", dataTimeZone);
asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix);
@@ -2531,6 +2537,20 @@ public class ServerConfig {
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
}
+ /**
+ * Return true if native ILIKE expression should be used if supported by the database platform (e.g. Postgres).
+ */
+ public boolean isExpressionNativeIlike() {
+ return expressionNativeIlike;
+ }
+
+ /**
+ * Set to true to use native ILIKE expression if supported by the database platform (e.g. Postgres).
+ */
+ public void setExpressionNativeIlike(boolean expressionNativeIlike) {
+ this.expressionNativeIlike = expressionNativeIlike;
+ }
+
/**
* Return true if L2 cache is disabled.
*/
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 a07651803..748d70976 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java
@@ -170,6 +170,8 @@ public class DatabasePlatform {
*/
protected int maxConstraintNameLength = 60;
+ protected boolean supportsNativeIlike;
+
/**
* Instantiates a new database platform.
*/
@@ -191,6 +193,13 @@ public class DatabasePlatform {
return name;
}
+ /**
+ * Return true if this database platform supports native ILIKE expression.
+ */
+ public boolean isSupportsNativeIlike() {
+ return supportsNativeIlike;
+ }
+
/**
* Return the maximum table name length.
*
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 17a537988..0654dfc50 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java
@@ -20,6 +20,7 @@ public class PostgresPlatform extends DatabasePlatform {
public PostgresPlatform() {
super();
this.name = "postgres";
+ this.supportsNativeIlike = true;
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
index 373eba960..6cd87afd8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
@@ -126,8 +126,9 @@ public class InternalConfiguration {
this.cacheManager = cacheManager;
this.serverConfig = serverConfig;
this.bootupClasses = bootupClasses;
- this.expressionFactory = new DefaultExpressionFactory(serverConfig.isExpressionEqualsWithNullAsNoop());
+ DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
+ this.expressionFactory = initExpressionFactory(serverConfig, databasePlatform);
this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
this.deployOrmXml = new DeployOrmXml();
@@ -140,13 +141,20 @@ public class InternalConfiguration {
Map asOfTableMapping = beanDescriptorManager.deploy();
Map draftTableMap = beanDescriptorManager.getDraftTableMap();
- DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
-
this.dataTimeZone = initDataTimeZone();
this.binder = getBinder(typeManager, databasePlatform, dataTimeZone);
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod(), draftTableMap);
}
+ /**
+ * Create and return the ExpressionFactory based on configuration and database platform.
+ */
+ private ExpressionFactory initExpressionFactory(ServerConfig serverConfig, DatabasePlatform databasePlatform) {
+
+ boolean nativeIlike = serverConfig.isExpressionNativeIlike() && databasePlatform.isSupportsNativeIlike();
+ return new DefaultExpressionFactory(serverConfig.isExpressionEqualsWithNullAsNoop(), nativeIlike);
+ }
+
private DocStoreFactory initDocStoreFactory(DocStoreFactory service) {
return service == null ? new NoneDocStoreFactory() : service;
}
@@ -324,10 +332,6 @@ public class InternalConfiguration {
return cQueryEngine;
}
- public ClusterManager getClusterManager() {
- return clusterManager;
- }
-
public SpiBackgroundExecutor getBackgroundExecutor() {
return backgroundExecutor;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
index 84db696b3..87465295f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
@@ -28,10 +28,13 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
private static final Object[] EMPTY_ARRAY = new Object[] {};
+ private final boolean nativeIlike;
+
private final boolean equalsWithNullAsNoop;
- public DefaultExpressionFactory(boolean equalsWithNullAsNoop) {
+ public DefaultExpressionFactory(boolean equalsWithNullAsNoop, boolean nativeIlike) {
this.equalsWithNullAsNoop = equalsWithNullAsNoop;
+ this.nativeIlike = nativeIlike;
}
public ExpressionFactory createExpressionFactory(){
@@ -242,7 +245,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
* a lower() function to make the expression case insensitive.
*/
public Expression ilike(String propertyName, String value) {
- return new LikeExpression(propertyName, value, true, LikeType.RAW);
+ if (nativeIlike) {
+ return new NativeILikeExpression(propertyName, value);
+ } else {
+ return new LikeExpression(propertyName, value, true, LikeType.RAW);
+ }
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NativeILikeExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NativeILikeExpression.java
new file mode 100644
index 000000000..5526fd145
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NativeILikeExpression.java
@@ -0,0 +1,79 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.LikeType;
+import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+import com.avaje.ebeaninternal.server.el.ElPropertyValue;
+
+import java.io.IOException;
+
+class NativeILikeExpression extends AbstractExpression {
+
+ private final String val;
+
+ NativeILikeExpression(String propertyName, String value) {
+ super(propertyName);
+ this.val = value;
+ }
+
+ @Override
+ public void writeDocQuery(DocQueryContext context) throws IOException {
+ context.writeLike(propName, val, LikeType.RAW, true);
+ }
+
+ @Override
+ public void addBindValues(SpiExpressionRequest request) {
+
+ ElPropertyValue prop = getElProp(request);
+ if (prop != null && prop.isDbEncrypted()) {
+ // bind the key as well as the value
+ String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
+ request.addBindEncryptKey(encryptKey);
+ }
+ request.addBindValue(val);
+ }
+
+ @Override
+ public void addSql(SpiExpressionRequest request) {
+
+ String pname = propName;
+ ElPropertyValue prop = getElProp(request);
+ if (prop != null && prop.isDbEncrypted()) {
+ pname = prop.getBeanProperty().getDecryptProperty(propName);
+ }
+
+ request.append(pname).append(" ilike ? ");
+ }
+
+ /**
+ * Based on caseInsensitive and the property name.
+ */
+ @Override
+ public void queryPlanHash(HashQueryPlanBuilder builder) {
+ builder.add(NativeILikeExpression.class).add(propName);
+ builder.bind(1);
+ }
+
+ @Override
+ public int queryBindHash() {
+ return val.hashCode();
+ }
+
+ @Override
+ public boolean isSameByPlan(SpiExpression other) {
+ if (!(other instanceof NativeILikeExpression)) {
+ return false;
+ }
+
+ NativeILikeExpression that = (NativeILikeExpression) other;
+ return this.propName.equals(that.propName);
+ }
+
+ @Override
+ public boolean isSameByBind(SpiExpression other) {
+ NativeILikeExpression that = (NativeILikeExpression) other;
+ return val.equals(that.val);
+ }
+
+}
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java
index 0b048117a..c19788624 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java
@@ -8,10 +8,26 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class DefaultExpressionFactoryTest {
+ @Test
+ public void testLowerILike() throws Exception {
+
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(false, false);
+ Expression expression = factory.ilike("name", "foo");
+ assertThat(expression).isInstanceOf(LikeExpression.class);
+ }
+
+ @Test
+ public void testNativeILike() throws Exception {
+
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(false, true);
+ Expression expression = factory.ilike("name", "foo");
+ assertThat(expression).isInstanceOf(NativeILikeExpression.class);
+ }
+
@Test
public void testEq() throws Exception {
- DefaultExpressionFactory factory = new DefaultExpressionFactory(false);
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(false, false);
Expression expression = factory.eq("name", null);
assertThat(expression).isInstanceOf(NullExpression.class);
}
@@ -19,7 +35,7 @@ public class DefaultExpressionFactoryTest {
@Test
public void testNe() throws Exception {
- DefaultExpressionFactory factory = new DefaultExpressionFactory(false);
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(false, false);
Expression expression = factory.ne("name", null);
assertThat(expression).isInstanceOf(NullExpression.class);
}
@@ -27,7 +43,7 @@ public class DefaultExpressionFactoryTest {
@Test
public void testIeq() throws Exception {
- DefaultExpressionFactory factory = new DefaultExpressionFactory(false);
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(false, false);
Expression expression = factory.ieq("name", null);
assertThat(expression).isInstanceOf(NullExpression.class);
}
@@ -35,7 +51,7 @@ public class DefaultExpressionFactoryTest {
@Test
public void testEq_with_equalsWithNullAsNoop() throws Exception {
- DefaultExpressionFactory factory = new DefaultExpressionFactory(true);
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(true, false);
Expression expression = factory.eq("name", null);
assertThat(expression).isInstanceOf(NoopExpression.class);
}
@@ -43,7 +59,7 @@ public class DefaultExpressionFactoryTest {
@Test
public void testNe_with_equalsWithNullAsNoop() throws Exception {
- DefaultExpressionFactory factory = new DefaultExpressionFactory(true);
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(true, false);
Expression expression = factory.ne("name", null);
assertThat(expression).isInstanceOf(NoopExpression.class);
}
@@ -51,7 +67,7 @@ public class DefaultExpressionFactoryTest {
@Test
public void testIeq_with_equalsWithNullAsNoop() throws Exception {
- DefaultExpressionFactory factory = new DefaultExpressionFactory(true);
+ DefaultExpressionFactory factory = new DefaultExpressionFactory(true, false);
Expression expression = factory.ieq("name", null);
assertThat(expression).isInstanceOf(NoopExpression.class);
}
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java
index a5a688fd8..14e095c6c 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionListTest.java
@@ -10,7 +10,7 @@ public class DefaultExpressionListTest {
DefaultExpressionList exp() {
- return new DefaultExpressionList