#919 - io.ebean package initial

This commit is contained in:
Rob Bygrave
2016-12-11 23:23:22 +13:00
parent 5821dc96b9
commit 971e2dc91b
2134 changed files with 10721 additions and 8652 deletions
@@ -0,0 +1,97 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.ManyWhereJoins;
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.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.SplitName;
/**
* Base class for simple expressions.
*/
public abstract class AbstractExpression implements SpiExpression {
protected final String propName;
protected AbstractExpression(String propName) {
this.propName = propName;
}
@Override
public void simplify() {
// do nothing
}
@Override
public Object getIdEqualTo(String idName) {
// override on SimpleExpression
return null;
}
@Override
public SpiExpression copyForPlanKey() {
return this;
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return propertyNestedPath(propName, desc);
}
protected String propertyNestedPath(String propertyName, BeanDescriptor<?> desc) {
if (propertyName != null) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName);
if (elProp != null && elProp.containsMany()) {
return SplitName.begin(propName);
}
}
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
propertyContainsMany(propName, desc, manyWhereJoin);
}
/**
* Check the logical property path for containing a 'many' property.
*/
protected void propertyContainsMany(String propertyName, BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
if (propertyName != null) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(propertyName);
if (elProp != null) {
if (elProp.containsFormulaWithJoin()) {
// for findRowCount query select clause
manyWhereJoin.addFormulaWithJoin(propertyName);
}
if (elProp.containsMany()) {
// for findRowCount we join to a many property
manyWhereJoin.add(elProp);
if (elProp.isAggregation()) {
manyWhereJoin.setAggregation();
}
}
}
}
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
// do nothing
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(propName);
}
protected final ElPropertyValue getElProp(SpiExpressionRequest request) {
return request.getBeanDescriptor().getElGetValue(propName);
}
}
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
/**
* Base class for TextExpressions that are only executable by doc store.
* <p>
* This means they can not be part of a SQL query nor do they use the built in query plan cache etc.
* </p>
*/
public abstract class AbstractTextExpression extends AbstractExpression {
protected AbstractTextExpression(String propName) {
super(propName);
}
@Override
public Object getIdEqualTo(String idName) {
// always null for this expression
return null;
}
@Override
public void addSql(SpiExpressionRequest request) {
// do nothing, only execute against document store
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// do nothing, only execute against document store
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
// do nothing, only execute against document store
}
@Override
public int queryBindHash() {
return 0;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
// do not compare by plan / bind values (this way)
return false;
}
@Override
public boolean isSameByBind(SpiExpression other) {
// do not compare by plan / bind values (this way)
return false;
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.expression;
/**
* Abstract expression that helps with named parameter use.
*/
public abstract class AbstractValueExpression extends AbstractExpression {
protected final Object bindValue;
/**
* Construct with property name and potential named parameter.
*/
protected AbstractValueExpression(String propName, Object bindValue) {
super(propName);
this.bindValue = bindValue;
}
/**
* Return the bind value taking into account named parameters.
*/
protected Object value() {
return NamedParamHelp.value(bindValue);
}
/**
* Return the String bind value taking into account named parameters.
*/
protected String strValue() {
return NamedParamHelp.valueAsString(bindValue);
}
}
@@ -0,0 +1,174 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
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.el.ElPropertyDeploy;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
class AllEqualsExpression extends NonPrepareExpression {
private final Map<String, Object> propMap;
AllEqualsExpression(Map<String, Object> propMap) {
this.propMap = propMap;
}
protected String name(String propName) {
return propName;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeAllEquals(propMap);
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
if (propMap != null) {
for (String propertyName : propMap.keySet()) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(propertyName));
if (elProp != null && elProp.containsMany()) {
manyWhereJoin.add(elProp);
}
}
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (String propName : propMap.keySet()) {
validation.validate(propName);
}
}
@Override
public void addBindValues(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
return;
}
for (Object value : propMap.values()) {
// null value uses is null clause
if (value != null) {
request.addBindValue(value);
}
}
}
@Override
public void addSql(SpiExpressionRequest request) {
if (propMap.isEmpty()) {
return;
}
request.append("(");
int count = 0;
for (Map.Entry<String, Object> entry : propMap.entrySet()) {
Object value = entry.getValue();
String propName = entry.getKey();
if (count > 0) {
request.append("and ");
}
request.append(name(propName));
if (value == null) {
request.append(" is null ");
} else {
request.append(" = ? ");
}
count++;
}
request.append(")");
}
/**
* Based on the properties and whether they are null.
* <p>
* The null check is required due to the "is null" sql being generated.
* </p>
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(AllEqualsExpression.class);
for (Entry<String, Object> entry : propMap.entrySet()) {
Object value = entry.getValue();
String propName = entry.getKey();
builder.add(propName).add(value == null ? 0 : 1);
builder.bindIfNotNull(value);
}
}
@Override
public int queryBindHash() {
int hc = 92821;
for (Object value : propMap.values()) {
hc = hc * 92821 + (value == null ? 0 : value.hashCode());
}
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof AllEqualsExpression)) {
return false;
}
AllEqualsExpression that = (AllEqualsExpression) other;
return isSameByValue(that, false);
}
@Override
public boolean isSameByBind(SpiExpression other) {
if (!(other instanceof AllEqualsExpression)) {
return false;
}
AllEqualsExpression that = (AllEqualsExpression) other;
return isSameByValue(that, true);
}
private boolean isSameByValue(AllEqualsExpression that, boolean byValue) {
if (propMap.size() != that.propMap.size()) {
return false;
}
Iterator<Entry<String, Object>> thisIt = propMap.entrySet().iterator();
Iterator<Entry<String, Object>> thatIt = that.propMap.entrySet().iterator();
while (thisIt.hasNext() && thatIt.hasNext()) {
Entry<String, Object> thisNext = thisIt.next();
Entry<String, Object> thatNext = thatIt.next();
if (!thisNext.getKey().equals(thatNext.getKey())) {
return false;
}
if (!Same.sameBy(byValue, thisNext.getValue(), thatNext.getValue())) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,93 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import java.io.IOException;
/**
* Contains expression for ARRAY type.
*/
public class ArrayContainsExpression extends AbstractExpression {
private final boolean contains;
private final Object[] values;
protected ArrayContainsExpression(String propName, boolean contains, Object... values) {
super(propName);
this.contains = contains;
this.values = values;
if (values == null || values.length == 0) {
throw new IllegalArgumentException("values must not be null or empty");
}
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
if (values.length == 1) {
context.writeEqualTo(propName, values[0]);
} else {
if (contains) {
context.startBoolMust();
} else {
context.startBoolMustNot();
}
for (Object value : values) {
context.writeEqualTo(propName, value);
}
context.endBool();
}
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(ArrayContainsExpression.class).add(propName).add(contains);
builder.bind(values.length);
}
@Override
public int queryBindHash() {
int hc = values[0].hashCode();
for (int i = 1; i < values.length; i++) {
hc = hc * 92821 + values[i].hashCode();
}
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof ArrayContainsExpression)) {
return false;
}
ArrayContainsExpression that = (ArrayContainsExpression) other;
return this.propName.equals(that.propName)
&& this.contains == that.contains
&& this.values.length == that.values.length;
}
@Override
public boolean isSameByBind(SpiExpression other) {
ArrayContainsExpression that = (ArrayContainsExpression) other;
for (int i = 0; i < this.values.length; i++) {
if (!this.values[i].equals(that.values[i])) {
return false;
}
}
return true;
}
@Override
public void addSql(SpiExpressionRequest request) {
request.getDbPlatformHandler().arrayContains(request, propName, contains, values);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
for (Object value : values) {
request.addBindValue(value);
}
}
}
@@ -0,0 +1,59 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import java.io.IOException;
/**
* IsEmpty expression for ARRAY type.
*/
public class ArrayIsEmptyExpression extends AbstractExpression {
private final boolean empty;
protected ArrayIsEmptyExpression(String propName, boolean empty) {
super(propName);
this.empty = empty;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeExists(!empty, propName);
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(ArrayIsEmptyExpression.class).add(propName);
}
@Override
public int queryBindHash() {
return empty ? 0 : 92821;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof ArrayIsEmptyExpression)) {
return false;
}
ArrayIsEmptyExpression that = (ArrayIsEmptyExpression) other;
return this.propName.equals(that.propName) && this.empty == that.empty;
}
@Override
public boolean isSameByBind(SpiExpression other) {
return true;
}
@Override
public void addSql(SpiExpressionRequest request) {
request.getDbPlatformHandler().arrayIsEmpty(request, propName, empty);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// nothing to bind
}
}
@@ -0,0 +1,76 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import java.io.IOException;
class BetweenExpression extends AbstractExpression {
private static final String BETWEEN = " between ";
private final Object valueHigh;
private final Object valueLow;
BetweenExpression(String propertyName, Object valueLow, Object valueHigh) {
super(propertyName);
this.valueLow = valueLow;
this.valueHigh = valueHigh;
}
private Object low() {
return NamedParamHelp.value(valueLow);
}
private Object high() {
return NamedParamHelp.value(valueHigh);
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeRange(propName, Op.GT_EQ, low(), Op.LT_EQ, high());
}
@Override
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(low());
request.addBindValue(high());
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(propName).append(BETWEEN).append(" ? and ? ");
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(BetweenExpression.class).add(propName);
builder.bind(2);
}
@Override
public int queryBindHash() {
int hc = low().hashCode();
hc = hc * 92821 + high().hashCode();
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof BetweenExpression)) {
return false;
}
BetweenExpression that = (BetweenExpression) other;
return this.propName.equals(that.propName);
}
@Override
public boolean isSameByBind(SpiExpression other) {
BetweenExpression that = (BetweenExpression) other;
return low().equals(that.low()) && high().equals(that.high());
}
}
@@ -0,0 +1,114 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
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.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.SplitName;
import java.io.IOException;
/**
* Between expression where a value is between two properties.
*/
class BetweenPropertyExpression extends NonPrepareExpression {
private static final String BETWEEN = " between ";
private final String lowProperty;
private final String highProperty;
private final Object value;
BetweenPropertyExpression(String lowProperty, String highProperty, Object value) {
this.lowProperty = lowProperty;
this.highProperty = highProperty;
this.value = value;
}
protected String name(String propName) {
return propName;
}
private Object val() {
return NamedParamHelp.value(value);
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.startBoolMust();
context.writeSimple(Op.LT_EQ, lowProperty, val());
context.writeSimple(Op.GT_EQ, highProperty, val());
context.endBool();
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(lowProperty));
if (elProp != null && elProp.containsMany()) {
// assumes highProperty is also nested property which seems reasonable
return SplitName.begin(lowProperty);
}
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
ElPropertyDeploy elProp = desc.getElPropertyDeploy(name(lowProperty));
if (elProp != null && elProp.containsMany()) {
manyWhereJoin.add(elProp);
}
elProp = desc.getElPropertyDeploy(name(highProperty));
if (elProp != null && elProp.containsMany()) {
manyWhereJoin.add(elProp);
}
}
@Override
public void validate(SpiExpressionValidation validation) {
validation.validate(lowProperty);
validation.validate(highProperty);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
request.addBindValue(val());
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(" ? ").append(BETWEEN).append(name(lowProperty)).append(" and ").append(name(highProperty));
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(BetweenPropertyExpression.class).add(lowProperty).add(highProperty);
builder.bind(1);
}
@Override
public int queryBindHash() {
return val().hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof BetweenPropertyExpression)) {
return false;
}
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
return lowProperty.equals(that.lowProperty) && highProperty.equals(that.highProperty);
}
@Override
public boolean isSameByBind(SpiExpression other) {
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
return val().equals(that.val());
}
}
@@ -0,0 +1,79 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.el.ElPropertyValue;
import java.io.IOException;
class CaseInsensitiveEqualExpression extends AbstractValueExpression {
CaseInsensitiveEqualExpression(String propertyName, Object value) {
super(propertyName, value);
}
/**
* Return the bind value taking into account named parameters.
*/
private String val() {
return strValue().toLowerCase();
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeIEqualTo(propName, val());
}
@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("lower(").append(pname).append(") =? ");
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(CaseInsensitiveEqualExpression.class).add(propName);
builder.bind(1);
}
@Override
public int queryBindHash() {
return val().hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof CaseInsensitiveEqualExpression)) {
return false;
}
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
return this.propName.equals(that.propName);
}
@Override
public boolean isSameByBind(SpiExpression other) {
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
return val().equals(that.val());
}
}
@@ -0,0 +1,324 @@
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.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
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 io.ebeaninternal.server.query.SplitName;
import java.io.IOException;
import java.util.ArrayList;
/**
* 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.
*/
private final EntityBean entity;
/**
* Set to true to use case insensitive expressions.
*/
private boolean caseInsensitive;
/**
* The type of like (RAW, STARTS_WITH, ENDS_WITH etc)
*/
private LikeType likeType;
/**
* 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).
*/
private ArrayList<SpiExpression> list;
/**
* 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 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.
*/
@Override
public void addBindValues(SpiExpressionRequest request) {
for (SpiExpression item : list) {
item.addBindValues(request);
}
}
/**
* Generates and adds the sql to the request.
*/
@Override
public void addSql(SpiExpressionRequest request) {
if (!list.isEmpty()) {
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.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(DefaultExampleExpression.class);
for (SpiExpression aList : list) {
aList.queryPlanHash(builder);
}
}
/**
* 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 isSameByPlan(SpiExpression other) {
if (!(other instanceof DefaultExampleExpression)) {
return false;
}
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).isSameByPlan(that.list.get(i))) {
return false;
}
}
return true;
}
@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.
*/
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).
*/
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.
*/
private boolean isZero(Object value) {
if (value instanceof Number) {
Number num = (Number) value;
double doubleValue = num.doubleValue();
if (doubleValue == 0) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,532 @@
package io.ebeaninternal.server.expression;
import io.ebean.ExampleExpression;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.Junction;
import io.ebean.LikeType;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.search.Match;
import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import io.ebeaninternal.api.SpiExpressionFactory;
import io.ebeaninternal.api.SpiQuery;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Default Expression factory for creating standard expressions.
*/
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, boolean nativeIlike) {
this.equalsWithNullAsNoop = equalsWithNullAsNoop;
this.nativeIlike = nativeIlike;
}
public ExpressionFactory createExpressionFactory() {
return this;
}
public String getLang() {
return "sql";
}
@Override
public Expression textMatch(String propertyName, String search, Match options) {
return new TextMatchExpression(propertyName, search, options);
}
@Override
public Expression textMultiMatch(String query, MultiMatch options) {
return new TextMultiMatchExpression(query, options);
}
@Override
public Expression textSimple(String search, TextSimple options) {
return new TextSimpleExpression(search, options);
}
@Override
public Expression textQueryString(String search, TextQueryString options) {
return new TextQueryStringExpression(search, options);
}
@Override
public Expression textCommonTerms(String search, TextCommonTerms options) {
return new TextCommonTermsExpression(search, options);
}
public Expression jsonExists(String propertyName, String path) {
return new JsonPathExpression(propertyName, path, Op.EXISTS, null);
}
public Expression jsonNotExists(String propertyName, String path) {
return new JsonPathExpression(propertyName, path, Op.NOT_EXISTS, null);
}
public Expression jsonEqualTo(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.EQ, value);
}
public Expression jsonNotEqualTo(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.NOT_EQ, value);
}
public Expression jsonGreaterThan(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.GT, value);
}
public Expression jsonGreaterOrEqual(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.GT_EQ, value);
}
public Expression jsonLessThan(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.LT, value);
}
public Expression jsonLessOrEqualTo(String propertyName, String path, Object value) {
return new JsonPathExpression(propertyName, path, Op.LT_EQ, value);
}
public Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
return new JsonPathExpression(propertyName, path, lowerValue, upperValue);
}
@Override
public Expression arrayContains(String propertyName, Object... values) {
return new ArrayContainsExpression(propertyName, true, values);
}
@Override
public Expression arrayNotContains(String propertyName, Object... values) {
return new ArrayContainsExpression(propertyName, false, values);
}
@Override
public Expression arrayIsEmpty(String propertyName) {
return new ArrayIsEmptyExpression(propertyName, true);
}
@Override
public Expression arrayIsNotEmpty(String propertyName) {
return new ArrayIsEmptyExpression(propertyName, false);
}
/**
* Equal To - property equal to the given value.
*/
public Expression eq(String propertyName, Object value) {
if (value == null) {
return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNull(propertyName);
}
return new SimpleExpression(propertyName, Op.EQ, value);
}
/**
* Not Equal To - property not equal to the given value.
*/
public Expression ne(String propertyName, Object value) {
if (value == null) {
return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNotNull(propertyName);
}
return new SimpleExpression(propertyName, Op.NOT_EQ, value);
}
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
public Expression ieq(String propertyName, String value) {
if (value == null) {
return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNull(propertyName);
}
return new CaseInsensitiveEqualExpression(propertyName, value);
}
/**
* Create for named parameter use (and without support for equalsWithNullAsNoop).
*/
public Expression ieqObject(String propertyName, Object value) {
return new CaseInsensitiveEqualExpression(propertyName, value);
}
/**
* Between - property between the two given values.
*/
public Expression between(String propertyName, Object value1, Object value2) {
return new BetweenExpression(propertyName, value1, value2);
}
/**
* Between - value between two given properties.
*/
public Expression betweenProperties(String lowProperty, String highProperty, Object value) {
return new BetweenPropertyExpression(lowProperty, highProperty, value);
}
/**
* Greater Than - property greater than the given value.
*/
public Expression gt(String propertyName, Object value) {
return new SimpleExpression(propertyName, Op.GT, value);
}
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public Expression ge(String propertyName, Object value) {
return new SimpleExpression(propertyName, Op.GT_EQ, value);
}
/**
* Less Than - property less than the given value.
*/
public Expression lt(String propertyName, Object value) {
return new SimpleExpression(propertyName, Op.LT, value);
}
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public Expression le(String propertyName, Object value) {
return new SimpleExpression(propertyName, Op.LT_EQ, value);
}
/**
* Is Null - property is null.
*/
public Expression isNull(String propertyName) {
return new NullExpression(propertyName, false);
}
/**
* Is Not Null - property is not null.
*/
public Expression isNotNull(String propertyName) {
return new NullExpression(propertyName, true);
}
private EntityBean checkEntityBean(Object bean) {
if (bean == null || (!(bean instanceof EntityBean))) {
throw new IllegalStateException("Expecting an EntityBean");
}
return (EntityBean) bean;
}
/**
* Case insensitive {@link #exampleLike(Object)}
*/
public ExampleExpression iexampleLike(Object example) {
return new DefaultExampleExpression(checkEntityBean(example), true, LikeType.RAW);
}
/**
* Create the query by Example expression which is case sensitive and using
* LikeType.RAW (you need to add you own wildcards % and _).
*/
public ExampleExpression exampleLike(Object example) {
return new DefaultExampleExpression(checkEntityBean(example), false, LikeType.RAW);
}
/**
* Create the query by Example expression specifying more options.
*/
public ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType) {
return new DefaultExampleExpression(checkEntityBean(example), caseInsensitive, likeType);
}
@Override
public Expression like(String propertyName, Object value, boolean caseInsensitive, LikeType likeType) {
return new LikeExpression(propertyName, value, caseInsensitive, likeType);
}
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
public Expression like(String propertyName, String value) {
return new LikeExpression(propertyName, value, false, LikeType.RAW);
}
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
public Expression ilike(String propertyName, String value) {
if (nativeIlike) {
return new NativeILikeExpression(propertyName, value);
} else {
return new LikeExpression(propertyName, value, true, LikeType.RAW);
}
}
/**
* Starts With - property like value%.
*/
public Expression startsWith(String propertyName, String value) {
return new LikeExpression(propertyName, value, false, LikeType.STARTS_WITH);
}
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
public Expression istartsWith(String propertyName, String value) {
return new LikeExpression(propertyName, value, true, LikeType.STARTS_WITH);
}
/**
* Ends With - property like %value.
*/
public Expression endsWith(String propertyName, String value) {
return new LikeExpression(propertyName, value, false, LikeType.ENDS_WITH);
}
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
public Expression iendsWith(String propertyName, String value) {
return new LikeExpression(propertyName, value, true, LikeType.ENDS_WITH);
}
/**
* Contains - property like %value%.
*/
public Expression contains(String propertyName, String value) {
return new LikeExpression(propertyName, value, false, LikeType.CONTAINS);
}
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
public Expression icontains(String propertyName, String value) {
return new LikeExpression(propertyName, value, true, LikeType.CONTAINS);
}
/**
* In - property has a value in the array of values.
*/
public Expression in(String propertyName, Object[] values) {
return new InExpression(propertyName, values, false);
}
/**
* In - using a subQuery.
*/
public Expression in(String propertyName, Query<?> subQuery) {
return new InQueryExpression(propertyName, (SpiQuery<?>) subQuery, false);
}
/**
* In - property has a value in the collection of values.
*/
public Expression in(String propertyName, Collection<?> values) {
return new InExpression(propertyName, values, false);
}
/**
* In - property has a value in the array of values.
*/
public Expression notIn(String propertyName, Object[] values) {
return new InExpression(propertyName, values, true);
}
/**
* Not In - property has a value in the collection of values.
*/
public Expression notIn(String propertyName, Collection<?> values) {
return new InExpression(propertyName, values, true);
}
/**
* In - using a subQuery.
*/
public Expression notIn(String propertyName, Query<?> subQuery) {
return new InQueryExpression(propertyName, (SpiQuery<?>) subQuery, true);
}
/**
* Exists subquery
*/
@Override
public Expression exists(Query<?> subQuery) {
return new ExistsQueryExpression((SpiQuery<?>) subQuery, false);
}
/**
* Not exists subquery
*/
@Override
public Expression notExists(Query<?> subQuery) {
return new ExistsQueryExpression((SpiQuery<?>) subQuery, true);
}
@Override
public Expression isEmpty(String propertyName) {
return new IsEmptyExpression(propertyName, true);
}
@Override
public Expression isNotEmpty(String propertyName) {
return new IsEmptyExpression(propertyName, false);
}
/**
* Id Equal to - ID property is equal to the value.
*/
public Expression idEq(Object value) {
if (value == null) {
throw new NullPointerException("The id value is null");
}
return new IdExpression(value);
}
/**
* Id IN a list of id values.
*/
public Expression idIn(List<?> idList) {
return new IdInExpression(idList);
}
/**
* Id IN a list of id values.
*/
public Expression idIn(Object... idValues) {
return new IdInExpression(Arrays.asList(idValues));
}
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap a map keyed by property names.
*/
public Expression allEq(Map<String, Object> propertyMap) {
return new AllEqualsExpression(propertyMap);
}
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
*/
public Expression raw(String raw, Object value) {
return new RawExpression(raw, new Object[]{value});
}
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
*/
public Expression raw(String raw, Object[] values) {
return new RawExpression(raw, values);
}
/**
* Add raw expression with no parameters.
*/
public Expression raw(String raw) {
return new RawExpression(raw, EMPTY_ARRAY);
}
/**
* And - join two expressions with a logical and.
*/
public Expression and(Expression expOne, Expression expTwo) {
return new LogicExpression.And(expOne, expTwo);
}
/**
* Or - join two expressions with a logical or.
*/
public Expression or(Expression expOne, Expression expTwo) {
return new LogicExpression.Or(expOne, expTwo);
}
/**
* Negate the expression (prefix it with NOT).
*/
public Expression not(Expression exp) {
return new NotExpression(exp);
}
/**
* Return a list of expressions that will be joined by AND's.
*/
public <T> Junction<T> conjunction(Query<T> query) {
return new JunctionExpression<>(Junction.Type.AND, query, query.where());
}
/**
* Return a list of expressions that will be joined by OR's.
*/
public <T> Junction<T> disjunction(Query<T> query) {
return new JunctionExpression<>(Junction.Type.OR, query, query.where());
}
/**
* Return a list of expressions that will be joined by AND's.
*/
public <T> Junction<T> conjunction(Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(Junction.Type.AND, query, parent);
}
/**
* Return a list of expressions that will be joined by OR's.
*/
public <T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(Junction.Type.OR, query, parent);
}
/**
* Return a list of expressions that are wrapped by NOT.
*/
public <T> Junction<T> junction(Junction.Type type, Query<T> query) {
return new JunctionExpression<>(type, query, query.where());
}
/**
* Create and return a Full text junction (Must, Must Not or Should).
*/
@Override
public <T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(type, query, parent);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,158 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.core.DbExpressionHandler;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.DeployParser;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.type.DataBind;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class DefaultExpressionRequest implements SpiExpressionRequest {
private final SpiOrmQueryRequest<?> queryRequest;
private final BeanDescriptor<?> beanDescriptor;
private final StringBuilder sql = new StringBuilder();
private final List<Object> bindValues = new ArrayList<>();
private final DeployParser deployParser;
private final Binder binder;
private final SpiExpressionList<?> expressionList;
private int paramIndex;
private StringBuilder bindLog;
public DefaultExpressionRequest(SpiOrmQueryRequest<?> queryRequest, DeployParser deployParser, Binder binder, SpiExpressionList<?> expressionList) {
this.queryRequest = queryRequest;
this.beanDescriptor = queryRequest.getBeanDescriptor();
this.deployParser = deployParser;
this.binder = binder;
this.expressionList = expressionList;
// immediately build the list of bind values (callback style)
expressionList.addBindValues(this);
}
public DefaultExpressionRequest(BeanDescriptor<?> beanDescriptor) {
this.beanDescriptor = beanDescriptor;
this.queryRequest = null;
this.deployParser = null;
this.binder = null;
this.expressionList = null;
}
/**
* Build sql for the underlying expression list.
*/
public String buildSql() {
expressionList.addSql(this);
return sql.toString();
}
/**
* Bind the values from the underlying expression list.
*/
public void bind(DataBind dataBind) throws SQLException {
for (Object bindValue : bindValues) {
binder.bindObject(dataBind, bindValue);
}
if (bindLog != null) {
dataBind.append(bindLog.toString());
}
}
@Override
public DbExpressionHandler getDbPlatformHandler() {
return binder.getDbExpressionHandler();
}
@Override
public String parseDeploy(String logicalProp) {
String s = deployParser.getDeployWord(logicalProp);
return s == null ? logicalProp : s;
}
/**
* Append the database platform like clause.
*/
@Override
public void appendLike() {
sql.append(" ");
sql.append(queryRequest.getDBLikeClause());
sql.append(" ");
}
/**
* Increments the parameter index and returns that value.
*/
@Override
public int nextParameter() {
return ++paramIndex;
}
@Override
public BeanDescriptor<?> getBeanDescriptor() {
return beanDescriptor;
}
@Override
public SpiOrmQueryRequest<?> getQueryRequest() {
return queryRequest;
}
/**
* Append text the underlying sql expression.
*/
@Override
public SpiExpressionRequest append(String sqlExpression) {
sql.append(sqlExpression);
return this;
}
@Override
public void addBindEncryptKey(Object bindValue) {
bindValues.add(bindValue);
bindLog("****");
}
@Override
public void addBindValue(Object bindValue) {
bindValues.add(bindValue);
bindLog(bindValue);
}
private void bindLog(Object val) {
if (bindLog == null) {
bindLog = new StringBuilder();
} else {
bindLog.append(",");
}
bindLog.append(val);
}
public String getBindLog() {
return bindLog == null ? "" : bindLog.toString();
}
@Override
public String getSql() {
return sql.toString();
}
@Override
public List<Object> getBindValues() {
return bindValues;
}
}
@@ -0,0 +1,170 @@
package io.ebeaninternal.server.expression;
import io.ebean.Junction;
import io.ebean.LikeType;
import io.ebean.plugin.ExpressionPath;
import io.ebean.search.Match;
import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Context for writing a doc store query.
*/
public interface DocQueryContext {
/**
* Start a junction.
*/
void startBool(Junction.Type type) throws IOException;
/**
* Start a conjunction.
*/
void startBoolMust() throws IOException;
/**
* Start a boolean NOT.
*/
void startBoolMustNot() throws IOException;
/**
* End a bool expression/group.
*/
void endBool() throws IOException;
/**
* Write a equalTo expression.
*/
void writeEqualTo(String propertyName, Object value) throws IOException;
/**
* Write a case insensitive equalTo expression.
*/
void writeIEqualTo(String propName, String value) throws IOException;
/**
* Write a range operation with one value.
*/
void writeRange(String propertyName, String rangeType, Object value) throws IOException;
/**
* Write a range operation with a lower and upper values.
*/
void writeRange(String propertyName, Op lowOp, Object valueLow, Op highOp, Object valueHigh) throws IOException;
/**
* Write an In expression.
*/
void writeIn(String propertyName, Object[] values, boolean not) throws IOException;
/**
* Write an Id in expression.
*/
void writeIds(List<?> idList) throws IOException;
/**
* Write an Id equals expression.
*/
void writeId(Object value) throws IOException;
/**
* Write a raw expression with bind values (might not be supported).
*/
void writeRaw(String raw, Object[] values) throws IOException;
/**
* Write an exists expression.
*/
void writeExists(boolean notNull, String propertyName) throws IOException;
/**
* Write one of the base expressions.
*/
void writeSimple(Op type, String propertyName, Object value) throws IOException;
/**
* Write an all equals expression.
*/
void writeAllEquals(Map<String, Object> propMap) throws IOException;
/**
* Write a Like expression.
*/
void writeLike(String propName, String val, LikeType type, boolean caseInsensitive) throws IOException;
/**
* Write a Match expression.
*/
void writeMatch(String propName, String search, Match options) throws IOException;
/**
* Write a Multi-match expression.
*/
void writeMultiMatch(String search, MultiMatch options) throws IOException;
/**
* Write a simple expression.
*/
void writeTextSimple(String search, TextSimple options) throws IOException;
/**
* Write a common terms expression.
*/
void writeTextCommonTerms(String search, TextCommonTerms options) throws IOException;
/**
* Write a query string expression.
*/
void writeTextQueryString(String search, TextQueryString options) throws IOException;
/**
* Start a Bool which may contain some of Must, Must Not, Should.
*/
void startBoolGroup() throws IOException;
/**
* Start a Must, Must Not or Should list.
*/
void startBoolGroupList(Junction.Type type) throws IOException;
/**
* End a Must, Must Not or Should list.
*/
void endBoolGroupList() throws IOException;
/**
* End the Bool group.
*/
void endBoolGroup() throws IOException;
/**
* Return the expression path for the given property path.
*/
ExpressionPath getExpressionPath(String propName);
/**
* Start nested path expressions.
*/
void startNested(String nestedPath) throws IOException;
/**
* End nested path expressions.
*/
void endNested() throws IOException;
/**
* Start a not wrapping an expression.
*/
void startNot() throws IOException;
/**
* End a not wrapper.
*/
void endNot() throws IOException;
}
@@ -0,0 +1,146 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.query.CQuery;
import java.io.IOException;
import java.util.List;
class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpression {
protected final boolean not;
protected final SpiQuery<?> subQuery;
protected List<Object> bindParams;
protected String sql;
public ExistsQueryExpression(SpiQuery<?> subQuery, boolean not) {
this.subQuery = subQuery;
this.not = not;
}
ExistsQueryExpression(boolean not, String sql, List<Object> bindParams) {
this.not = not;
this.sql = sql;
this.bindParams = bindParams;
this.subQuery = null;
}
@Override
public void simplify() {
// do nothing
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
throw new IllegalStateException("Not supported");
}
@Override
public Object getIdEqualTo(String idName) {
// always return null for this expression
return null;
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
CQuery<?> subQuery = compileSubQuery(request);
this.bindParams = subQuery.getPredicates().getWhereExprBindValues();
this.sql = subQuery.getGeneratedSql().replace('\n', ' ');
}
@Override
public SpiExpression copyForPlanKey() {
return this;
}
/**
* Compile/build the sub query.
*/
protected CQuery<?> compileSubQuery(BeanQueryRequest<?> queryRequest) {
SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer();
return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction());
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(ExistsQueryExpression.class).add(not);
builder.add(sql).add(bindParams.size());
}
@Override
public int queryBindHash() {
return subQuery.queryBindHash();
}
@Override
public void addSql(SpiExpressionRequest request) {
if (not) {
request.append(" not");
}
request.append(" exists (");
request.append(sql);
request.append(") ");
}
@Override
public void addBindValues(SpiExpressionRequest request) {
for (Object bindParam : bindParams) {
request.addBindValue(bindParam);
}
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof ExistsQueryExpression)) {
return false;
}
ExistsQueryExpression that = (ExistsQueryExpression) other;
return this.sql.equals(that.sql)
&& this.not == that.not
&& this.bindParams.size() == that.bindParams.size();
}
@Override
public boolean isSameByBind(SpiExpression other) {
ExistsQueryExpression that = (ExistsQueryExpression) other;
if (this.bindParams.size() != that.bindParams.size()) {
return false;
}
for (int i = 0; i < bindParams.size(); i++) {
if (!bindParams.get(i).equals(that.bindParams.get(i))) {
return false;
}
}
return true;
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// Nothing to do for exists expression
}
@Override
public void validate(SpiExpressionValidation validation) {
// Nothing to do for exists expression
}
}
@@ -0,0 +1,43 @@
package io.ebeaninternal.server.expression;
import java.io.Serializable;
/**
* This is the path prefix for filterMany.
* <p>
* The actual path can change due to FetchConfig query joins that proceed the
* query that includes the filterMany.
* </p>
*/
public class FilterExprPath implements Serializable {
private static final long serialVersionUID = -6420905565372842018L;
/**
* The path of the filterMany.
*/
private final String path;
public FilterExprPath(String path) {
this.path = path;
}
/**
* Return a copy of the FilterExprPath trimming off leading part of the path
* due to a proceeding (earlier) query join etc.
*/
public FilterExprPath trimPath(int prefixTrim) {
if (prefixTrim >= path.length()) {
return new FilterExprPath(null);
}
return new FilterExprPath(path.substring(prefixTrim));
}
/**
* Return the path. This is a prefix used in the filterMany expressions.
*/
public String getPath() {
return path;
}
}
@@ -0,0 +1,153 @@
package io.ebeaninternal.server.expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebeaninternal.api.SpiExpressionList;
import javax.persistence.PersistenceException;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class FilterExpressionList<T> extends DefaultExpressionList<T> {
private static final String notAllowedMessage = "This method is not allowed on a filter";
private final Query<T> rootQuery;
private final FilterExprPath pathPrefix;
public FilterExpressionList(FilterExprPath pathPrefix, FilterExpressionList<T> original) {
super(null, original.expr, null, original.getUnderlyingList());
this.pathPrefix = pathPrefix;
this.rootQuery = original.rootQuery;
}
public FilterExpressionList(FilterExprPath pathPrefix, ExpressionFactory expr, Query<T> rootQuery) {
super(null, expr, null);
this.pathPrefix = pathPrefix;
this.rootQuery = rootQuery;
}
@Override
public SpiExpressionList<?> trimPath(int prefixTrim) {
return new FilterExpressionList<>(pathPrefix.trimPath(prefixTrim), this);
}
@Override
public ExpressionList<T> filterMany(String prop) {
return rootQuery.filterMany(prop);
}
@Override
public FutureIds<T> findFutureIds() {
return rootQuery.findFutureIds();
}
@Override
public FutureList<T> findFutureList() {
return rootQuery.findFutureList();
}
@Override
public FutureRowCount<T> findFutureCount() {
return rootQuery.findFutureCount();
}
@Override
public List<T> findList() {
return rootQuery.findList();
}
@Override
public <K> Map<K, T> findMap() {
return rootQuery.findMap();
}
@Override
public int findCount() {
return rootQuery.findCount();
}
@Override
public Set<T> findSet() {
return rootQuery.findSet();
}
@Override
public T findUnique() {
return rootQuery.findUnique();
}
@Override
public ExpressionList<T> having() {
throw new PersistenceException(notAllowedMessage);
}
@Override
public ExpressionList<T> idEq(Object value) {
throw new PersistenceException(notAllowedMessage);
}
@Override
public ExpressionList<T> idIn(List<?> idValues) {
throw new PersistenceException(notAllowedMessage);
}
@Override
public OrderBy<T> order() {
return rootQuery.order();
}
@Override
public Query<T> order(String orderByClause) {
return rootQuery.order(orderByClause);
}
@Override
public Query<T> orderBy(String orderBy) {
return rootQuery.orderBy(orderBy);
}
@Override
public Query<T> query() {
return rootQuery;
}
@Override
public Query<T> select(String properties) {
throw new PersistenceException(notAllowedMessage);
}
@Override
public Query<T> setFirstRow(int firstRow) {
return rootQuery.setFirstRow(firstRow);
}
@Override
public Query<T> setMapKey(String mapKey) {
return rootQuery.setMapKey(mapKey);
}
@Override
public Query<T> setMaxRows(int maxRows) {
return rootQuery.setMaxRows(maxRows);
}
@Override
public Query<T> setUseCache(boolean useCache) {
return rootQuery.setUseCache(useCache);
}
@Override
public ExpressionList<T> where() {
return rootQuery.where();
}
}
@@ -0,0 +1,91 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
/**
* Slightly redundant as Query.setId() ultimately also does the same job.
*/
class IdExpression extends NonPrepareExpression implements SpiExpression {
private final Object value;
IdExpression(Object value) {
this.value = value;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeId(value);
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
}
/**
* Always returns false.
*/
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// 'flatten' EmbeddedId and multiple Id cases
// into an array of the underlying scalar field values
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
Object[] bindIdValues = r.getBeanDescriptor().getBindIdValues(value);
for (Object bindIdValue : bindIdValues) {
request.addBindValue(bindIdValue);
}
}
@Override
public void addSql(SpiExpressionRequest request) {
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
String idSql = r.getBeanDescriptor().getIdBinderIdSql();
request.append(idSql).append(" ");
}
/**
* No properties so this is just a unique static number.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(IdExpression.class);
builder.bind(1);
}
@Override
public int queryBindHash() {
return value.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
return other instanceof IdExpression;
}
@Override
public boolean isSameByBind(SpiExpression other) {
IdExpression that = (IdExpression) other;
return value.equals(that.value);
}
}
@@ -0,0 +1,121 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
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.id.IdBinder;
import java.io.IOException;
import java.util.List;
/**
* Slightly redundant as Query.setId() ultimately also does the same job.
*/
public class IdInExpression extends NonPrepareExpression {
private final List<?> idList;
public IdInExpression(List<?> idList) {
this.idList = idList;
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeIds(idList);
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// Bind the Id values including EmbeddedId and multiple Id
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
BeanDescriptor<?> descriptor = r.getBeanDescriptor();
IdBinder idBinder = descriptor.getIdBinder();
for (Object anIdList : idList) {
idBinder.addIdInBindValue(request, anIdList);
}
}
/**
* For use with deleting non attached detail beans during stateless update.
*/
public void addSqlNoAlias(SpiExpressionRequest request) {
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
BeanDescriptor<?> descriptor = r.getBeanDescriptor();
IdBinder idBinder = descriptor.getIdBinder();
request.append(descriptor.getIdBinder().getBindIdInSql(null));
String inClause = idBinder.getIdInValueExpr(idList.size());
request.append(inClause);
}
@Override
public void addSql(SpiExpressionRequest request) {
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
BeanDescriptor<?> descriptor = r.getBeanDescriptor();
IdBinder idBinder = descriptor.getIdBinder();
request.append(descriptor.getIdBinderInLHSSql());
String inClause = idBinder.getIdInValueExpr(idList.size());
request.append(inClause);
}
/**
* Incorporates the number of Id values to bind.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(IdInExpression.class).add(idList.size());
builder.bind(idList.size());
}
@Override
public int queryBindHash() {
return idList.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof IdInExpression)) {
return false;
}
IdInExpression that = (IdInExpression) other;
return this.idList.size() == that.idList.size();
}
@Override
public boolean isSameByBind(SpiExpression other) {
IdInExpression that = (IdInExpression) other;
if (this.idList.size() != that.idList.size()) {
return false;
}
for (int i = 0; i < idList.size(); i++) {
if (!idList.get(i).equals(that.idList.get(i))) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,154 @@
package io.ebeaninternal.server.expression;
import io.ebean.bean.EntityBean;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.el.ElPropertyValue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
class InExpression extends AbstractExpression {
private final boolean not;
private final Collection<?> sourceValues;
private Object[] bindValues;
InExpression(String propertyName, Collection<?> sourceValues, boolean not) {
super(propertyName);
this.sourceValues = sourceValues;
this.not = not;
}
InExpression(String propertyName, Object[] array, boolean not) {
super(propertyName);
this.sourceValues = Arrays.asList(array);
this.not = not;
}
private Object[] values() {
List<Object> vals = new ArrayList<>();
for (Object sourceValue : sourceValues) {
NamedParamHelp.valueAdd(vals, sourceValue);
}
return vals.toArray();
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
bindValues = values();
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeIn(propName, values(), not);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
if (prop != null && !prop.isAssocId()) {
prop = null;
}
for (Object bindValue : bindValues) {
if (prop == null) {
request.addBindValue(bindValue);
} else {
// extract the id values from the bean
Object[] ids = prop.getAssocIdValues((EntityBean) bindValue);
if (ids != null) {
for (Object id : ids) {
request.addBindValue(id);
}
}
}
}
}
@Override
public void addSql(SpiExpressionRequest request) {
if (bindValues.length == 0) {
String expr = not ? "1=1" : "1=0";
request.append(expr);
return;
}
ElPropertyValue prop = getElProp(request);
if (prop != null && !prop.isAssocId()) {
prop = null;
}
if (prop != null) {
request.append(prop.getAssocIdInExpr(propName));
String inClause = prop.getAssocIdInValueExpr(bindValues.length);
request.append(inClause);
} else {
request.append(propName);
if (not) {
request.append(" not");
}
request.append(" in (?");
for (int i = 1; i < bindValues.length; i++) {
request.append(", ").append("?");
}
request.append(" ) ");
}
}
/**
* Based on the number of values in the in clause.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(InExpression.class).add(propName).add(bindValues.length).add(not);
builder.bind(bindValues.length);
}
@Override
public int queryBindHash() {
int hc = 92821;
for (Object bindValue : bindValues) {
hc = 92821 * hc + bindValue.hashCode();
}
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof InExpression)) {
return false;
}
InExpression that = (InExpression) other;
return propName.equals(that.propName)
&& not == that.not
&& bindValues.length == that.bindValues.length;
}
@Override
public boolean isSameByBind(SpiExpression other) {
InExpression that = (InExpression) other;
if (this.bindValues.length != that.bindValues.length) {
return false;
}
for (int i = 0; i < bindValues.length; i++) {
if (!bindValues[i].equals(that.bindValues[i])) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,125 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.query.CQuery;
import java.io.IOException;
import java.util.List;
/**
* In expression using a sub query.
*/
class InQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression {
private final boolean not;
private final SpiQuery<?> subQuery;
private List<Object> bindParams;
private String sql;
InQueryExpression(String propertyName, SpiQuery<?> subQuery, boolean not) {
super(propertyName);
this.subQuery = subQuery;
this.not = not;
}
InQueryExpression(String propertyName, boolean not, String sql, List<Object> bindParams) {
super(propertyName);
this.subQuery = null;
this.not = not;
this.sql = sql;
this.bindParams = bindParams;
}
@Override
public void simplify() {
// do nothing
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
throw new IllegalStateException("Not supported");
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
CQuery<?> subQuery = compileSubQuery(request);
this.bindParams = subQuery.getPredicates().getWhereExprBindValues();
this.sql = subQuery.getGeneratedSql().replace('\n', ' ');
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(InQueryExpression.class).add(propName).add(not);
builder.add(sql).add(bindParams.size());
}
/**
* Compile/build the sub query.
*/
private CQuery<?> compileSubQuery(BeanQueryRequest<?> queryRequest) {
SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer();
return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction());
}
@Override
public int queryBindHash() {
return subQuery.queryBindHash();
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(" (").append(propName).append(")");
if (not) {
request.append(" not");
}
request.append(" in (");
request.append(sql);
request.append(") ");
}
@Override
public void addBindValues(SpiExpressionRequest request) {
for (Object bindParam : bindParams) {
request.addBindValue(bindParam);
}
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof InQueryExpression)) {
return false;
}
InQueryExpression that = (InQueryExpression) other;
return propName.equals(that.propName)
&& sql.equals(that.sql)
&& not == that.not
&& bindParams.size() == that.bindParams.size();
}
@Override
public boolean isSameByBind(SpiExpression other) {
InQueryExpression that = (InQueryExpression) other;
if (this.bindParams.size() != that.bindParams.size()) {
return false;
}
for (int i = 0; i < bindParams.size(); i++) {
if (!bindParams.get(i).equals(that.bindParams.get(i))) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,120 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.SplitName;
import java.io.IOException;
class IsEmptyExpression extends AbstractExpression {
private final boolean empty;
private final String propertyPath;
private String nestedPath;
IsEmptyExpression(String propertyName, boolean empty) {
super(propertyName);
this.empty = empty;
this.propertyPath = SplitName.split(propertyName)[0];
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
if (empty) {
// capture the nestedPath as we want to put wrap
// a NOT around the outer of the nested path exists
this.nestedPath = propertyNestedPath(propName, desc);
return null;
} else {
return super.nestedPath(desc);
}
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
if (nestedPath == null) {
context.writeExists(!empty, propName);
} else {
// wrap bool must not around the outside of nested path exists expression
context.startBoolMustNot();
context.startNested(nestedPath);
context.writeExists(empty, propName);
context.endNested();
context.endBool();
}
}
public final String getPropName() {
return propName;
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// no bind values
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
// we don't want the extra join
propertyContainsMany(propertyPath, desc, manyWhereJoin);
}
@Override
public void addSql(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
if (prop == null) {
throw new IllegalStateException("Property [" + propName + "] not found");
}
isEmptySql(request, prop, empty, propertyPath);
}
/**
* Append an exists subQuery for the property.
*/
static void isEmptySql(SpiExpressionRequest request, ElPropertyValue prop, boolean empty, String propertyPath) {
if (empty) {
request.append("not ");
}
request
.append("exists (select 1 from ")
.append(prop.getAssocIsEmpty(request, propertyPath))
.append(")");
}
/**
* Based on the type and propertyName.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(IsEmptyExpression.class).add(propName);
}
@Override
public int queryBindHash() {
return 1;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof IsEmptyExpression)) {
return false;
}
IsEmptyExpression that = (IsEmptyExpression) other;
return this.propName.equals(that.propName) && this.empty == that.empty;
}
@Override
public boolean isSameByBind(SpiExpression other) {
return (other instanceof IsEmptyExpression);
}
}
@@ -0,0 +1,126 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import java.io.IOException;
/**
* Generally speaking tests the value at a given path in the JSON document.
* <p>
* Supports the usual operators (equal to, greater than etc).
* </p>
* <p>
* The value passed in is expected to be a valid JSON type so string, number, boolean.
* </p>
*/
class JsonPathExpression extends AbstractExpression {
/**
* The path in the JSON document in dot notation form.
*/
protected final String path;
/**
* The expression operator.
*/
protected final Op operator;
/**
* The bind value used to compare against the document path value.
*/
protected final Object value;
/**
* For Between this is the upper bind value.
*/
protected final Object upperValue;
/**
* Construct for Operator (not BETWEEN though).
*/
JsonPathExpression(String propertyName, String path, Op operator, Object value) {
super(propertyName);
this.path = path;
this.operator = operator;
this.value = value;
this.upperValue = null;
}
/**
* Construct for BETWEEN expression.
*/
JsonPathExpression(String propertyName, String path, Object value, Object upperValue) {
super(propertyName);
this.path = path;
this.operator = Op.BETWEEN;
this.value = value;
this.upperValue = upperValue;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
String fullName = propName + "." + path;
if (operator == Op.BETWEEN) {
context.writeRange(fullName, Op.GT_EQ, value, Op.LT_EQ, upperValue);
} else {
context.writeSimple(operator, fullName, value);
}
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(JsonPathExpression.class).add(propName).add(path).add(operator);
builder.bindIfNotNull(value);
builder.bindIfNotNull(upperValue);
}
@Override
public int queryBindHash() {
int hc = (value == null) ? 0 : value.hashCode();
hc = (upperValue == null) ? hc : hc * 92821 + upperValue.hashCode();
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof JsonPathExpression)) {
return false;
}
JsonPathExpression that = (JsonPathExpression) other;
return propName.equals(that.propName)
&& operator == that.operator
&& Same.sameByValue(path, that.path)
&& Same.sameByNull(value, that.value)
&& Same.sameByNull(upperValue, that.upperValue);
}
@Override
public boolean isSameByBind(SpiExpression other) {
JsonPathExpression that = (JsonPathExpression) other;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return upperValue != null ? upperValue.equals(that.upperValue) : that.upperValue == null;
}
@Override
public void addSql(SpiExpressionRequest request) {
// Use DB specific expression handling (Postgres and Oracle supported)
request.getDbPlatformHandler().json(request, propName, path, operator, value);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
if (value != null) {
// value is null for EXISTS/NOT EXISTS
request.addBindValue(value);
}
if (upperValue != null) {
// upperValue only for BETWEEN operator
request.addBindValue(upperValue);
}
}
}
@@ -0,0 +1,846 @@
package io.ebeaninternal.server.expression;
import io.ebean.Expression;
import io.ebean.ExpressionList;
import io.ebean.FetchPath;
import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.Junction;
import io.ebean.OrderBy;
import io.ebean.PagedList;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebean.search.Match;
import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
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 java.io.IOException;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Junction implementation.
*/
class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, ExpressionList<T> {
protected DefaultExpressionList<T> exprList;
protected Junction.Type type;
JunctionExpression(Junction.Type type, Query<T> query, ExpressionList<T> parent) {
this.type = type;
this.exprList = new DefaultExpressionList<>(query, parent);
}
/**
* Construct for copyForPlanKey.
*/
JunctionExpression(Junction.Type type, DefaultExpressionList<T> exprList) {
this.type = type;
this.exprList = exprList;
}
/**
* Simplify nested expressions where possible.
* <p>
* This is expected to only used after expressions are built via query language parsing.
* </p>
*/
@SuppressWarnings("unchecked")
@Override
public void simplify() {
exprList.simplifyEntries();
List<SpiExpression> list = exprList.list;
if (list.size() == 1 && list.get(0) instanceof JunctionExpression) {
JunctionExpression nested = (JunctionExpression) list.get(0);
if (type == Type.AND && !nested.type.isText()) {
// and (and (a, b, c)) -> and (a, b, c)
// and (not (a, b, c)) -> not (a, b, c)
// and (or (a, b, c)) -> or (a, b, c)
this.exprList = nested.exprList;
this.type = nested.type;
} else if (type == Type.NOT && nested.type == Type.AND) {
// not (and (a, b, c)) -> not (a, b, c)
this.exprList = nested.exprList;
}
}
}
@Override
public SpiExpression copyForPlanKey() {
return new JunctionExpression<>(type, exprList.copyForPlanKey());
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.startBool(type);
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
aList.writeDocQuery(context);
}
context.endBool();
}
@Override
public void writeDocQueryJunction(DocQueryContext context) throws IOException {
context.startBoolGroupList(type);
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
aList.writeDocQuery(context);
}
context.endBoolGroupList();
}
@Override
public Object getIdEqualTo(String idName) {
// always null for this expression
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
List<SpiExpression> list = exprList.internalList();
// get the current state for 'require outer joins'
boolean parentOuterJoins = manyWhereJoin.isRequireOuterJoins();
if (type == Type.OR) {
// turn on outer joins required for disjunction expressions
manyWhereJoin.setRequireOuterJoins(true);
}
for (SpiExpression aList : list) {
aList.containsMany(desc, manyWhereJoin);
}
if (type == Type.OR && !parentOuterJoins) {
// restore state to not forcing outer joins
manyWhereJoin.setRequireOuterJoins(false);
}
}
@Override
public void validate(SpiExpressionValidation validation) {
exprList.validate(validation);
}
@Override
public Junction<T> add(Expression item) {
exprList.add(item);
return this;
}
@Override
public Junction<T> addAll(ExpressionList<T> addList) {
exprList.addAll(addList);
return this;
}
@Override
public void addBindValues(SpiExpressionRequest request) {
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
aList.addBindValues(request);
}
}
@Override
public void addSql(SpiExpressionRequest request) {
List<SpiExpression> list = exprList.internalList();
if (!list.isEmpty()) {
request.append(type.prefix());
request.append("(");
for (int i = 0; i < list.size(); i++) {
SpiExpression item = list.get(i);
if (i > 0) {
request.append(type.literal());
}
item.addSql(request);
}
request.append(") ");
}
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
aList.prepareExpression(request);
}
}
/**
* Based on Junction type and all the expression contained.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(JunctionExpression.class).add(type);
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
aList.queryPlanHash(builder);
}
}
@Override
public int queryBindHash() {
int hc = JunctionExpression.class.getName().hashCode();
List<SpiExpression> list = exprList.internalList();
for (SpiExpression aList : list) {
hc = hc * 92821 + aList.queryBindHash();
}
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof JunctionExpression)) {
return false;
}
JunctionExpression that = (JunctionExpression) other;
return type == that.type && exprList.isSameByPlan(that.exprList);
}
@Override
public boolean isSameByBind(SpiExpression other) {
JunctionExpression that = (JunctionExpression) other;
return type == that.type && exprList.isSameByBind(that.exprList);
}
@Override
public ExpressionList<T> match(String propertyName, String search) {
return match(propertyName, search, null);
}
@Override
public ExpressionList<T> match(String propertyName, String search, Match options) {
return exprList.match(propertyName, search, options);
}
@Override
public ExpressionList<T> multiMatch(String query, String... properties) {
return exprList.multiMatch(query, properties);
}
@Override
public ExpressionList<T> multiMatch(String query, MultiMatch options) {
return exprList.multiMatch(query, options);
}
@Override
public ExpressionList<T> textSimple(String search, TextSimple options) {
return exprList.textSimple(search, options);
}
@Override
public ExpressionList<T> textQueryString(String search, TextQueryString options) {
return exprList.textQueryString(search, options);
}
@Override
public ExpressionList<T> textCommonTerms(String search, TextCommonTerms options) {
return exprList.textCommonTerms(search, options);
}
@Override
public ExpressionList<T> allEq(Map<String, Object> propertyMap) {
return exprList.allEq(propertyMap);
}
@Override
public ExpressionList<T> and(Expression expOne, Expression expTwo) {
return exprList.and(expOne, expTwo);
}
@Override
public ExpressionList<T> between(String propertyName, Object value1, Object value2) {
return exprList.between(propertyName, value1, value2);
}
@Override
public ExpressionList<T> betweenProperties(String lowProperty, String highProperty, Object value) {
return exprList.betweenProperties(lowProperty, highProperty, value);
}
@Override
public ExpressionList<T> contains(String propertyName, String value) {
return exprList.contains(propertyName, value);
}
@Override
public ExpressionList<T> endsWith(String propertyName, String value) {
return exprList.endsWith(propertyName, value);
}
@Override
public ExpressionList<T> eq(String propertyName, Object value) {
return exprList.eq(propertyName, value);
}
@Override
public ExpressionList<T> exampleLike(Object example) {
return exprList.exampleLike(example);
}
@Override
public ExpressionList<T> filterMany(String prop) {
throw new IllegalStateException("filterMany not allowed on Junction expression list");
}
@Override
public int delete() {
return exprList.delete();
}
@Override
public int update() {
return exprList.update();
}
@Override
public Query<T> asOf(Timestamp asOf) {
return exprList.asOf(asOf);
}
@Override
public Query<T> asDraft() {
return exprList.asDraft();
}
@Override
public Query<T> setIncludeSoftDeletes() {
return exprList.setIncludeSoftDeletes();
}
@Override
public List<Version<T>> findVersions() {
return exprList.findVersions();
}
@Override
public List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end) {
return exprList.findVersionsBetween(start, end);
}
@Override
public Query<T> apply(FetchPath fetchPath) {
return exprList.apply(fetchPath);
}
@Override
public FutureIds<T> findFutureIds() {
return exprList.findFutureIds();
}
@Override
public FutureList<T> findFutureList() {
return exprList.findFutureList();
}
@Override
public FutureRowCount<T> findFutureCount() {
return exprList.findFutureCount();
}
@Override
public <A> List<A> findIds() {
return exprList.findIds();
}
@Override
public QueryIterator<T> findIterate() {
return exprList.findIterate();
}
@Override
public void findEach(Consumer<T> consumer) {
exprList.findEach(consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
exprList.findEachWhile(consumer);
}
@Override
public List<T> findList() {
return exprList.findList();
}
@Override
public <K> Map<K, T> findMap() {
return exprList.findMap();
}
@Override
public <A> List<A> findSingleAttributeList() {
return exprList.findSingleAttributeList();
}
@Override
public PagedList<T> findPagedList() {
return exprList.findPagedList();
}
@Override
public int findCount() {
return exprList.findCount();
}
@Override
public Set<T> findSet() {
return exprList.findSet();
}
@Override
public T findUnique() {
return exprList.findUnique();
}
/**
* Path exists - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonExists(String propertyName, String path) {
return exprList.jsonExists(propertyName, path);
}
/**
* Path does not exist - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonNotExists(String propertyName, String path) {
return exprList.jsonNotExists(propertyName, path);
}
/**
* Equal to - for the value at the given path in the JSON document.
*/
@Override
public ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value) {
return exprList.jsonEqualTo(propertyName, path, value);
}
/**
* Not Equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object val) {
return exprList.jsonNotEqualTo(propertyName, path, val);
}
/**
* Greater than - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val) {
return exprList.jsonGreaterThan(propertyName, path, val);
}
/**
* Greater than or equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val) {
return exprList.jsonGreaterOrEqual(propertyName, path, val);
}
/**
* Less than - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonLessThan(String propertyName, String path, Object val) {
return exprList.jsonLessThan(propertyName, path, val);
}
/**
* Less than or equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val) {
return exprList.jsonLessOrEqualTo(propertyName, path, val);
}
/**
* Between - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
return exprList.jsonBetween(propertyName, path, lowerValue, upperValue);
}
@Override
public ExpressionList<T> arrayContains(String propertyName, Object... values) {
return exprList.arrayContains(propertyName, values);
}
@Override
public ExpressionList<T> arrayNotContains(String propertyName, Object... values) {
return exprList.arrayNotContains(propertyName, values);
}
@Override
public ExpressionList<T> arrayIsEmpty(String propertyName) {
return exprList.arrayIsEmpty(propertyName);
}
@Override
public ExpressionList<T> arrayIsNotEmpty(String propertyName) {
return exprList.arrayIsNotEmpty(propertyName);
}
@Override
public ExpressionList<T> ge(String propertyName, Object value) {
return exprList.ge(propertyName, value);
}
@Override
public ExpressionList<T> gt(String propertyName, Object value) {
return exprList.gt(propertyName, value);
}
@Override
public ExpressionList<T> having() {
throw new IllegalStateException("having() not allowed on Junction expression list");
}
@Override
public ExpressionList<T> icontains(String propertyName, String value) {
return exprList.icontains(propertyName, value);
}
@Override
public ExpressionList<T> idEq(Object value) {
return exprList.idEq(value);
}
@Override
public ExpressionList<T> idIn(Object... idValues) {
return exprList.idIn(idValues);
}
@Override
public ExpressionList<T> idIn(List<?> idValues) {
return exprList.idIn(idValues);
}
@Override
public ExpressionList<T> iendsWith(String propertyName, String value) {
return exprList.iendsWith(propertyName, value);
}
@Override
public ExpressionList<T> ieq(String propertyName, String value) {
return exprList.ieq(propertyName, value);
}
@Override
public ExpressionList<T> iexampleLike(Object example) {
return exprList.iexampleLike(example);
}
@Override
public ExpressionList<T> ilike(String propertyName, String value) {
return exprList.ilike(propertyName, value);
}
@Override
public ExpressionList<T> in(String propertyName, Collection<?> values) {
return exprList.in(propertyName, values);
}
@Override
public ExpressionList<T> in(String propertyName, Object... values) {
return exprList.in(propertyName, values);
}
@Override
public ExpressionList<T> in(String propertyName, Query<?> subQuery) {
return exprList.in(propertyName, subQuery);
}
@Override
public ExpressionList<T> notIn(String propertyName, Collection<?> values) {
return exprList.notIn(propertyName, values);
}
@Override
public ExpressionList<T> notIn(String propertyName, Object... values) {
return exprList.notIn(propertyName, values);
}
@Override
public ExpressionList<T> notIn(String propertyName, Query<?> subQuery) {
return exprList.notIn(propertyName, subQuery);
}
@Override
public ExpressionList<T> isEmpty(String propertyName) {
return exprList.isEmpty(propertyName);
}
@Override
public ExpressionList<T> isNotEmpty(String propertyName) {
return exprList.isNotEmpty(propertyName);
}
@Override
public ExpressionList<T> exists(Query<?> subQuery) {
return exprList.exists(subQuery);
}
@Override
public ExpressionList<T> notExists(Query<?> subQuery) {
return exprList.exists(subQuery);
}
@Override
public ExpressionList<T> isNotNull(String propertyName) {
return exprList.isNotNull(propertyName);
}
@Override
public ExpressionList<T> isNull(String propertyName) {
return exprList.isNull(propertyName);
}
@Override
public ExpressionList<T> istartsWith(String propertyName, String value) {
return exprList.istartsWith(propertyName, value);
}
@Override
public ExpressionList<T> le(String propertyName, Object value) {
return exprList.le(propertyName, value);
}
@Override
public ExpressionList<T> like(String propertyName, String value) {
return exprList.like(propertyName, value);
}
@Override
public ExpressionList<T> lt(String propertyName, Object value) {
return exprList.lt(propertyName, value);
}
@Override
public ExpressionList<T> ne(String propertyName, Object value) {
return exprList.ne(propertyName, value);
}
@Override
public ExpressionList<T> not(Expression exp) {
return exprList.not(exp);
}
@Override
public ExpressionList<T> or(Expression expOne, Expression expTwo) {
return exprList.or(expOne, expTwo);
}
@Override
public OrderBy<T> order() {
return exprList.order();
}
@Override
public Query<T> order(String orderByClause) {
return exprList.order(orderByClause);
}
@Override
public OrderBy<T> orderBy() {
return exprList.orderBy();
}
@Override
public Query<T> orderBy(String orderBy) {
return exprList.orderBy(orderBy);
}
@Override
public Query<T> query() {
return exprList.query();
}
@Override
public ExpressionList<T> raw(String raw, Object value) {
return exprList.raw(raw, value);
}
@Override
public ExpressionList<T> raw(String raw, Object... values) {
return exprList.raw(raw, values);
}
@Override
public ExpressionList<T> raw(String raw) {
return exprList.raw(raw);
}
@Override
public Query<T> select(String properties) {
return exprList.select(properties);
}
@Override
public Query<T> setDistinct(boolean distinct) {
return exprList.setDistinct(distinct);
}
@Override
public Query<T> setDocIndexName(String indexName) {
return exprList.setDocIndexName(indexName);
}
@Override
public Query<T> setFirstRow(int firstRow) {
return exprList.setFirstRow(firstRow);
}
@Override
public Query<T> setMapKey(String mapKey) {
return exprList.setMapKey(mapKey);
}
@Override
public Query<T> setMaxRows(int maxRows) {
return exprList.setMaxRows(maxRows);
}
@Override
public Query<T> setOrderBy(String orderBy) {
return exprList.setOrderBy(orderBy);
}
@Override
public Query<T> setUseCache(boolean useCache) {
return exprList.setUseCache(useCache);
}
@Override
public Query<T> setUseQueryCache(boolean useCache) {
return exprList.setUseQueryCache(useCache);
}
@Override
public Query<T> setUseDocStore(boolean useDocsStore) {
return exprList.setUseDocStore(useDocsStore);
}
@Override
public Query<T> setDisableLazyLoading(boolean disableLazyLoading) {
return exprList.setDisableLazyLoading(disableLazyLoading);
}
@Override
public Query<T> setDisableReadAuditing() {
return exprList.setDisableReadAuditing();
}
@Override
public ExpressionList<T> startsWith(String propertyName, String value) {
return exprList.startsWith(propertyName, value);
}
@Override
public ExpressionList<T> where() {
return exprList.where();
}
@Override
public Junction<T> and() {
return conjunction();
}
@Override
public Junction<T> or() {
return disjunction();
}
@Override
public Junction<T> not() {
return exprList.not();
}
@Override
public Junction<T> conjunction() {
return exprList.conjunction();
}
@Override
public Junction<T> disjunction() {
return exprList.disjunction();
}
@Override
public Junction<T> must() {
return exprList.must();
}
@Override
public Junction<T> should() {
return exprList.should();
}
@Override
public Junction<T> mustNot() {
return exprList.mustNot();
}
@Override
public ExpressionList<T> endJunction() {
return exprList.endJunction();
}
@Override
public ExpressionList<T> endAnd() {
return endJunction();
}
@Override
public ExpressionList<T> endOr() {
return endJunction();
}
@Override
public ExpressionList<T> endNot() {
return endJunction();
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
PrepareDocNested.prepare(exprList, desc, type);
String nestedPath = exprList.allDocNestedPath;
if (nestedPath != null) {
// push the nestedPath up to parent
exprList.setAllDocNested(null);
return nestedPath;
}
return null;
}
}
@@ -0,0 +1,116 @@
package io.ebeaninternal.server.expression;
import io.ebean.LikeType;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.el.ElPropertyValue;
import java.io.IOException;
class LikeExpression extends AbstractValueExpression {
private final boolean caseInsensitive;
private final LikeType type;
LikeExpression(String propertyName, Object value, boolean caseInsensitive, LikeType type) {
super(propertyName, value);
this.caseInsensitive = caseInsensitive;
this.type = type;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeLike(propName, strValue(), type, caseInsensitive);
}
@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);
}
String bindValue = getValue(strValue(), caseInsensitive, type);
request.addBindValue(bindValue);
}
@Override
public void addSql(SpiExpressionRequest request) {
String pname = propName;
ElPropertyValue prop = getElProp(request);
if (prop != null && prop.isDbEncrypted()) {
pname = prop.getBeanProperty().getDecryptProperty(propName);
}
if (caseInsensitive) {
request.append("lower(").append(pname).append(")");
} else {
request.append(pname);
}
if (type.equals(LikeType.EQUAL_TO)) {
request.append(" = ? ");
} else {
// append db platform like clause
request.appendLike();
}
}
/**
* Based on caseInsensitive and the property name.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(LikeExpression.class).add(caseInsensitive).add(propName);
builder.bind(1);
}
@Override
public int queryBindHash() {
return strValue().hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof LikeExpression)) {
return false;
}
LikeExpression that = (LikeExpression) other;
return this.propName.equals(that.propName)
&& this.caseInsensitive == that.caseInsensitive
&& this.type == that.type;
}
@Override
public boolean isSameByBind(SpiExpression other) {
LikeExpression that = (LikeExpression) other;
return strValue().equals(that.strValue());
}
private static String getValue(String value, boolean caseInsensitive, LikeType type) {
if (caseInsensitive) {
value = value.toLowerCase();
}
switch (type) {
case RAW:
return value;
case STARTS_WITH:
return value + "%";
case ENDS_WITH:
return "%" + value;
case CONTAINS:
return "%" + value + "%";
case EQUAL_TO:
return value;
default:
throw new RuntimeException("LikeType " + type + " missed?");
}
}
}
@@ -0,0 +1,173 @@
package io.ebeaninternal.server.expression;
import io.ebean.Expression;
import io.ebean.Junction;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
/**
* A logical And or Or for joining two expressions.
*/
abstract class LogicExpression implements SpiExpression {
static final String AND = " and ";
static final String OR = " or ";
static class And extends LogicExpression {
And(Expression expOne, Expression expTwo) {
super(AND, expOne, expTwo);
}
@Override
public SpiExpression copyForPlanKey() {
return new And(expOne.copyForPlanKey(), expTwo.copyForPlanKey());
}
}
static class Or extends LogicExpression {
Or(Expression expOne, Expression expTwo) {
super(OR, expOne, expTwo);
}
@Override
public SpiExpression copyForPlanKey() {
return new Or(expOne.copyForPlanKey(), expTwo.copyForPlanKey());
}
}
protected SpiExpression expOne;
protected SpiExpression expTwo;
private final String joinType;
LogicExpression(String joinType, Expression expOne, Expression expTwo) {
this.joinType = joinType;
this.expOne = (SpiExpression) expOne;
this.expTwo = (SpiExpression) expTwo;
}
@Override
public void simplify() {
// do nothing
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
boolean conjunction = joinType.equals(AND);
context.startBool(conjunction ? Junction.Type.AND : Junction.Type.OR);
expOne.writeDocQuery(context);
expTwo.writeDocQuery(context);
context.endBool();
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
String pathOne = expOne.nestedPath(desc);
String pathTwo = expTwo.nestedPath(desc);
if (pathOne == null && pathTwo == null) {
return null;
}
if (pathOne != null && pathOne.equals(pathTwo)) {
return pathOne;
}
if (pathOne != null) {
expOne = new NestedPathWrapperExpression(pathOne, expOne);
}
if (pathTwo != null) {
expTwo = new NestedPathWrapperExpression(pathTwo, expTwo);
}
return null;
}
@Override
public Object getIdEqualTo(String idName) {
// always return null for this expression
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
expOne.containsMany(desc, manyWhereJoin);
expTwo.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
expOne.validate(validation);
expTwo.validate(validation);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
expOne.addBindValues(request);
expTwo.addBindValues(request);
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append("(");
expOne.addSql(request);
request.append(joinType);
expTwo.addSql(request);
request.append(") ");
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
expOne.prepareExpression(request);
expTwo.prepareExpression(request);
}
/**
* Based on the joinType plus the two expressions.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(LogicExpression.class).add(joinType);
expOne.queryPlanHash(builder);
expTwo.queryPlanHash(builder);
}
@Override
public int queryBindHash() {
int hc = expOne.queryBindHash();
hc = hc * 92821 + expTwo.queryBindHash();
return hc;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof LogicExpression)) {
return false;
}
LogicExpression that = (LogicExpression) other;
return this.joinType.equals(that.joinType)
&& this.expOne.isSameByPlan(that.expOne)
&& this.expTwo.isSameByPlan(that.expTwo);
}
@Override
public boolean isSameByBind(SpiExpression other) {
LogicExpression that = (LogicExpression) other;
return this.expOne.isSameByBind(that.expOne)
&& this.expTwo.isSameByBind(that.expTwo);
}
}
@@ -0,0 +1,43 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.SpiNamedParam;
import java.util.Collection;
import java.util.List;
/**
* Helper for evaluating named parameters.
*/
class NamedParamHelp {
/**
* Return the bind value taking into account named parameters.
*/
static Object value(Object val) {
if (val instanceof SpiNamedParam) {
return ((SpiNamedParam) val).getValue();
}
return val;
}
/**
* Return the value as a string.
*/
static String valueAsString(Object val) {
Object value = value(val);
return (value == null) ? null : value.toString();
}
/**
* Add the potentially named parameter(s) to the values.
*/
public static void valueAdd(List<Object> values, Object sourceValue) {
Object value = value(sourceValue);
if (value instanceof Collection) {
values.addAll((Collection<?>) value);
} else {
values.add(value);
}
}
}
@@ -0,0 +1,79 @@
package io.ebeaninternal.server.expression;
import io.ebean.LikeType;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.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);
}
}
@@ -0,0 +1,104 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
/**
* Wraps a single expression with nestedPath for document queries.
*/
class NestedPathWrapperExpression implements SpiExpression {
protected final String nestedPath;
protected final SpiExpression delegate;
NestedPathWrapperExpression(String nestedPath, SpiExpression delegate) {
this.nestedPath = nestedPath;
this.delegate = delegate;
}
@Override
public void simplify() {
// do nothing
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.startNested(nestedPath);
delegate.writeDocQuery(context);
context.endNested();
}
@Override
public Object getIdEqualTo(String idName) {
// always return null for this expression
return null;
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return nestedPath;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
delegate.containsMany(desc, whereManyJoins);
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
delegate.prepareExpression(request);
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
delegate.queryPlanHash(builder);
}
@Override
public int queryBindHash() {
return delegate.queryBindHash();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (other instanceof NestedPathWrapperExpression) {
NestedPathWrapperExpression that = (NestedPathWrapperExpression) other;
return nestedPath.equals(that.nestedPath)
&& delegate.isSameByPlan(that.delegate);
}
return false;
}
@Override
public boolean isSameByBind(SpiExpression other) {
return delegate.isSameByBind(other);
}
@Override
public void addSql(SpiExpressionRequest request) {
delegate.addSql(request);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
delegate.addBindValues(request);
}
@Override
public void validate(SpiExpressionValidation validation) {
delegate.validate(validation);
}
@Override
public SpiExpression copyForPlanKey() {
return new NestedPathWrapperExpression(nestedPath, delegate.copyForPlanKey());
}
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.SpiExpression;
/**
* Base abstract expression that does nothing for prepareExpression().
*/
abstract class NonPrepareExpression implements SpiExpression {
@Override
public void simplify() {
// do nothing
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
// do nothing
}
@Override
public Object getIdEqualTo(String idName) {
// always null in this expression
return null;
}
@Override
public SpiExpression copyForPlanKey() {
return this;
}
}
@@ -0,0 +1,90 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
/**
* Effectively an expression that has no effect.
*/
class NoopExpression implements SpiExpression {
protected static final NoopExpression INSTANCE = new NoopExpression();
@Override
public void simplify() {
// do nothing
}
@Override
public SpiExpression copyForPlanKey() {
return this;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
}
@Override
public Object getIdEqualTo(String idName) {
// always return null for this expression
return null;
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
// nothing to do
}
@Override
public void validate(SpiExpressionValidation validation) {
// always valid
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
// do nothing
}
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(NoopExpression.class);
}
@Override
public int queryBindHash() {
// no bind values
return 0;
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append("1=1");
}
@Override
public void addBindValues(SpiExpressionRequest request) {
// nothing to do
}
@Override
public boolean isSameByPlan(SpiExpression other) {
return other instanceof NoopExpression;
}
@Override
public boolean isSameByBind(SpiExpression other) {
return true;
}
}
@@ -0,0 +1,108 @@
package io.ebeaninternal.server.expression;
import io.ebean.Expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
final class NotExpression implements SpiExpression {
private static final String NOT_START = "not (";
private static final String NOT_END = ") ";
private final SpiExpression exp;
NotExpression(Expression exp) {
this.exp = (SpiExpression) exp;
}
@Override
public void simplify() {
// do nothing
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.startBoolMustNot();
exp.writeDocQuery(context);
context.endBool();
}
@Override
public Object getIdEqualTo(String idName) {
// always return null for this expression
return null;
}
@Override
public SpiExpression copyForPlanKey() {
return new NotExpression(exp.copyForPlanKey());
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return exp.nestedPath(desc);
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
exp.containsMany(desc, manyWhereJoin);
}
@Override
public void validate(SpiExpressionValidation validation) {
exp.validate(validation);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
exp.addBindValues(request);
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(NOT_START);
exp.addSql(request);
request.append(NOT_END);
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
exp.prepareExpression(request);
}
/**
* Based on the expression.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(NotExpression.class);
exp.queryPlanHash(builder);
}
@Override
public int queryBindHash() {
return exp.queryBindHash();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof NotExpression)) {
return false;
}
NotExpression that = (NotExpression) other;
return exp.isSameByPlan(that.exp);
}
@Override
public boolean isSameByBind(SpiExpression other) {
NotExpression that = (NotExpression) other;
return exp.isSameByBind(that.exp);
}
}
@@ -0,0 +1,104 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.SplitName;
import java.io.IOException;
/**
* Null / Not Null expression.
* <p>
* Note that for OneToMany/ManyToMany this effectively gets translated into isEmpty()/isNotEmpty().
* </p>
*/
class NullExpression extends AbstractExpression {
private final boolean notNull;
private ElPropertyValue elProperty;
private boolean assocMany;
private String propertyPath;
NullExpression(String propertyName, boolean notNull) {
super(propertyName);
this.notNull = notNull;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
elProperty = desc.getElGetValue(propName);
if (elProperty != null && elProperty.isAssocMany()) {
// it is OneToMany or ManyToMany so going to be treated as isEmpty() expression
assocMany = true;
propertyPath = SplitName.split(propName)[0];
propertyContainsMany(propertyPath, desc, manyWhereJoin);
} else {
propertyContainsMany(propName, desc, manyWhereJoin);
}
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeExists(notNull, propName);
}
@Override
public void addBindValues(SpiExpressionRequest request) {
}
@Override
public void addSql(SpiExpressionRequest request) {
if (assocMany) {
// translate to exists subquery
IsEmptyExpression.isEmptySql(request, elProperty, !notNull, propertyPath);
return;
}
String nullExpr = notNull ? " is not null " : " is null ";
if (elProperty != null && elProperty.isAssocId()) {
request.append(elProperty.getAssocIdExpression(propName, nullExpr));
} else {
request.append(propName).append(nullExpr);
}
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof NullExpression)) {
return false;
}
NullExpression that = (NullExpression) other;
return this.propName.equals(that.propName)
&& this.notNull == that.notNull;
}
@Override
public boolean isSameByBind(SpiExpression other) {
// no bind values so always true
return true;
}
/**
* Based on notNull flag and the propertyName.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(NullExpression.class).add(notNull).add(propName);
}
@Override
public int queryBindHash() {
return (notNull ? 1 : 0);
}
}
@@ -0,0 +1,76 @@
package io.ebeaninternal.server.expression;
/**
* Simple operators - equals, greater than, less than etc.
*/
public enum Op {
/**
* Exists (JSON).
*/
EXISTS(" is not null ", ""),
/**
* Not Exists (JSON).
*/
NOT_EXISTS(" is null ", ""),
/**
* Between (JSON).
*/
BETWEEN(" between ? and ? ", ""),
/**
* Equal to
*/
EQ(" = ? ", ""),
/**
* Not equal to.
*/
NOT_EQ(" <> ? ", ""),
/**
* Less than.
*/
LT(" < ? ", "lt"),
/**
* Less than or equal to.
*/
LT_EQ(" <= ? ", "lte"),
/**
* Greater than.
*/
GT(" > ? ", "gt"),
/**
* Greater than or equal to.
*/
GT_EQ(" >= ? ", "gte");
final String exp;
final String docExp;
Op(String exp, String docExp) {
this.exp = exp;
this.docExp = docExp;
}
/**
* Return the bind expression include JDBC ? bind placeholder.
*/
public String bind() {
return exp;
}
/**
* Return the doc store expression.
*/
public String docExp() {
return docExp;
}
}
@@ -0,0 +1,163 @@
package io.ebeaninternal.server.expression;
import io.ebean.Junction;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Prepare nested path expressions for
*/
class PrepareDocNested {
/**
* Prepare the top level expressions for nested path handling.
*/
static void prepare(DefaultExpressionList<?> expressions, BeanDescriptor<?> beanDescriptor) {
new PrepareDocNested(expressions, beanDescriptor, null).process();
}
/**
* Prepare the Junction expressions for nested path handling.
*/
static void prepare(DefaultExpressionList<?> expressions, BeanDescriptor<?> beanDescriptor, Junction.Type type) {
new PrepareDocNested(expressions, beanDescriptor, type).process();
}
enum Mode {
NONE,
SINGLE,
MIXED
}
private final Junction.Type type;
private final DefaultExpressionList<?> original;
private final BeanDescriptor<?> beanDescriptor;
private final List<SpiExpression> origUnderlying;
private final int origSize;
private boolean hasNesting;
private boolean hasMixedNesting;
private String firstNestedPath;
PrepareDocNested(DefaultExpressionList<?> original, BeanDescriptor<?> beanDescriptor, Junction.Type type) {
this.type = type;
this.beanDescriptor = beanDescriptor;
this.original = original;
this.origUnderlying = original.getUnderlyingList();
this.origSize = origUnderlying.size();
}
void process() {
PrepareDocNested.Mode mode = determineMode();
if (mode == PrepareDocNested.Mode.SINGLE) {
original.setAllDocNested(firstNestedPath);
} else if (mode == PrepareDocNested.Mode.MIXED) {
original.setUnderlying(group());
}
}
/**
* Reorganise the flat list of expressions into a tree grouping expressions by nested path.
* <p>
* Returns the new top level list of expressions.
*/
private List<SpiExpression> group() {
Map<String, Group> groups = new LinkedHashMap<>();
// organise expressions by nestedPath
for (int i = 0; i < origSize; i++) {
SpiExpression expr = origUnderlying.get(i);
String nestedPath = expr.nestedPath(beanDescriptor);
Group group = groups.get(nestedPath);
if (group == null) {
group = new Group(nestedPath);
groups.put(nestedPath, group);
}
group.list.add(expr);
}
List<SpiExpression> newList = new ArrayList<>();
Collection<Group> values = groups.values();
for (Group group : values) {
group.addTo(newList);
}
return newList;
}
/**
* Determined the nested path mode.
*/
private Mode determineMode() {
if (!hasNesting()) {
// no nested paths at all
return Mode.NONE;
}
if (!hasMixedNesting) {
// single nested path for all expressions
return Mode.SINGLE;
}
// mixed nested paths to underlying expression list needs re-organising by nested path
return Mode.MIXED;
}
/**
* Return true if the expressions have nested paths.
*/
private boolean hasNesting() {
for (int i = 0; i < origSize; i++) {
SpiExpression expr = origUnderlying.get(i);
String nestedPath = expr.nestedPath(beanDescriptor);
if (nestedPath == null) {
hasMixedNesting = true;
}
if (nestedPath != null) {
hasNesting = true;
if (firstNestedPath == null) {
firstNestedPath = nestedPath;
} else if (hasMixedNesting || !firstNestedPath.equals(nestedPath)) {
hasMixedNesting = true;
return true;
}
}
}
return hasNesting;
}
/**
* List of SpiExpression grouped by nested path.
*/
class Group {
final String nestedPath;
final List<SpiExpression> list = new ArrayList<>();
Group(String nestedPath) {
this.nestedPath = nestedPath;
}
void addTo(List<SpiExpression> newList) {
if (nestedPath == null) {
newList.addAll(list);
} else {
newList.add(original.wrap(list, nestedPath, type));
}
}
}
}
@@ -0,0 +1,96 @@
package io.ebeaninternal.server.expression;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
class RawExpression extends NonPrepareExpression {
private final String sql;
private final Object[] values;
RawExpression(String sql, Object[] values) {
this.sql = sql;
this.values = values;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeRaw(sql, values);
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
}
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
}
@Override
public void validate(SpiExpressionValidation validation) {
// always ignored
}
@Override
public void addBindValues(SpiExpressionRequest request) {
if (values != null) {
for (Object value : values) {
request.addBindValue(value);
}
}
}
@Override
public void addSql(SpiExpressionRequest request) {
request.append(sql);
}
/**
* Based on the sql.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(RawExpression.class).add(sql);
}
@Override
public int queryBindHash() {
return sql.hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof RawExpression)) {
return false;
}
RawExpression that = (RawExpression) other;
return sql.equals(that.sql);
}
@Override
public boolean isSameByBind(SpiExpression other) {
if (!(other instanceof RawExpression)) {
return false;
}
RawExpression that = (RawExpression) other;
if (values.length != that.values.length) {
return false;
}
for (int i = 0; i < values.length; i++) {
if (!Same.sameByValue(values[i], that.values[i])) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.expression;
import java.util.Collection;
import java.util.Iterator;
/**
* Utility to help isSame methods.
*/
public class Same {
/**
* Return true if both values are null or both an not null.
*/
public static boolean sameByNull(Object v1, Object v2) {
return v1 == null ? v2 == null : v2 != null;
}
/**
* Null safe equals check.
*/
public static boolean sameByValue(Object v1, Object v2) {
return v1 == null ? v2 == null : v1.equals(v2);
}
/**
* Return true if both collections are the same by value and order is taken into account.
*/
public static boolean sameByValue(Collection<?> v1, Collection<?> v2) {
if (v1 == null) {
return v2 == null;
}
if (v2 == null || v1.size() != v2.size()) {
return false;
}
Iterator<?> thisIt = v1.iterator();
Iterator<?> thatIt = v2.iterator();
while (thisIt.hasNext() && thatIt.hasNext()) {
if (!thisIt.next().equals(thatIt.next())) {
return false;
}
}
return true;
}
/**
* Null safe check by sameByValue or sameByNull based on byValue.
*/
public static boolean sameBy(boolean byValue, Object value, Object value1) {
if (byValue) {
return sameByValue(value, value1);
} else {
return sameByNull(value, value1);
}
}
}
@@ -0,0 +1,133 @@
package io.ebeaninternal.server.expression;
import io.ebean.bean.EntityBean;
import io.ebean.plugin.ExpressionPath;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.el.ElPropertyValue;
import java.io.IOException;
public class SimpleExpression extends AbstractValueExpression {
private final Op type;
public SimpleExpression(String propertyName, Op type, Object value) {
super(propertyName, value);
this.type = type;
}
@Override
public Object getIdEqualTo(String idName) {
if (type == Op.EQ && idName.equals(propName)) {
return value();
}
return null;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
if (type == Op.BETWEEN) {
throw new IllegalStateException("BETWEEN Not expected in SimpleExpression?");
}
ExpressionPath prop = context.getExpressionPath(propName);
if (prop != null && prop.isAssocId()) {
String idName = prop.getAssocIdExpression(propName, "");
Object[] ids = prop.getAssocIdValues((EntityBean) value());
if (ids == null || ids.length != 1) {
throw new IllegalArgumentException("Expecting 1 Id value for " + idName + " but got " + ids);
}
context.writeSimple(type, idName, ids[0]);
} else {
context.writeSimple(type, propName, value());
}
}
public final String getPropName() {
return propName;
}
public boolean isOpEquals() {
return Op.EQ.equals(type);
}
public Object getValue() {
return value();
}
@Override
public void addBindValues(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
if (prop != null) {
if (prop.isAssocId()) {
Object[] ids = prop.getAssocIdValues((EntityBean) value());
if (ids != null) {
for (Object id : ids) {
request.addBindValue(id);
}
}
return;
}
if (prop.isDbEncrypted()) {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindEncryptKey(encryptKey);
}
//else if (prop.isLocalEncrypted()) {
// not supporting this for equals (but probably could)
// prop.getBeanProperty().getScalarType();
}
request.addBindValue(value());
}
@Override
public void addSql(SpiExpressionRequest request) {
ElPropertyValue prop = getElProp(request);
if (prop != null) {
if (prop.isAssocId()) {
request.append(prop.getAssocIdExpression(propName, type.bind()));
return;
}
if (prop.isDbEncrypted()) {
String dsql = prop.getBeanProperty().getDecryptSql();
request.append(dsql).append(type.bind());
return;
}
}
request.append(propName).append(type.bind());
}
/**
* Based on the type and propertyName.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(SimpleExpression.class).add(propName).add(type.name());
builder.bind(1);
}
@Override
public int queryBindHash() {
return value().hashCode();
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof SimpleExpression)) {
return false;
}
SimpleExpression that = (SimpleExpression) other;
return this.propName.equals(that.propName) && this.type == that.type;
}
@Override
public boolean isSameByBind(SpiExpression other) {
SimpleExpression that = (SimpleExpression) other;
return value().equals(that.value());
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.expression;
import io.ebean.search.TextCommonTerms;
import java.io.IOException;
/**
* Full text common terms expression.
*/
class TextCommonTermsExpression extends AbstractTextExpression {
private final String search;
private final TextCommonTerms options;
public TextCommonTermsExpression(String search, TextCommonTerms options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeTextCommonTerms(search, options);
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.expression;
import io.ebean.search.Match;
import java.io.IOException;
/**
* Full text MATCH expression.
*/
public class TextMatchExpression extends AbstractTextExpression {
private final String search;
private final Match options;
public TextMatchExpression(String propertyName, String search, Match options) {
super(propertyName);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeMatch(propName, search, options);
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.expression;
import io.ebean.search.MultiMatch;
import java.io.IOException;
/**
* Full text Multi-Match expression.
*/
public class TextMultiMatchExpression extends AbstractTextExpression {
private final String search;
private final MultiMatch options;
public TextMultiMatchExpression(String search, MultiMatch options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeMultiMatch(search, options);
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.expression;
import io.ebean.search.TextQueryString;
import java.io.IOException;
/**
* Full text query string expression.
*/
class TextQueryStringExpression extends AbstractTextExpression {
private final String search;
private final TextQueryString options;
public TextQueryStringExpression(String search, TextQueryString options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeTextQueryString(search, options);
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.expression;
import io.ebean.search.TextSimple;
import java.io.IOException;
/**
* Full text Multi-Match expression.
*/
class TextSimpleExpression extends AbstractTextExpression {
private final String search;
private final TextSimple options;
public TextSimpleExpression(String search, TextSimple options) {
super(null);
this.search = search;
this.options = options;
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeTextSimple(search, options);
}
}
@@ -0,0 +1,7 @@
package io.ebeaninternal.server.expression;
/**
* Marked interface for expressions unsupported in doc store.
*/
public interface UnsupportedDocStoreExpression {
}
@@ -0,0 +1,36 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Collection objects for lazy loading</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Expressions for building WHERE clauses.
<p>
You will most likely use expressions directly off the ExpressionList.
</p>
<pre class="code">
Query&lt;Order&gt; query = Ebean.createQuery(Order.class)
.where()
.like(&quot;customer.name&quot;,&quot;rob%&quot;)
.gt(&quot;orderDate&quot;,lastWeek);
List&lt;Order&gt; orderList = query.findList();
...
</pre>
<p>
In the code above the LIKE and GREATER THAN Expressions are added to the where clause.
The way this works is that where() returns an ExpressionList which has methods on
it to create the standard expressions (EQUAL TO, LIKE etc).
</p>
<p>
I expect most people to add their expressions in this way. The Expr expression factory
object also has the ability to create the standard expressions.
</p>
<p>
You can build your own Expression objects by implementing the Expression interface.
</p>
</Body>
</HTML>