#570 - ENH: Add ability to configure eBean to use true "ilike" expression

This commit is contained in:
Robin Bygrave
2016-05-06 14:26:08 +12:00
parent 2797f6bbcc
commit 2b602f6e51
12 changed files with 199 additions and 19 deletions
@@ -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.
*/
@@ -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.
* <p>
@@ -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;
@@ -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<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> 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;
}
@@ -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);
}
}
/**
@@ -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);
}
}
@@ -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);
}
@@ -10,7 +10,7 @@ public class DefaultExpressionListTest {
DefaultExpressionList exp() {
return new DefaultExpressionList<Object>(null, new DefaultExpressionFactory(true), null);
return new DefaultExpressionList<Object>(null, new DefaultExpressionFactory(true, true), null);
}
DefaultExpressionList spi(ExpressionList list) {
@@ -16,7 +16,7 @@ public class JunctionExpressionTest {
DefaultExpressionList<?> exp(Expression... expressions) {
DefaultExpressionList<Object> list = new DefaultExpressionList<Object>(null, new DefaultExpressionFactory(true), null);
DefaultExpressionList<Object> list = new DefaultExpressionList<Object>(null, new DefaultExpressionFactory(true, false), null);
for (Expression ex : expressions) {
list.add(ex);
}
@@ -89,7 +89,7 @@ public class TestQueryLanguage extends BaseTestCase {
BeanDescriptor<Order> desc = server.getBeanDescriptor(Order.class);
DefaultOrmQuery<Order> qry = new DefaultOrmQuery<Order>(desc, server,
new DefaultExpressionFactory(false), (String) null);
new DefaultExpressionFactory(false, false), (String) null);
p.assign(qry);
return qry;
@@ -0,0 +1,44 @@
package com.avaje.tests.query.sqlquery;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.ebean.plugin.SpiServer;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestNativeILikeExpression extends BaseTestCase {
@Test
public void test() {
boolean expectNative = isExpectNative();
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.where().ilike("name", "rob")
.query();
List<Customer> list = query.findList();
if (expectNative) {
assertThat(query.getGeneratedSql()).contains(" from o_customer t0 where t0.name ilike ?");
assertThat(list).isNotEmpty();
}
}
private boolean isExpectNative() {
SpiServer pluginApi = server().getPluginApi();
boolean expressionNativeIlike = pluginApi.getServerConfig().isExpressionNativeIlike();
String platformName = pluginApi.getDatabasePlatform().getName();
return expressionNativeIlike && platformName.equalsIgnoreCase("postgres");
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ ebean.persistBatch=NONE
ebean.persistBatchOnCascade=ALL
ebean.currentUserProvider=com.avaje.ebean.MyCurrentUserProvider
#ebean.expressionNativeIlike=true
#ebean.jsonInclude=NON_NULL
#ebean.cluster.type=mcast