package io.ebeaninternal.server.querydefn;
import io.ebean.ExpressionFactory;
import io.ebean.FetchConfig;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionFactory;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.expression.FilterExprPath;
import io.ebeaninternal.server.expression.FilterExpressionList;
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.
/**
* Represents the Properties of an Object Relational query.
*/
public class OrmQueryProperties implements Serializable {
private static final long serialVersionUID = -8785582703966455658L;
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<String> included;
private final FetchConfig fetchConfig;
Flag set when this fetch path needs to be a query join.
/**
* Flag set when this fetch path needs to be a query join.
*/
private boolean markForQueryJoin;
private boolean cache;
private boolean readOnly;
Included bean joins.
/**
* Included bean joins.
*/
private Set<String> includedBeanJoin;
Add these properties to the select so that the foreign key columns are included in the query.
/**
* Add these properties to the select so that the foreign key columns are included in the query.
*/
private Set<String> secondaryQueryJoins;
private List<OrmQueryProperties> secondaryChildren;
OrderBy properties that where on the main query but moved here as they relate to this (query join).
/**
* 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.
/**
* 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.
/**
* Construct for root so path (and parentPath) are null.
*/
public OrmQueryProperties() {
this(null);
}
Construct with a given path.
/**
* 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<String> 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.
/**
* Join the set of properties into a comma delimited string.
*/
private String join(LinkedHashSet<String> 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.
/**
* Copy constructor.
*/
private OrmQueryProperties(OrmQueryProperties source, FetchConfig sourceFetchConfig) {
this.fetchConfig = sourceFetchConfig;
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.filterMany = source.filterMany;
this.markForQueryJoin = source.markForQueryJoin;
this.included = (source.included == null) ? null : new LinkedHashSet<>(source.included);
}
Creates a copy of the OrmQueryProperties.
/**
* Creates a copy of the OrmQueryProperties.
*/
public OrmQueryProperties copy() {
return new OrmQueryProperties(this, this.fetchConfig);
}
Create a copy with the given fetch config.
/**
* Create a copy with the given fetch config.
*/
public OrmQueryProperties copy(FetchConfig fetchConfig) {
return new OrmQueryProperties(this, fetchConfig);
}
Move a OrderBy.Property from the main query to this query join.
/**
* Move a OrderBy.Property from the main query to this query join.
*/
@SuppressWarnings("rawtypes")
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.
/**
* Return the expressions used to filter on this path. This should be a many path to use this
* method.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> SpiExpressionList<T> filterMany(Query<T> 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).
/**
* Return the filterMany expression list (can be null).
*/
private SpiExpressionList<?> getFilterManyTrimPath(int trimPath) {
if (filterMany == null) {
return null;
}
return filterMany.trimPath(trimPath);
}
Return the filterMany expression list (can be null).
/**
* Return the filterMany expression list (can be null).
*/
public SpiExpressionList<?> getFilterMany() {
return filterMany;
}
Set the filterMany expression list.
/**
* Set the filterMany expression list.
*/
public void setFilterMany(SpiExpressionList<?> filterMany) {
this.filterMany = filterMany;
this.markForQueryJoin = true;
}
Define the select and joins for this query.
/**
* 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<SpiExpression> underlyingList = trimPath.getUnderlyingList();
for (SpiExpression spiExpression : underlyingList) {
query.where().add(spiExpression);
}
}
if (secondaryChildren != null) {
int trimPath = path.length() + 1;
for (OrmQueryProperties p : secondaryChildren) {
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.
/**
* Return true if the properties and configuration are empty.
*/
public boolean isEmpty() {
return rawProperties == null || rawProperties.isEmpty();
}
@Override
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();
}
boolean isChild(OrmQueryProperties possibleChild) {
return possibleChild.getPath().startsWith(path + ".");
}
For secondary queries add a child element.
/**
* 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.
/**
* Return the raw properties.
*/
public String getProperties() {
return rawProperties;
}
Return true if this includes all properties on the path.
/**
* 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.
/**
* Return true if this property is included as a bean join.
* <p>
* If a property is included as a bean join then it should not be included as a reference/proxy to
* avoid duplication.
* </p>
*/
public boolean isIncludedBeanJoin(String propertyName) {
return includedBeanJoin != null && includedBeanJoin.contains(propertyName);
}
Add a bean join property.
/**
* Add a bean join property.
*/
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.
/**
* This excludes the bean joined properties.
* <p>
* This is because bean joins will have there own node in the SqlTree.
* </p>
*/
public Set<String> getSelectProperties() {
if (secondaryQueryJoins == null) {
return included;
}
LinkedHashSet<String> temp = new LinkedHashSet<>(2 * (secondaryQueryJoins.size() + included.size()));
temp.addAll(included);
temp.addAll(secondaryQueryJoins);
return temp;
}
void addSecondaryQueryJoin(String property) {
if (secondaryQueryJoins == null) {
secondaryQueryJoins = new HashSet<>(4);
}
secondaryQueryJoins.add(property);
}
Return the property set.
/**
* Return the property set.
*/
public Set<String> getIncluded() {
return included;
}
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.
/**
* Mark this path as needing to be a query join.
*/
void markForQueryJoin() {
markForQueryJoin = true;
}
Return true if this path is a 'query join'.
/**
* 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'.
/**
* Return true if this path is a 'fetch join'.
*/
boolean isFetchJoin() {
return !isQueryFetch() && !isLazyFetch();
}
Return true if this path is a lazy fetch.
/**
* Return true if this path is a lazy fetch.
*/
boolean isLazyFetch() {
return getLazyFetchBatch() > -1;
}
Return the batch size to use for the query join.
/**
* 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'.
/**
* 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.
/**
* Return the batch size to use for lazy loading.
*/
public int getLazyFetchBatch() {
return fetchConfig.getLazyBatchSize();
}
Return true if this path has the +readonly option.
/**
* 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.
/**
* Return true if this path has the +cache option to hit the cache.
*/
public boolean isCache() {
return cache;
}
Return the parent path.
/**
* Return the parent path.
*/
String getParentPath() {
return parentPath;
}
Return the path relative to the root of the graph.
/**
* 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.
/**
* Return true if the properties are the same for autoTune purposes.
*/
boolean isSameByAutoTune(OrmQueryProperties p2) {
if (included == null) {
return p2 == null || p2.included == null;
} else if (p2 == null) {
return false;
}
return included.equals(p2.included);
}
Calculate the query plan hash.
/**
* Calculate the query plan hash.
*/
public void queryPlanHash(StringBuilder builder) {
builder.append("qpp[");
builder.append(path);
if (included != null){
builder.append(" included:").append(included);
}
if (secondaryQueryJoins != null) {
builder.append(" secondary:").append(secondaryQueryJoins);
}
if (filterMany != null) {
builder.append(" filterMany[");
filterMany.queryPlanHash(builder);
builder.append("]");
}
if (fetchConfig != null) {
builder.append(" config:").append(fetchConfig.hashCode());
}
builder.append("]");
}
}