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

io.ebeaninternal.server.querydefn.DefaultOrmQuery Maven / Gradle / Ivy

There is a newer version: 15.8.0
Show newest version
package io.ebeaninternal.server.querydefn;

import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanQueryRequest;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.*;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.expression.DefaultExpressionList;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.expression.InExpression;
import io.ebeaninternal.server.expression.SimpleExpression;
import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;

import jakarta.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * Default implementation of an Object Relational query.
 */
@NonNullApi
public class DefaultOrmQuery extends AbstractQuery implements SpiQuery {

  private static final String DEFAULT_QUERY_NAME = "default";
  private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
  private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery();
  private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy();

  private final Class beanType;
  private final ExpressionFactory expressionFactory;
  private final BeanDescriptor rootBeanDescriptor;
  private BeanDescriptor beanDescriptor;
  private SpiEbeanServer server;
  private SpiTransaction transaction;
  /**
   * For lazy loading of ManyToMany we need to add a join to the intersection table. This is that
   * join to the intersection table.
   */
  private TableJoin m2mIncludeJoin;
  private ProfilingListener profilingListener;
  private Type type;
  private String label;
  private String hint;
  private Mode mode = Mode.NORMAL;
  private boolean usingFuture;
  private Object tenantId;
  /**
   * Holds query in structured form.
   */
  private OrmQueryDetail detail;
  private int maxRows;
  private int firstRow;
  private boolean disableLazyLoading;
  /**
   * Lazy loading batch size (can override server wide default).
   */
  private int lazyLoadBatchSize;
  private String distinctOn;
  private OrderBy orderBy;
  private String loadMode;
  private String loadDescription;
  private String generatedSql;
  private String lazyLoadProperty;
  private String lazyLoadManyPath;
  private boolean allowLoadErrors;
  /**
   * Flag set for report/DTO beans when we may choose to explicitly include the Id property.
   */
  private boolean manualId;

  /**
   * Set to true by a user wanting a DISTINCT query (id property must be excluded).
   */
  private boolean distinct;

  private int timeout;

  /**
   * The property used to get the key value for a Map.
   */
  private String mapKey;

  /**
   * Used for find by id type query.
   */
  private Object id;

  private Map namedParams;

  /**
   * Bind parameters when using the query language.
   */
  private BindParams bindParams;
  private DefaultExpressionList whereExpressions;
  private DefaultExpressionList havingExpressions;
  private boolean asOfBaseTable;
  private int asOfTableCount;

  /**
   * Set for flashback style 'as of' query.
   */
  private Timestamp asOf;
  private TemporalMode temporalMode = TemporalMode.CURRENT;
  private Timestamp versionsStart;
  private Timestamp versionsEnd;
  private List softDeletePredicates;
  private boolean disableReadAudit;
  private int bufferFetchSizeHint;
  private boolean usageProfiling = true;
  private CacheMode useBeanCache = CacheMode.AUTO;
  private CacheMode useQueryCache = CacheMode.OFF;
  private Boolean readOnly;
  private PersistenceContextScope persistenceContextScope;

  /**
   * Allow for explicit on off or null for default.
   */
  private Boolean autoTune;
  private LockWait forUpdate;
  private LockType lockType;
  private boolean singleAttribute;
  private CountDistinctOrder countDistinctOrder;
  private boolean autoTuned;
  private String rootTableAlias;
  private String baseTable;
  /**
   * The node of the bean or collection that fired lazy loading. Not null if profiling is on and
   * this query is for lazy loading. Used to hook back a lazy loading query to the "original" query
   * point.
   */
  private ObjectGraphNode parentNode;
  private BeanPropertyAssocMany lazyLoadForParentsProperty;
  private CQueryPlanKey queryPlanKey;
  private PersistenceContext persistenceContext;
  private ManyWhereJoins manyWhereJoins;
  private SpiRawSql rawSql;
  private OrmUpdateProperties updateProperties;
  private String nativeSql;
  private boolean orderById;
  private ProfileLocation profileLocation;

  public DefaultOrmQuery(BeanDescriptor desc, SpiEbeanServer server, ExpressionFactory expressionFactory) {
    this.beanDescriptor = desc;
    this.rootBeanDescriptor = desc;
    this.beanType = desc.type();
    this.server = server;
    this.disableLazyLoading = server.config().isDisableLazyLoading();
    this.expressionFactory = expressionFactory;
    this.detail = new OrmQueryDetail();
  }

  public final void setNativeSql(String nativeSql) {
    this.nativeSql = nativeSql;
  }

  @Override
  public final  DtoQuery asDto(Class dtoClass) {
    return server.findDto(dtoClass, this);
  }

  @Override
  public final UpdateQuery asUpdate() {
    return new DefaultUpdateQuery<>(this);
  }

  @Override
  public final BeanDescriptor descriptor() {
    return beanDescriptor;
  }

  @Override
  public final boolean isFindAll() {
    return whereExpressions == null && nativeSql == null && rawSql == null;
  }

  @Override
  public final boolean isFindById() {
    if (id == null && whereExpressions != null) {
      id = whereExpressions.idEqualTo(beanDescriptor.idName());
      if (id != null) {
        whereExpressions = null;
      }
    }
    return id != null;
  }

  @Override
  public final String profileEventId() {
    switch (mode) {
      case LAZYLOAD_BEAN:
        return FIND_ONE_LAZY;
      case LAZYLOAD_MANY:
        return FIND_MANY_LAZY;
      default:
        return type.profileEventId();
    }
  }

  @Override
  public final String profileId() {
    return planLabel();
  }

  @Override
  public final Query setProfileLocation(ProfileLocation profileLocation) {
    this.profileLocation = profileLocation;
    return this;
  }

  @Override
  public final String label() {
    return label;
  }

  @Override
  public final String hint() {
    return hint;
  }

  @Override
  public final String planLabel() {
    if (label != null) {
      return label;
    }
    if (profileLocation != null) {
      return profileLocation.label();
    }
    return null;
  }

  @Override
  public final void setProfilePath(String label, String relativePath, @Nullable ProfileLocation profileLocation) {
    this.profileLocation = profileLocation;
    this.label = (profileLocation == null ? label : profileLocation.label()) + '_' + relativePath;
  }

  @Override
  public final Query setLabel(String label) {
    this.label = label;
    return this;
  }

  @Override
  public final Query setHint(String hint) {
    this.hint = hint;
    return this;
  }

  @Override
  public final boolean isAutoTunable() {
    return nativeSql == null && beanDescriptor.isAutoTunable();
  }

  @Override
  public final Query apply(FetchPath fetchPath) {
    fetchPath.apply(this);
    return this;
  }

  @Override
  public Query also(Consumer> apply) {
    apply.accept(this);
    return this;
  }

  @Override
  public Query alsoIf(BooleanSupplier predicate, Consumer> consumer) {
    if (predicate.getAsBoolean()) {
      consumer.accept(this);
    }
    return this;
  }

  @Override
  public final void addSoftDeletePredicate(String softDeletePredicate) {
    if (softDeletePredicates == null) {
      softDeletePredicates = new ArrayList<>();
    }
    softDeletePredicates.add(softDeletePredicate);
  }

  @Override
  public final List softDeletePredicates() {
    return softDeletePredicates;
  }

  @Override
  public final boolean isAsOfBaseTable() {
    return asOfBaseTable;
  }

  @Override
  public final void setAsOfBaseTable() {
    this.asOfBaseTable = true;
  }

  @Override
  public final Query setAllowLoadErrors() {
    this.allowLoadErrors = true;
    return this;
  }

  @Override
  public final void incrementAsOfTableCount() {
    asOfTableCount++;
  }

  @Override
  public final void incrementAsOfTableCount(int increment) {
    asOfTableCount += increment;
  }

  @Override
  public final int getAsOfTableCount() {
    return asOfTableCount;
  }

  @Override
  public final Timestamp getAsOf() {
    return asOf;
  }

  @Override
  public final Query asOf(Timestamp asOfDateTime) {
    this.temporalMode = (asOfDateTime != null) ? TemporalMode.AS_OF : TemporalMode.CURRENT;
    this.asOf = asOfDateTime;
    return this;
  }

  @Override
  public final Query setIncludeSoftDeletes() {
    this.temporalMode = TemporalMode.SOFT_DELETED;
    return this;
  }

  @Override
  public final SpiRawSql rawSql() {
    return rawSql;
  }

  @Override
  public final Query setRawSql(RawSql rawSql) {
    this.rawSql = (SpiRawSql) rawSql;
    return this;
  }

  @Override
  public final String getOriginKey() {
    if (parentNode == null || parentNode.origin() == null) {
      return null;
    } else {
      return parentNode.origin().key();
    }
  }

  @Override
  public final int lazyLoadBatchSize() {
    return lazyLoadBatchSize;
  }

  @Override
  public final Query setLazyLoadBatchSize(int lazyLoadBatchSize) {
    this.lazyLoadBatchSize = lazyLoadBatchSize;
    return this;
  }

  @Override
  public final String lazyLoadProperty() {
    return lazyLoadProperty;
  }

  @Override
  public final void setLazyLoadProperty(String lazyLoadProperty) {
    this.lazyLoadProperty = lazyLoadProperty;
  }

  @Override
  public final ExpressionFactory getExpressionFactory() {
    return expressionFactory;
  }

  private void createExtraJoinsToSupportManyWhereClause() {
    final var manyWhere = new ManyWhereJoins();
    if (whereExpressions != null) {
      whereExpressions.containsMany(beanDescriptor, manyWhere);
    }
    if (havingExpressions != null) {
      havingExpressions.containsMany(beanDescriptor, manyWhere);
    }
    if (orderBy != null) {
      for (OrderBy.Property orderProperty : orderBy.getProperties()) {
        ElPropertyDeploy elProp = beanDescriptor.elPropertyDeploy(orderProperty.getProperty());
        if (elProp != null && elProp.containsFormulaWithJoin()) {
          manyWhere.addFormulaWithJoin(elProp.elPrefix(), elProp.name());
        }
      }
    }
    manyWhereJoins = manyWhere;
  }

  /**
   * Return the extra joins required to support the where clause for 'Many' properties.
   */
  @Override
  public final ManyWhereJoins manyWhereJoins() {
    return manyWhereJoins;
  }

  /**
   * Return true if select all properties was used to ensure the property invoking a lazy load was
   * included in the query.
   */
  @Override
  public final void selectAllForLazyLoadProperty() {
    if (lazyLoadProperty != null) {
      if (!detail.containsProperty(lazyLoadProperty)) {
        detail.select("*");
      }
    }
  }

  private List removeQueryJoins() {
    List queryJoins = detail.removeSecondaryQueries();
    if (queryJoins != null) {
      if (orderBy != null) {
        // remove any orderBy properties that relate to
        // paths of the secondary queries
        for (OrmQueryProperties joinPath : queryJoins) {
          // loop through the orderBy properties and
          // move any ones related to the query join
          List properties = orderBy.getProperties();
          Iterator it = properties.iterator();
          while (it.hasNext()) {
            OrderBy.Property property = it.next();
            if (property.getProperty().startsWith(joinPath.getPath())) {
              // remove this orderBy segment and
              // add it to the secondary join
              it.remove();
              joinPath.addSecJoinOrderProperty(property);
            }
          }
        }
      }
    }
    return queryJoins;
  }

  private List removeLazyJoins() {
    return detail.removeSecondaryLazyQueries();
  }

  @Override
  public final void setLazyLoadManyPath(String lazyLoadManyPath) {
    this.lazyLoadManyPath = lazyLoadManyPath;
  }

  @Override
  public final SpiQueryManyJoin convertJoins() {
    createExtraJoinsToSupportManyWhereClause();
    return markQueryJoins();
  }

  @Override
  public SpiQuerySecondary secondaryQuery() {
    return new OrmQuerySecondary(removeQueryJoins(), removeLazyJoins());
  }

  /**
   * Limit the number of fetch joins to Many properties, mark as query joins as needed.
   *
   * @return The query join many property or null.
   */
  private SpiQueryManyJoin markQueryJoins() {
    // no automatic join to query join conversion when distinctOn is used
    return distinctOn != null ? null : detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type.defaultSelect());
  }

  private boolean isAllowOneManyFetch() {
    if (Mode.LAZYLOAD_MANY == mode) {
      return false;
    } else {
      return singleAttribute || !hasMaxRowsOrFirstRow() || isRawSql();
    }
  }

  @Override
  public final void setDefaultSelectClause() {
    if (type.defaultSelect()) {
      detail.setDefaultSelectClause(beanDescriptor);
    } else if (!detail.hasSelectClause()) {
      // explicit empty select when single attribute query on non-root fetch path
      detail.setEmptyBase();
    }
  }

  @Override
  public final void setTenantId(Object tenantId) {
    this.tenantId = tenantId;
  }

  @Override
  public final Object tenantId() {
    return tenantId;
  }

  @Override
  public final void setDetail(OrmQueryDetail detail) {
    this.detail = detail;
  }

  @Override
  public final boolean tuneFetchProperties(OrmQueryDetail tunedDetail) {
    return detail.tuneFetchProperties(tunedDetail);
  }

  @Override
  public final OrmQueryDetail detail() {
    return detail;
  }

  @Override
  public final ExpressionList filterMany(String prop) {

    OrmQueryProperties chunk = detail.getChunk(prop, true);
    return chunk.filterMany(this);
  }

  @Override
  public final void setFilterMany(String prop, ExpressionList filterMany) {
    if (filterMany != null) {
      OrmQueryProperties chunk = detail.getChunk(prop, true);
      chunk.setFilterMany((SpiExpressionList) filterMany);
    }
  }

  /**
   * Setup to be a delete or update query.
   */
  @Override
  public final void setupForDeleteOrUpdate() {
    forUpdate = null;
    rootTableAlias = "${RTA}"; // alias we remove later
    setSelectId();
  }

  @Override
  public final CQueryPlanKey setDeleteByIdsPlan() {
    // re-build plan for cascading via delete by ids
    queryPlanKey = queryPlanKey.withDeleteByIds();
    return queryPlanKey;
  }

  /**
   * Set the select clause to select the Id property.
   */
  @Override
  public final void setSelectId() {
    if (rawSql != null) {
      String column = rawSql.mapToColumn(beanDescriptor.idSelect());
      if (column != null) {
        select(column);
      }
    } else {
      // clear select and fetch joins
      detail.clear();
      select(beanDescriptor.idSelect());
      singleAttribute = true;
    }
  }

  @Override
  public final void setSingleAttribute() {
    this.singleAttribute = true;
  }

  /**
   * Return true if this is a single attribute query.
   */
  @Override
  public final boolean isSingleAttribute() {
    return singleAttribute;
  }

  @Override
  public final CountDistinctOrder countDistinctOrder() {
    return countDistinctOrder;
  }

  @Override
  public final boolean isWithId() {
    // distinctOn orm query will auto include the id property
    // distinctOn dto query does NOT (via setting manualId to true)
    return !manualId && !singleAttribute && (!distinct || distinctOn != null);
  }

  @Override
  public final CacheIdLookup cacheIdLookup() {
    if (whereExpressions == null) {
      return null;
    }
    List underlyingList = whereExpressions.underlyingList();
    if (underlyingList.isEmpty()) {
      if (id != null) {
        return new CacheIdLookupSingle<>(id);
      }
    } else if (underlyingList.size() == 1) {
      SpiExpression singleExpression = underlyingList.get(0);
      if (singleExpression instanceof IdInExpression) {
        return new CacheIdLookupMany<>((IdInExpression) singleExpression);
      } else if (singleExpression instanceof InExpression) {
        InExpression in = (InExpression) singleExpression;
        if (in.property().equals(beanDescriptor.idName())) {
          return new CacheIdLookupMany<>(in);
        }
      }
    }
    return null;
  }

  @Override
  public final NaturalKeyQueryData naturalKey() {
    if (whereExpressions == null) {
      return null;
    }
    BeanNaturalKey naturalKey = beanDescriptor.naturalKey();
    if (naturalKey == null) {
      return null;
    }

    NaturalKeyQueryData data = new NaturalKeyQueryData<>(naturalKey);
    for (SpiExpression expression : whereExpressions.underlyingList()) {
      // must be eq or in
      if (!expression.naturalKey(data)) {
        return null;
      }
    }
    return data;
  }

  @Override
  public final NaturalKeyBindParam naturalKeyBindParam() {
    NaturalKeyBindParam namedBind = null;
    if (bindParams != null) {
      namedBind = bindParams.naturalKeyBindParam();
      if (namedBind == null) {
        return null;
      }
    }

    if (whereExpressions != null) {
      List exprList = whereExpressions.internalList();
      if (exprList.size() > 1) {
        return null;
      } else if (exprList.isEmpty()) {
        return namedBind;
      } else {
        if (namedBind != null) {
          return null;
        }
        SpiExpression se = exprList.get(0);
        if (se instanceof SimpleExpression) {
          SimpleExpression e = (SimpleExpression) se;
          if (e.isOpEquals()) {
            return new NaturalKeyBindParam(e.getPropName(), e.getValue());
          }
        }
      }
    }
    return null;
  }

  @Override
  public SpiQuery copy() {
    return copy(server);
  }

  @Override
  public SpiQuery copy(SpiEbeanServer server) {
    // forUpdate is NOT copied - see #2762
    DefaultOrmQuery copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
    copy.transaction = transaction;
    copy.useMaster = useMaster;
    copy.m2mIncludeJoin = m2mIncludeJoin;
    copy.profilingListener = profilingListener;
    copy.profileLocation = profileLocation;
    copy.baseTable = baseTable;
    copy.rootTableAlias = rootTableAlias;
    copy.distinct = distinct;
    copy.distinctOn = distinctOn;
    copy.allowLoadErrors = allowLoadErrors;
    copy.timeout = timeout;
    copy.mapKey = mapKey;
    copy.id = id;
    copy.hint = hint;
    copy.label = label;
    copy.nativeSql = nativeSql;
    copy.useBeanCache = useBeanCache;
    copy.useQueryCache = useQueryCache;
    copy.readOnly = readOnly;
    if (detail != null) {
      copy.detail = detail.copy();
    }
    copy.temporalMode = temporalMode;
    copy.firstRow = firstRow;
    copy.maxRows = maxRows;
    if (orderBy != null) {
      copy.orderBy = orderBy.copy();
    }
    copy.orderById = orderById;
    if (bindParams != null) {
      copy.bindParams = bindParams.copy();
    }
    if (whereExpressions != null) {
      copy.whereExpressions = whereExpressions.copy(copy);
    }
    if (havingExpressions != null) {
      copy.havingExpressions = havingExpressions.copy(copy);
    }
    copy.persistenceContextScope = persistenceContextScope;
    copy.usageProfiling = usageProfiling;
    copy.autoTune = autoTune;
    copy.parentNode = parentNode;
    copy.rawSql = rawSql;
    setCancelableQuery(copy); // required to cancel findId query
    return copy;
  }

  @Override
  public final Query setPersistenceContextScope(PersistenceContextScope scope) {
    this.persistenceContextScope = scope;
    return this;
  }

  @Override
  public final PersistenceContextScope persistenceContextScope() {
    return persistenceContextScope;
  }

  @Override
  public final Type type() {
    return type;
  }

  @Override
  public final void setType(Type type) {
    this.type = type;
  }

  @Override
  public String distinctOn() {
    return distinctOn;
  }

  @Override
  public final String loadDescription() {
    return loadDescription;
  }

  @Override
  public final String loadMode() {
    return loadMode;
  }

  @Override
  public final void setLoadDescription(String loadMode, String loadDescription) {
    this.loadMode = loadMode;
    this.loadDescription = loadDescription;
  }

  /**
   * Return the TransactionContext.
   * 

* If no TransactionContext is present on the query then the TransactionContext from the * Transaction is used (transaction scoped persistence context). *

*/ @Override public final PersistenceContext persistenceContext() { return persistenceContext; } /** * Set an explicit TransactionContext (typically for a refresh query). *

* If no TransactionContext is present on the query then the TransactionContext from the * Transaction is used (transaction scoped persistence context). *

*/ @Override public final void setPersistenceContext(PersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; } @Override public final void setLazyLoadForParents(BeanPropertyAssocMany many) { this.lazyLoadForParentsProperty = many; } @Override public final BeanPropertyAssocMany lazyLoadMany() { return lazyLoadForParentsProperty; } /** * Return true if the query detail has neither select or joins specified. */ @Override public final boolean isDetailEmpty() { return detail.isEmpty(); } @Override public final boolean isAutoTuned() { return autoTuned; } @Override public final void setAutoTuned(boolean autoTuned) { this.autoTuned = autoTuned; } @Override public final Boolean isAutoTune() { return autoTune; } @Override public final Query setAutoTune(boolean autoTune) { this.autoTune = autoTune; return this; } @Override public final Query withLock(LockType lockType) { return setForUpdateWithMode(LockWait.WAIT, lockType); } @Override public final Query withLock(LockType lockType, LockWait lockWait) { return setForUpdateWithMode(lockWait, lockType); } @Override public final Query forUpdate() { return setForUpdateWithMode(LockWait.WAIT, LockType.DEFAULT); } @Override public final Query forUpdateNoWait() { return setForUpdateWithMode(LockWait.NOWAIT, LockType.DEFAULT); } @Override public final Query forUpdateSkipLocked() { return setForUpdateWithMode(LockWait.SKIPLOCKED, LockType.DEFAULT); } private Query setForUpdateWithMode(LockWait mode, LockType lockType) { this.forUpdate = mode; this.lockType = lockType; this.useBeanCache = CacheMode.OFF; return this; } @Override public final boolean isForUpdate() { return forUpdate != null; } @Override public final LockWait getForUpdateLockWait() { return forUpdate; } @Override public final LockType getForUpdateLockType() { return lockType; } @Override public final ProfilingListener profilingListener() { return profilingListener; } @Override public final void setProfilingListener(ProfilingListener profilingListener) { this.profilingListener = profilingListener; } @Override public final QueryType getQueryType() { if (type != null) { switch (type) { case DELETE: return QueryType.DELETE; case UPDATE: return QueryType.UPDATE; } } return QueryType.FIND; } @Override public final Mode mode() { return mode; } @Override public final TemporalMode temporalMode() { return temporalMode; } @Override public final boolean isAsOfQuery() { return asOf != null; } @Override public final boolean isIncludeSoftDeletes() { return TemporalMode.SOFT_DELETED == temporalMode; } @Override public final void setMode(Mode mode) { this.mode = mode; } @Override public final void usingFuture() { this.usingFuture = true; } @Override public final boolean isUsingFuture() { return usingFuture; } @Override public final boolean isUsageProfiling() { return usageProfiling; } @Override public final void setUsageProfiling(boolean usageProfiling) { this.usageProfiling = usageProfiling; } @Override public final void setParentNode(ObjectGraphNode parentNode) { this.parentNode = parentNode; } @Override public final ObjectGraphNode parentNode() { return parentNode; } @Override public final ObjectGraphNode setOrigin(CallOrigin callOrigin) { // create a 'origin' which links this query to the profiling information ObjectGraphOrigin o = new ObjectGraphOrigin(calculateOriginQueryHash(), callOrigin, beanType.getName()); parentNode = new ObjectGraphNode(o, null); return parentNode; } /** * Calculate a hash for use in determining the ObjectGraphOrigin. *

* This should be quite a stable hash as most uniqueness is determined by the CallStack, so we * only use the bean type and overall query type. *

*

* This stable hash allows the query to be changed (joins added etc) without losing the already * collected usage profiling. *

*/ private int calculateOriginQueryHash() { int hc = beanType.getName().hashCode(); hc = hc * 92821 + (type == null ? 0 : type.ordinal()); return hc; } /** * Calculate the query hash for either AutoTune query tuning or Query Plan caching. */ final CQueryPlanKey createQueryPlanKey() { if (isNativeSql()) { String bindHash = (bindParams == null) ? "" : bindParams.calcQueryPlanHash(); queryPlanKey = new NativeSqlQueryPlanKey(type.ordinal() + nativeSql + "-" + firstRow + "-" + maxRows + "-" + bindHash); } else { queryPlanKey = new OrmQueryPlanKey(planDescription(), maxRows, firstRow, rawSql); } return queryPlanKey; } private String planDescription() { StringBuilder sb = new StringBuilder(300); if (type != null) { sb.append(type.ordinal()); } if (temporalMode != SpiQuery.TemporalMode.CURRENT) { sb.append("/tm").append(temporalMode.ordinal()); if (versionsStart != null) { sb.append('v'); } } if (forUpdate != null) { sb.append("/fu").append(forUpdate.ordinal()); if (lockType != null) { sb.append('t').append(lockType.ordinal()); } } if (id != null) { sb.append("/id"); } if (manualId) { sb.append("/md"); } if (hint != null) { sb.append("/h:").append(hint); } if (distinct) { sb.append("/dt"); if (distinctOn != null) { sb.append("/o:").append(distinctOn); } } if (allowLoadErrors) { sb.append("/ae"); } if (disableLazyLoading) { sb.append("/dl"); } if (baseTable != null) { sb.append("/bt").append(baseTable); } if (rootTableAlias != null) { sb.append("/ra").append(rootTableAlias); } if (orderBy != null) { sb.append("/ob").append(orderBy.toStringFormat()); } if (m2mIncludeJoin != null) { sb.append("/m2").append(m2mIncludeJoin.getTable()); } if (mapKey != null) { sb.append("/mk").append(mapKey); } if (countDistinctOrder != null) { sb.append("/cd").append(countDistinctOrder.name()); } if (detail != null) { sb.append("/d["); detail.queryPlanHash(sb); sb.append(']'); } if (bindParams != null) { sb.append("/b["); bindParams.buildQueryPlanHash(sb); sb.append(']'); } if (whereExpressions != null) { sb.append("/w["); whereExpressions.queryPlanHash(sb); sb.append(']'); } if (havingExpressions != null) { sb.append("/h["); havingExpressions.queryPlanHash(sb); sb.append(']'); } if (updateProperties != null) { sb.append("/u["); updateProperties.buildQueryPlanHash(sb); sb.append(']'); } return sb.toString(); } @Override public final boolean isNativeSql() { return nativeSql != null; } @Override public final String nativeSql() { return nativeSql; } @Override public final Object queryPlanKey() { return queryPlanKey; } /** * Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key. */ @Override public final CQueryPlanKey prepare(SpiOrmQueryRequest request) { prepareExpressions(request); prepareForPaging(); queryPlanKey = createQueryPlanKey(); return queryPlanKey; } /** * Prepare the expressions (compile sub-queries etc). */ private void prepareExpressions(BeanQueryRequest request) { detail.prepareExpressions(request); if (whereExpressions != null) { whereExpressions.prepareExpression(request); } if (havingExpressions != null) { havingExpressions.prepareExpression(request); } } /** * deemed to be a be a paging query - check that the order by contains the id * property to ensure unique row ordering for predicable paging but only in * case, this is not a distinct query */ private void prepareForPaging() { // add the rawSql statement - if any if (orderByIsEmpty()) { if (rawSql != null && rawSql.getSql() != null) { orderBy(rawSql.getSql().getOrderBy()); } } if (checkPagingOrderBy()) { beanDescriptor.appendOrderById(this); } } @Override public final void queryBindKey(BindValuesKey key) { key.add(id); if (whereExpressions != null) whereExpressions.queryBindKey(key); if (havingExpressions != null) havingExpressions.queryBindKey(key); if (bindParams != null) bindParams.queryBindHash(key); key.add(asOf).add(versionsStart).add(versionsEnd); } /** * Return a hash that includes the query plan and bind values. *

* This hash can be used to identify if we have executed the exact same query (including bind * values) before. *

*/ @Override public final HashQuery queryHash() { // calculateQueryPlanHash is called just after potential AutoTune tuning // so queryPlanHash is calculated well before this method is called BindValuesKey bindKey = new BindValuesKey(); queryBindKey(bindKey); return new HashQuery(queryPlanKey, bindKey); } @Override public final boolean isRawSql() { return rawSql != null; } /** * Return the timeout. */ @Override public final int timeout() { return timeout; } @Override public final boolean hasMaxRowsOrFirstRow() { return maxRows > 0 || firstRow > 0; } @Override public final boolean isVersionsBetween() { return versionsStart != null; } @Override public final Timestamp versionStart() { return versionsStart; } @Override public final Timestamp versionEnd() { return versionsEnd; } @Override public final Boolean isReadOnly() { return readOnly; } @Override public final Query setReadOnly(boolean readOnly) { this.readOnly = readOnly; return this; } @Override public final boolean isBeanCachePut() { return useBeanCache.isPut() && beanDescriptor.isBeanCaching(); } @Override public final boolean isBeanCacheGet() { return useBeanCache.isGet() && beanDescriptor.isBeanCaching(); } @Override public final boolean isForceHitDatabase() { return forUpdate != null || CacheMode.PUT == useBeanCache; } @Override public final void resetBeanCacheAutoMode(boolean findOne) { if (useBeanCache == CacheMode.AUTO && useQueryCache != CacheMode.OFF) { useBeanCache = CacheMode.OFF; } } @Override public final CacheMode beanCacheMode() { return useBeanCache; } @Override public final CacheMode queryCacheMode() { return useQueryCache; } @Override public final Query setBeanCacheMode(CacheMode beanCacheMode) { this.useBeanCache = beanCacheMode; return this; } @Override public final Query setUseQueryCache(CacheMode useQueryCache) { this.useQueryCache = useQueryCache; return this; } @Override public final Query setTimeout(int secs) { this.timeout = secs; return this; } @Override public final void selectProperties(Set props) { detail.selectProperties(props); } @Override public final void fetchProperties(String property, Set columns, FetchConfig config) { detail.fetchProperties(property, columns, config); } @Override public final void selectProperties(OrmQueryProperties properties) { detail.selectProperties(properties); } @Override public final void fetchProperties(String path, OrmQueryProperties other) { detail.fetchProperties(path, other); } @Override public final void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config) { detail.addNested(name, nestedDetail, config); } @Override public final Query distinctOn(String distinctOn) { this.distinctOn = distinctOn; this.distinct = true; return this; } @Override public final Query select(String columns) { detail.select(columns); return this; } @Override public final Query select(FetchGroup fetchGroup) { if (fetchGroup != null) { this.detail = ((SpiFetchGroup) fetchGroup).detail(); } return this; } @Override public final Query fetch(String path) { return fetch(path, null, null); } @Override public final Query fetch(String path, FetchConfig joinConfig) { return fetch(path, null, joinConfig); } @Override public final Query fetch(String path, String properties) { return fetch(path, properties, null); } @Override public final Query fetch(String path, String properties, FetchConfig config) { if (nativeSql != null && (config == null || config.isJoin())) { // can't use fetch join with nativeSql (as the root query) config = FETCH_QUERY; } return fetchInternal(path, properties, config); } @Override public final Query fetchQuery(String path) { return fetchInternal(path, null, FETCH_QUERY); } @Override public final Query fetchCache(String path) { return fetchInternal(path, null, FETCH_CACHE); } @Override public final Query fetchLazy(String path) { return fetchInternal(path, null, FETCH_LAZY); } @Override public final Query fetchQuery(String path, String properties) { return fetchInternal(path, properties, FETCH_QUERY); } @Override public final Query fetchCache(String path, String properties) { return fetchInternal(path, properties, FETCH_CACHE); } @Override public final Query fetchLazy(String path, String properties) { return fetchInternal(path, properties, FETCH_LAZY); } private Query fetchInternal(String path, String properties, FetchConfig config) { detail.fetch(path, properties, config); return this; } @Override public SpiTransaction transaction() { return transaction; } @Override public final Query usingTransaction(Transaction transaction) { this.transaction = (SpiTransaction) transaction; return this; } @Override public final Query usingConnection(Connection connection) { this.transaction = new ExternalJdbcTransaction(connection); return this; } @Override public final Query usingDatabase(Database database) { this.server = (SpiEbeanServer) database; return this; } @Override public Query usingMaster() { this.useMaster = true; return this; } @Override public boolean isUseMaster() { return useMaster; } @Override public final int delete() { return server.delete(this); } @Override public final int delete(Transaction transaction) { return server.delete(this); } @Override public final int update() { return server.update(this); } @Override public final int update(Transaction transaction) { return server.update(this); } @Override public final List findIds() { // a copy of this query is made in the server // as the query needs to modified (so we modify // the copy rather than this query instance) return server.findIds(this); } @Override public final boolean exists() { return server.exists(this); } @Override public final int findCount() { // a copy of this query is made in the server // as the query needs to modified (so we modify // the copy rather than this query instance) return server.findCount(this); } @Override public final void findEachWhile(Predicate consumer) { server.findEachWhile(this, consumer); } @Override public final void findEach(Consumer consumer) { server.findEach(this, consumer); } @Override public final void findEach(int batch, Consumer> consumer) { server.findEach(this, batch, consumer); } @Override public final QueryIterator findIterate() { return server.findIterate(this); } @Override public final Stream findStream() { return server.findStream(this); } @Override public final List> findVersions() { this.temporalMode = TemporalMode.VERSIONS; return server.findVersions(this); } @Override public final List> findVersionsBetween(Timestamp start, Timestamp end) { if (start == null || end == null) { throw new IllegalArgumentException("start and end must not be null"); } this.temporalMode = TemporalMode.VERSIONS; this.versionsStart = start; this.versionsEnd = end; return server.findVersions(this); } @Override public final List findList() { return server.findList(this); } @Override public final Set findSet() { return server.findSet(this); } @Override public final Map findMap() { return server.findMap(this); } @Override public final List findSingleAttributeList() { return server.findSingleAttributeList(this); } @Override public final Set findSingleAttributeSet() { return server.findSingleAttributeSet(this); } @Override public final A findSingleAttribute() { List list = findSingleAttributeList(); return !list.isEmpty() ? list.get(0) : null; } @Override public final Optional findSingleAttributeOrEmpty() { return Optional.ofNullable(findSingleAttribute()); } @Override public final T findOne() { return server.findOne(this); } @Override public final Optional findOneOrEmpty() { return server.findOneOrEmpty(this); } @Override public final FutureIds findFutureIds() { return server.findFutureIds(this); } @Override public final FutureList findFutureList() { return server.findFutureList(this); } @Override public final FutureRowCount findFutureCount() { return server.findFutureCount(this); } @Override public final PagedList findPagedList() { return server.findPagedList(this); } @Override public final Query setParameter(Object value) { initBindParams().setNextParameter(value); return this; } @Override public final Query setParameters(Object... values) { initBindParams().setNextParameters(values); return this; } /** * Set an ordered bind parameter according to its position. Note that the position starts at 1 to * be consistent with JDBC PreparedStatement. You need to set a parameter value for each ? you * have in the query. */ @Override public final Query setParameter(int position, Object value) { initBindParams().setParameter(position, value); return this; } /** * Set a named bind parameter. Named parameters have a colon to prefix the name. */ @Override public final Query setParameter(String name, Object value) { if (namedParams != null) { ONamedParam param = namedParams.get(name); if (param != null) { param.setValue(value); return this; } } initBindParams().setParameter(name, value); return this; } @Override public final void setArrayParameter(String name, Collection values) { if (namedParams != null) { throw new IllegalStateException("setArrayParameter() not supported when EQL parsed query"); } initBindParams().setArrayParameter(name, values); } @Override public final boolean checkPagingOrderBy() { return orderById; } @Override public final boolean orderByIsEmpty() { return orderBy == null || orderBy.isEmpty(); } @Override public final OrderBy getOrderBy() { return orderBy; } @Override public final OrderBy orderBy() { if (orderBy == null) { orderBy = new OrderBy<>(this, null); } return orderBy; } @Override public final Query orderBy(String orderByClause) { if (orderByClause == null || orderByClause.trim().isEmpty()) { this.orderBy = null; } else { this.orderBy = new OrderBy<>(this, orderByClause); } return this; } @Override public final Query setOrderBy(OrderBy orderBy) { this.orderBy = orderBy; if (orderBy != null) { orderBy.setQuery(this); } return this; } @Override public final boolean isManualId() { return manualId; } @Override public final void setManualId() { if (detail != null && detail.hasSelectClause()) { this.manualId = true; } } /** * return true if user specified to use SQL DISTINCT (effectively excludes id property). */ @Override public final boolean isDistinct() { return distinct; } /** * Internally set to use SQL DISTINCT on the query but still have id property included. */ @Override public final Query setDistinct(boolean distinct) { this.distinct = distinct; return this; } @Override public final Query setCountDistinct(CountDistinctOrder countDistinctOrder) { this.countDistinctOrder = countDistinctOrder; return this; } @Override public final boolean isCountDistinct() { return countDistinctOrder != null; } @Override public final Class getBeanType() { return beanType; } @Override public final String toString() { return "Query " + whereExpressions; } @Override public final TableJoin m2mIncludeJoin() { return m2mIncludeJoin; } @Override public final void setM2MIncludeJoin(TableJoin m2mIncludeJoin) { this.m2mIncludeJoin = m2mIncludeJoin; } @Override public final Query setDisableLazyLoading(boolean disableLazyLoading) { this.disableLazyLoading = disableLazyLoading; return this; } @Override public final boolean isDisableLazyLoading() { return disableLazyLoading; } @Override public final int getFirstRow() { return firstRow; } @Override public final Query setFirstRow(int firstRow) { this.firstRow = firstRow; return this; } @Override public final int getMaxRows() { return maxRows; } @Override public final Query setMaxRows(int maxRows) { this.maxRows = maxRows; return this; } @Override @SuppressWarnings("unchecked") public Query setPaging(@Nullable Paging paging) { if (paging != null && paging.pageSize() > 0) { firstRow = paging.pageIndex() * paging.pageSize(); maxRows = paging.pageSize(); orderBy = (OrderBy) paging.orderBy(); if (orderBy == null || orderBy.isEmpty()) { // should not be paging without any order by clause so set // orderById such that the Id property is used orderById = true; } } return this; } @Override public final String mapKey() { return mapKey; } @Override public final Query setMapKey(String mapKey) { this.mapKey = mapKey; return this; } @Override public final Object getId() { return id; } @Override public final Query setId(Object id) { if (id == null) { throw new NullPointerException("The id is null"); } this.id = id; return this; } @Override public final BindParams bindParams() { return bindParams; } @Override public BindParams initBindParams() { if (bindParams == null) { bindParams = new BindParams(); } return bindParams; } @Override public final Query where(Expression expression) { where().add(expression); return this; } @Override public final ExpressionList where() { if (whereExpressions == null) { whereExpressions = new DefaultExpressionList<>(this, null); } return whereExpressions; } @Override public final void simplifyExpressions() { if (whereExpressions != null) { whereExpressions.simplify(); } } @Override public final Query having(Expression expression) { having().add(expression); return this; } @Override public final ExpressionList having() { if (havingExpressions == null) { havingExpressions = new DefaultExpressionList<>(this, null); } return havingExpressions; } @Override public final SpiExpressionList havingExpressions() { return havingExpressions; } @Override public final SpiExpressionList whereExpressions() { return whereExpressions; } @Override public final String getGeneratedSql() { return generatedSql; } @Override public final void setGeneratedSql(String generatedSql) { this.generatedSql = generatedSql; } @Override public final void checkNamedParameters() { if (namedParams != null) { for (ONamedParam value : namedParams.values()) { value.checkValueSet(); } } } @Override public final SpiNamedParam createNamedParameter(String name) { if (namedParams == null) { namedParams = new HashMap<>(); } return namedParams.computeIfAbsent(name, ONamedParam::new); } @Override public final void setDefaultFetchBuffer(int fetchSize) { if (bufferFetchSizeHint == 0) { bufferFetchSizeHint = fetchSize; } } @Override public final Query setBufferFetchSizeHint(int bufferFetchSizeHint) { this.bufferFetchSizeHint = bufferFetchSizeHint; return this; } @Override public final int bufferFetchSizeHint() { return bufferFetchSizeHint; } @Override public final Query setBaseTable(String baseTable) { this.baseTable = baseTable; return this; } @Override public final String baseTable() { return baseTable; } @Override public final Query alias(String alias) { this.rootTableAlias = alias; return this; } @Override public final String alias() { return rootTableAlias; } @Override public final String getAlias(String defaultAlias) { return rootTableAlias != null ? rootTableAlias : defaultAlias; } @Override public final Set validate() { return server.validateQuery(this); } /** * Validate all the expression properties/paths given the bean descriptor. */ @Override public final Set validate(BeanType desc) { SpiExpressionValidation validation = new SpiExpressionValidation(desc); if (whereExpressions != null) { whereExpressions.validate(validation); } if (havingExpressions != null) { havingExpressions.validate(validation); } if (orderBy != null) { for (OrderBy.Property property : orderBy.getProperties()) { validation.validate(property.getProperty()); } } return validation.unknownProperties(); } final void setUpdateProperties(OrmUpdateProperties updateProperties) { this.updateProperties = updateProperties; } @Override public final OrmUpdateProperties updateProperties() { return updateProperties; } @Override public final ProfileLocation profileLocation() { return profileLocation; } @Override public final void handleLoadError(String fullName, Exception e) { if (!allowLoadErrors) { throw new PersistenceException("Error loading on " + fullName, e); } } @Override public final Query orderById(boolean orderById) { this.orderById = orderById; return this; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy