com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties Maven / Gradle / Ivy
package com.avaje.ebeaninternal.server.querydefn;
import com.avaje.ebean.ExpressionFactory;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.Query;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpression;
import com.avaje.ebeaninternal.api.SpiExpressionFactory;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.expression.FilterExprPath;
import com.avaje.ebeaninternal.server.expression.FilterExpressionList;
import com.avaje.ebeaninternal.server.expression.Same;
import com.avaje.ebeaninternal.server.query.SplitName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Represents the Properties of an Object Relational query.
*/
public class OrmQueryProperties implements Serializable {
private static final long serialVersionUID = -8785582703966455658L;
protected static final FetchConfig DEFAULT_FETCH = new FetchConfig();
private final String parentPath;
private final String path;
private final String rawProperties;
private final String trimmedProperties;
private final LinkedHashSet included;
private final FetchConfig fetchConfig;
/**
* Flag set when this fetch path needs to be a query join.
*/
private boolean markForQueryJoin;
private boolean cache;
private boolean readOnly;
/**
* Included bean joins.
*/
private Set includedBeanJoin;
/**
* Add these properties to the select so that the foreign key columns are included in the query.
*/
private Set secondaryQueryJoins;
private List secondaryChildren;
/**
* OrderBy properties that where on the main query but moved here as they relate to this (query join).
*/
@SuppressWarnings("rawtypes")
private OrderBy orderBy;
/**
* A filter that can be applied to the fetch of this path in the object graph.
*/
@SuppressWarnings("rawtypes")
private SpiExpressionList filterMany;
/**
* Construct for root so path (and parentPath) are null.
*/
public OrmQueryProperties() {
this((String)null);
}
/**
* Construct with a given path.
*/
public OrmQueryProperties(String path) {
this.path = path;
this.parentPath = SplitName.parent(path);
this.rawProperties = null;
this.trimmedProperties = null;
this.included = null;
this.fetchConfig = DEFAULT_FETCH;
}
public OrmQueryProperties(String path, String rawProperties) {
this(path, rawProperties, null);
}
public OrmQueryProperties(String path, String rawProperties, FetchConfig fetchConfig) {
OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties);
this.path = path;
this.parentPath = SplitName.parent(path);
this.rawProperties = rawProperties;
this.trimmedProperties = response.properties;
this.included = response.included;
this.cache = response.cache;
this.readOnly = response.readOnly;
if (fetchConfig != null) {
this.fetchConfig = fetchConfig;
} else {
this.fetchConfig = response.fetchConfig;
}
}
public OrmQueryProperties(String path, LinkedHashSet parsedProperties) {
if (parsedProperties == null) {
throw new IllegalArgumentException("parsedProperties is null");
}
this.path = path;
this.parentPath = SplitName.parent(path);
// for rawSql parsedProperties can be empty (when only fetching Id property)
this.included = parsedProperties;
this.rawProperties = join(parsedProperties);
this.trimmedProperties = rawProperties;
this.cache = false;
this.readOnly = false;
this.fetchConfig = DEFAULT_FETCH;
}
/**
* Join the set of properties into a comma delimited string.
*/
private String join(LinkedHashSet parsedProperties) {
StringBuilder sb = new StringBuilder(50);
boolean first = true;
for (String property : parsedProperties) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(property);
}
return sb.toString();
}
/**
* Copy constructor.
*/
private OrmQueryProperties(OrmQueryProperties source) {
this.parentPath = source.parentPath;
this.path = source.path;
this.rawProperties = source.rawProperties;
this.trimmedProperties = source.trimmedProperties;
this.cache = source.cache;
this.readOnly = source.readOnly;
this.fetchConfig = source.fetchConfig;
this.filterMany = source.filterMany;
this.included = (source.included == null) ? null : new LinkedHashSet(source.included);
if (includedBeanJoin != null) {
this.includedBeanJoin = new HashSet(source.includedBeanJoin);
}
}
/**
* Creates a copy of the OrmQueryProperties.
*/
public OrmQueryProperties copy() {
return new OrmQueryProperties(this);
}
/**
* Move a OrderBy.Property from the main query to this query join.
*/
@SuppressWarnings("rawtypes")
public void addSecJoinOrderProperty(OrderBy.Property orderProp) {
if (orderBy == null) {
orderBy = new OrderBy();
}
orderBy.add(orderProp);
}
public FetchConfig getFetchConfig() {
return fetchConfig;
}
/**
* Return the expressions used to filter on this path. This should be a many path to use this
* method.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public SpiExpressionList filterMany(Query rootQuery) {
if (filterMany == null) {
FilterExprPath exprPath = new FilterExprPath(path);
SpiExpressionFactory queryEf = (SpiExpressionFactory) rootQuery.getExpressionFactory();
ExpressionFactory filterEf = queryEf.createExpressionFactory();// exprPath);
filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery);
// by default we need to make this a 'query join' now
markForQueryJoin = true;
}
return filterMany;
}
/**
* Return the filterMany expression list (can be null).
*/
public SpiExpressionList> getFilterManyTrimPath(int trimPath) {
if (filterMany == null) {
return null;
}
return filterMany.trimPath(trimPath);
}
/**
* Return the filterMany expression list (can be null).
*/
public SpiExpressionList> getFilterMany() {
return filterMany;
}
/**
* Set the filterMany expression list.
*/
public void setFilterMany(SpiExpressionList> filterMany) {
this.filterMany = filterMany;
this.markForQueryJoin = true;
}
/**
* Define the select and joins for this query.
*/
@SuppressWarnings("unchecked")
public void configureBeanQuery(SpiQuery> query) {
if (trimmedProperties != null && !trimmedProperties.isEmpty()) {
query.select(trimmedProperties);
}
if (filterMany != null) {
SpiExpressionList> trimPath = filterMany.trimPath(path.length() + 1);
List underlyingList = trimPath.getUnderlyingList();
for (SpiExpression spiExpression : underlyingList) {
query.where().add(spiExpression);
}
}
if (secondaryChildren != null) {
int trimPath = path.length() + 1;
for (int i = 0; i < secondaryChildren.size(); i++) {
OrmQueryProperties p = secondaryChildren.get(i);
String path = p.getPath();
path = path.substring(trimPath);
query.fetch(path, p.getProperties(), p.getFetchConfig());
query.setFilterMany(path, p.getFilterManyTrimPath(trimPath));
}
}
if (orderBy != null) {
query.setOrder(orderBy.copyWithTrim(path));
}
}
public boolean hasSelectClause() {
if ("*".equals(trimmedProperties)) {
// explicitly selected all properties
return true;
}
// explicitly selected some properties
return included != null || filterMany != null;
}
/**
* Return true if the properties and configuration are empty.
*/
public boolean isEmpty() {
return rawProperties == null || rawProperties.isEmpty();
}
public String toString() {
StringBuilder sb = new StringBuilder(40);
append("", sb);
return sb.toString();
}
public String append(String prefix, StringBuilder sb) {
sb.append(prefix);
if (path != null) {
sb.append(path).append(" ");
}
if (!isEmpty()) {
sb.append("(").append(rawProperties).append(") ");
}
return sb.toString();
}
public boolean isChild(OrmQueryProperties possibleChild) {
return possibleChild.getPath().startsWith(path + ".");
}
/**
* For secondary queries add a child element.
*/
public void add(OrmQueryProperties child) {
if (secondaryChildren == null) {
secondaryChildren = new ArrayList();
}
secondaryChildren.add(child);
}
/**
* Return the raw properties.
*/
public String getProperties() {
return rawProperties;
}
/**
* Return true if this includes all properties on the path.
*/
public boolean allProperties() {
return included == null;
}
/**
* Return true if this property is included as a bean join.
*
* If a property is included as a bean join then it should not be included as a reference/proxy to
* avoid duplication.
*
*/
public boolean isIncludedBeanJoin(String propertyName) {
return includedBeanJoin != null && includedBeanJoin.contains(propertyName);
}
/**
* Add a bean join property.
*/
public void includeBeanJoin(String propertyName) {
if (includedBeanJoin == null) {
includedBeanJoin = new HashSet();
}
includedBeanJoin.add(propertyName);
}
/**
* This excludes the bean joined properties.
*
* This is because bean joins will have there own node in the SqlTree.
*
*/
public Set getSelectProperties() {
if (secondaryQueryJoins == null) {
return included;
}
LinkedHashSet temp = new LinkedHashSet(2 * (secondaryQueryJoins.size() + included.size()));
temp.addAll(included);
temp.addAll(secondaryQueryJoins);
return temp;
}
public void addSecondaryQueryJoin(String property) {
if (secondaryQueryJoins == null) {
secondaryQueryJoins = new HashSet(4);
}
secondaryQueryJoins.add(property);
}
/**
* Return the property set.
*/
public Set getIncluded() {
return included;
}
public boolean isIncluded(String propName) {
if (includedBeanJoin != null && includedBeanJoin.contains(propName)) {
return false;
}
// all properties included
return included == null || included.contains(propName);
}
/**
* Mark this path as needing to be a query join.
*/
public void markForQueryJoin() {
markForQueryJoin = true;
}
/**
* Return true if this path is a 'query join'.
*/
public boolean isQueryFetch() {
return markForQueryJoin || getQueryFetchBatch() > -1;
}
/**
* Return true if this path is a 'fetch join'.
*/
public boolean isFetchJoin() {
return !isQueryFetch() && !isLazyFetch();
}
/**
* Return true if this path is a lazy fetch.
*/
public boolean isLazyFetch() {
return getLazyFetchBatch() > -1;
}
/**
* Return the batch size to use for the query join.
*/
public int getQueryFetchBatch() {
return fetchConfig.getQueryBatchSize();
}
/**
* Return true if a query join should eagerly fetch 'all' rather than the 'first'.
*/
public boolean isQueryFetchAll() {
return fetchConfig.isQueryAll();
}
/**
* Return the batch size to use for lazy loading.
*/
public int getLazyFetchBatch() {
return fetchConfig.getLazyBatchSize();
}
/**
* Return true if this path has the +readonly option.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Return true if this path has the +cache option to hit the cache.
*/
public boolean isCache() {
return cache;
}
/**
* Return the parent path.
*/
public String getParentPath() {
return parentPath;
}
/**
* Return the path relative to the root of the graph.
*/
public String getPath() {
return path;
}
/**
* Return true if the properties are the same for autoTune purposes.
*/
public boolean isSameByAutoTune(OrmQueryProperties p2) {
if (included == null) {
return p2 == null || p2.included == null;
} else if (p2 == null) {
return false;
}
return included.equals(p2.included);
}
/**
* Properties are the same for query plan purposes.
*/
public boolean isSameByPlan(OrmQueryProperties p2) {
if (!Same.sameByValue(secondaryQueryJoins, p2.secondaryQueryJoins)) return false;
if (!Same.sameByValue(included, p2.included)) return false;
if (!Same.sameByNull(filterMany, p2.filterMany)) return false;
if (filterMany != null && !filterMany.isSameByPlan(p2.filterMany)) return false;
return fetchConfig.equals(p2.fetchConfig);
}
/**
* Calculate the query plan hash.
*/
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(path);
builder.addOrdered(included);
builder.add(secondaryQueryJoins);
builder.add(filterMany != null);
if (filterMany != null) {
filterMany.queryPlanHash(builder);
}
builder.add(fetchConfig.hashCode());
}
}