mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#919 - io.ebean package initial
This commit is contained in:
@@ -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<Order> query = Ebean.createQuery(Order.class)
|
||||
.where()
|
||||
.like("customer.name","rob%")
|
||||
.gt("orderDate",lastWeek);
|
||||
|
||||
List<Order> 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>
|
||||
Reference in New Issue
Block a user