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,507 @@
|
||||
package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.ExpressionFactory;
|
||||
import io.ebean.FetchConfig;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.Query;
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
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 io.ebeaninternal.server.expression.Same;
|
||||
import io.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;
|
||||
|
||||
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.
|
||||
*/
|
||||
private boolean markForQueryJoin;
|
||||
|
||||
private boolean cache;
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
/**
|
||||
* Included bean joins.
|
||||
*/
|
||||
private Set<String> includedBeanJoin;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
@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<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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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")
|
||||
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 <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).
|
||||
*/
|
||||
private 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<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.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
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.
|
||||
* <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.
|
||||
*/
|
||||
void includeBeanJoin(String propertyName) {
|
||||
if (includedBeanJoin == null) {
|
||||
includedBeanJoin = new HashSet<>();
|
||||
}
|
||||
includedBeanJoin.add(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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'.
|
||||
*/
|
||||
boolean isFetchJoin() {
|
||||
return !isQueryFetch() && !isLazyFetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this path is a lazy fetch.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user