#1214 - ENH: Add inPairs() expression - support L2 cache hits for complex natural key with findList() and in pairs expression

This commit is contained in:
Rob Bygrave
2017-11-19 16:47:32 +13:00
parent f0ddf6c7e6
commit ef91c643ed
22 changed files with 765 additions and 87 deletions
@@ -6,6 +6,7 @@ import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.Junction;
import io.ebean.LikeType;
import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.search.Match;
@@ -361,6 +362,14 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new LikeExpression(propertyName, value, true, LikeType.CONTAINS);
}
/**
* In - property has a value in the collection of values.
*/
@Override
public Expression inPairs(Pairs pairs) {
return new InPairsExpression(pairs, false);
}
/**
* In - property has a value in the array of values.
*/
@@ -11,6 +11,7 @@ import io.ebean.FutureRowCount;
import io.ebean.Junction;
import io.ebean.OrderBy;
import io.ebean.PagedList;
import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Version;
@@ -21,13 +22,13 @@ import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.api.SpiJunction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.api.NaturalKeyQueryData;
import java.io.IOException;
import java.sql.Timestamp;
@@ -789,6 +790,12 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return this;
}
@Override
public ExpressionList<T> inPairs(Pairs pairs) {
add(expr.inPairs(pairs));
return this;
}
@Override
public ExpressionList<T> in(String propertyName, Query<?> subQuery) {
add(expr.in(propertyName, subQuery));
@@ -0,0 +1,142 @@
package io.ebeaninternal.server.expression;
import io.ebean.Pairs;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
class InPairsExpression extends AbstractExpression {
private final boolean not;
private final Pairs pairs;
private final String property0, property1;
private final List<Pairs.Entry> entries;
private boolean multiValueSupported;
private final String separator;
private final String suffix;
private List<Object> concatBindValues;
InPairsExpression(Pairs pairs, boolean not) {
super(pairs.getProperty0());
this.pairs = pairs;
this.property0 = pairs.getProperty0();
this.property1 = pairs.getProperty1();
this.entries = pairs.getEntries();
this.not = not;
this.separator = pairs.getConcatSeparator();
this.suffix = pairs.getConcatSuffix();
}
@Override
public boolean naturalKey(NaturalKeyQueryData data) {
return !not && data.matchInPairs(pairs);
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
// at this stage translating pairs into varchar via DB concat
multiValueSupported = request.isMultiValueSupported(String.class);
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
throw new RuntimeException("Not supported with document query");
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// Note at this point entries may have been removed when used with l2 caching
// ... for each l2 cache hit an entry was removed
this.concatBindValues = new ArrayList<>(entries.size());
for (Pairs.Entry entry : entries) {
concatBindValues.add(concat(entry.getA(), entry.getB()));
}
request.addBindValue(new MultiValueWrapper(concatBindValues, String.class));
}
/**
* Using DB concat at this stage. Usually a DB expression index should match the concat.
*/
private String concat(Object key, Object value) {
StringBuilder sb = new StringBuilder(30);
sb.append(key);
sb.append(separator);
sb.append(value);
if (suffix != null) {
sb.append(suffix);
}
return sb.toString();
}
@Override
public void addSql(SpiExpressionRequest request) {
if (entries.isEmpty()) {
String expr = not ? "1=1" : "1=0";
request.append(expr);
return;
}
String concat = request.getDbPlatformHandler().getConcatOperator();
String concatFormula = "(" + property0 + concat + "'" + separator + "'" + concat + property1;
if (suffix != null && !suffix.isEmpty()) {
concatFormula += concat + "'" + suffix + "'";
}
concatFormula += ")";
request.append(concatFormula);
request.appendInExpression(not, concatBindValues);
}
/**
* Based on the number of values in the in clause.
*/
@Override
public void queryPlanHash(StringBuilder builder) {
if (not) {
builder.append("NotInPairs[");
} else {
builder.append("InPairs[");
}
builder.append(property0).append("-");
builder.append(property1).append("-");
builder.append(separator).append("-");
builder.append(suffix).append(" ?");
if (!multiValueSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(entries.size());
}
builder.append("]");
}
@Override
public int queryBindHash() {
int hc = 92821;
for (Pairs.Entry entry : entries) {
hc = 92821 * hc + entry.hashCode();
}
return hc;
}
@Override
public boolean isSameByBind(SpiExpression other) {
InPairsExpression that = (InPairsExpression) other;
return this.entries.size() == that.entries.size() && entries.equals(that.entries);
}
}
@@ -10,6 +10,7 @@ import io.ebean.FutureRowCount;
import io.ebean.Junction;
import io.ebean.OrderBy;
import io.ebean.PagedList;
import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Version;
@@ -20,12 +21,12 @@ import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.api.SpiJunction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.api.NaturalKeyQueryData;
import java.io.IOException;
import java.sql.Timestamp;
@@ -586,6 +587,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.ilike(propertyName, value);
}
@Override
public ExpressionList<T> inPairs(Pairs pairs) {
return exprList.inPairs(pairs);
}
@Override
public ExpressionList<T> in(String propertyName, Collection<?> values) {
return exprList.in(propertyName, values);