All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties Maven / Gradle / Ivy

There is a newer version: 8.1.1
Show newest version
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()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy