package io.ebeaninternal.server.expression;

import io.ebean.ExampleExpression;
import io.ebean.LikeType;
import io.ebean.bean.EntityBean;
import io.ebean.event.BeanQueryRequest;
import io.ebean.util.SplitName;
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.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;

import java.io.IOException;
import java.util.ArrayList;

A "Query By Example" type of expression.

Pass in an example entity and for each non-null scalar properties an expression is added.


// create an example bean and set the properties
// with the query parameters you want
Customer example = new Customer();
example.setName("Rob%");
example.setNotes("%something%");
List<Customer> list = Ebean.find(Customer.class)
  .where()
  .exampleLike(example)
  .findList();
/** * A "Query By Example" type of expression. * <p> * Pass in an example entity and for each non-null scalar properties an * expression is added. * </p> * <p> * <pre>{@code * // create an example bean and set the properties * // with the query parameters you want * Customer example = new Customer(); * example.setName("Rob%"); * example.setNotes("%something%"); * * List<Customer> list = Ebean.find(Customer.class) * .where() * .exampleLike(example) * .findList(); * * }</pre> */
public class DefaultExampleExpression implements SpiExpression, ExampleExpression {
The example bean containing the properties.
/** * The example bean containing the properties. */
private final EntityBean entity;
Set to true to use case insensitive expressions.
/** * Set to true to use case insensitive expressions. */
private boolean caseInsensitive;
The type of like (RAW, STARTS_WITH, ENDS_WITH etc)
/** * The type of like (RAW, STARTS_WITH, ENDS_WITH etc) */
private LikeType likeType;
By default zeros are excluded.
/** * By default zeros are excluded. */
private boolean includeZeros;
The non null bean properties and found and together added as a list of expressions (like or equal to expressions).
/** * The non null bean properties and found and together added as a list of * expressions (like or equal to expressions). */
private ArrayList<SpiExpression> list;
Construct the query by example expression.
Params:
  • entity – the example entity with non null property values
  • caseInsensitive – if true use case insensitive expressions
  • likeType – the type of Like wild card used
/** * Construct the query by example expression. * * @param entity the example entity with non null property values * @param caseInsensitive if true use case insensitive expressions * @param likeType the type of Like wild card used */
public DefaultExampleExpression(EntityBean entity, boolean caseInsensitive, LikeType likeType) { this.entity = entity; this.caseInsensitive = caseInsensitive; this.likeType = likeType; } DefaultExampleExpression(ArrayList<SpiExpression> source) { this.entity = null; this.list = new ArrayList<>(source.size()); for (SpiExpression expression : source) { list.add(expression.copyForPlanKey()); } } @Override public boolean naturalKey(NaturalKeyQueryData<?> data) { // can't use naturalKey cache return false; } @Override public void simplify() { // do nothing } @Override public void writeDocQuery(DocQueryContext context) throws IOException { if (!list.isEmpty()) { context.startBoolMust(); for (SpiExpression expr : list) { expr.writeDocQuery(context); } context.endBool(); } } @Override public Object getIdEqualTo(String idName) { // always return null for this expression return null; } @Override public SpiExpression copyForPlanKey() { return new DefaultExampleExpression(list); } @Override public String nestedPath(BeanDescriptor<?> desc) { return null; } @Override public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) { list = buildExpressions(desc); if (list != null) { for (SpiExpression aList : list) { aList.containsMany(desc, whereManyJoins); } } } @Override public void prepareExpression(BeanQueryRequest<?> request) { // do nothing } @Override public ExampleExpression includeZeros() { includeZeros = true; return this; } @Override public ExampleExpression caseInsensitive() { caseInsensitive = true; return this; } @Override public ExampleExpression useStartsWith() { likeType = LikeType.STARTS_WITH; return this; } @Override public ExampleExpression useContains() { likeType = LikeType.CONTAINS; return this; } @Override public ExampleExpression useEndsWith() { likeType = LikeType.ENDS_WITH; return this; } @Override public ExampleExpression useEqualTo() { likeType = LikeType.EQUAL_TO; return this; } @Override public void validate(SpiExpressionValidation validation) { for (SpiExpression aList : list) { aList.validate(validation); } }
Adds bind values to the request.
/** * Adds bind values to the request. */
@Override public void addBindValues(SpiExpressionRequest request) { for (SpiExpression item : list) { item.addBindValues(request); } }
Generates and adds the sql to the request.
/** * Generates and adds the sql to the request. */
@Override public void addSql(SpiExpressionRequest request) { if (list.isEmpty()) { request.append(SQL_TRUE); } else { request.append("("); for (int i = 0; i < list.size(); i++) { SpiExpression item = list.get(i); if (i > 0) { request.append(" and "); } item.addSql(request); } request.append(")"); } }
Return a hash for AutoTune query identification.
/** * Return a hash for AutoTune query identification. */
@Override public void queryPlanHash(StringBuilder builder) { builder.append("Example["); for (SpiExpression aList : list) { aList.queryPlanHash(builder); builder.append(","); } builder.append("]"); }
Return a hash for the actual bind values used.
/** * Return a hash for the actual bind values used. */
@Override public int queryBindHash() { int hc = DefaultExampleExpression.class.getName().hashCode(); for (SpiExpression aList : list) { hc = hc * 92821 + aList.queryBindHash(); } return hc; } @Override public boolean isSameByBind(SpiExpression other) { DefaultExampleExpression that = (DefaultExampleExpression) other; if (this.list.size() != that.list.size()) { return false; } for (int i = 0; i < list.size(); i++) { if (!list.get(i).isSameByBind(that.list.get(i))) { return false; } } return true; }
Build the List of expressions.
/** * Build the List of expressions. */
private ArrayList<SpiExpression> buildExpressions(BeanDescriptor<?> beanDescriptor) { ArrayList<SpiExpression> list = new ArrayList<>(); addExpressions(list, beanDescriptor, entity, null); return list; }
Add expressions to the list for all the non-null properties (and do this recursively).
/** * Add expressions to the list for all the non-null properties (and do this recursively). */
private void addExpressions(ArrayList<SpiExpression> list, BeanDescriptor<?> beanDescriptor, EntityBean bean, String prefix) { for (BeanProperty beanProperty : beanDescriptor.propertiesAll()) { if (!beanProperty.isTransient()) { Object value = beanProperty.getValue(bean); if (value != null) { String propName = SplitName.add(prefix, beanProperty.getName()); if (beanProperty.isScalar()) { if (value instanceof String) { list.add(new LikeExpression(propName, value, caseInsensitive, likeType)); } else { // exclude the zero values typically to weed out // primitive int and long that initialise to 0 if (includeZeros || !isZero(value)) { list.add(new SimpleExpression(propName, Op.EQ, value)); } } } else if ((beanProperty instanceof BeanPropertyAssocOne) && (value instanceof EntityBean)) { BeanPropertyAssocOne<?> assocOne = (BeanPropertyAssocOne<?>) beanProperty; BeanDescriptor<?> targetDescriptor = assocOne.getTargetDescriptor(); addExpressions(list, targetDescriptor, (EntityBean) value, propName); } } } } }
Return true if the value is a numeric zero.
/** * Return true if the value is a numeric zero. */
private boolean isZero(Object value) { if (value instanceof Number) { if (((Number) value).doubleValue() == 0) { return true; } } return false; } }