001package io.ebeaninternal.server.autotune.service;
002
003import io.ebean.bean.CallOrigin;
004import io.ebean.bean.ObjectGraphNode;
005import io.ebean.config.AutoTuneConfig;
006import io.ebean.config.AutoTuneMode;
007import io.ebeaninternal.api.SpiEbeanServer;
008import io.ebeaninternal.api.SpiQuery;
009import io.ebeaninternal.server.autotune.ProfilingListener;
010import io.ebeaninternal.server.autotune.model.Origin;
011import io.ebeaninternal.server.querydefn.OrmQueryDetail;
012
013import javax.persistence.PersistenceException;
014import java.util.Collection;
015import java.util.Map;
016import java.util.Set;
017import java.util.concurrent.ConcurrentHashMap;
018
019/**
020 *
021 */
022public class BaseQueryTuner {
023
024  private final boolean queryTuning;
025
026  private final boolean profiling;
027
028  private final AutoTuneMode mode;
029
030  /**
031   * Map of the tuned query details per profile query point.
032   */
033  private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<>();
034
035  private final SpiEbeanServer server;
036
037  private final ProfilingListener profilingListener;
038
039  /**
040   * Flag set true when there is no profiling or query tuning.
041   */
042  private final boolean skipAll;
043
044  BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) {
045    this.server = server;
046    this.profilingListener = profilingListener;
047    this.mode = config.getMode();
048    this.queryTuning = config.isQueryTuning();
049    this.profiling = config.isProfiling();
050    this.skipAll = !queryTuning && !profiling;
051  }
052
053  /**
054   * Return all the current tuned query entries.
055   */
056  public Collection<TunedQueryInfo> getAll() {
057    return tunedQueryInfoMap.values();
058  }
059
060  /**
061   * Put a query tuning entry.
062   */
063  public void put(Origin origin) {
064
065    tunedQueryInfoMap.put(origin.getKey(), new TunedQueryInfo(origin));
066  }
067
068  /**
069   * Load the tuned query information.
070   */
071  public void load(String key, TunedQueryInfo queryInfo) {
072    tunedQueryInfoMap.put(key, queryInfo);
073  }
074
075  /**
076   * Return the detail currently used for tuning.
077   * This returns null if there is currently no matching tuning.
078   */
079  public OrmQueryDetail get(String key) {
080    TunedQueryInfo info = tunedQueryInfoMap.get(key);
081    return (info == null) ? null : info.getTunedDetail();
082  }
083
084  /**
085   * Auto tune the query and enable profiling.
086   */
087  boolean tuneQuery(SpiQuery<?> query) {
088
089    if (skipAll || !tunableQuery(query)) {
090      return false;
091    }
092
093    if (query.getProfilingListener() != null) {
094      // profiling secondary query
095      return false;
096    }
097
098    if (!useTuning(query)) {
099      if (profiling) {
100        profiling(query, server.createCallOrigin());
101      }
102      return false;
103    }
104
105    if (query.getParentNode() != null) {
106      // This is a +lazy/+query query with profiling on.
107      // We continue to collect the profiling information.
108      query.setProfilingListener(profilingListener);
109      return true;
110    }
111
112    // create a query point to identify the query
113    CallOrigin callOrigin = server.createCallOrigin();
114    ObjectGraphNode origin = query.setOrigin(callOrigin);
115
116    if (profiling) {
117      if (profilingListener.isProfileRequest(origin, query)) {
118        // collect more profiling based on profiling rate etc
119        query.setProfilingListener(profilingListener);
120      }
121    }
122
123    if (queryTuning) {
124      // get current "tuned fetch" for this query point
125      TunedQueryInfo tuneInfo = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey());
126      return tuneInfo != null && tuneInfo.tuneQuery(query);
127    }
128    return false;
129  }
130
131  /**
132   * Return false for row count, find ids, subQuery, delete and Versions queries.
133   * <p>
134   * These queries are not applicable for autoTune in that they don't have a select/fetch (fetch group).
135   * </p>
136   * <p>
137   * We also exclude queries that are explicitly set to load the L2 bean cache as we want full beans
138   * in that case.
139   * </p>
140   */
141  private boolean tunableQuery(SpiQuery<?> query) {
142    SpiQuery.Type type = query.getType();
143    switch (type) {
144      case COUNT:
145      case ATTRIBUTE:
146      case ID_LIST:
147      case UPDATE:
148      case DELETE:
149      case SUBQUERY:
150        return false;
151      default:
152        // not using autoTune when explicitly loading the l2 bean cache
153        // or when using Versions query
154        return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
155    }
156  }
157
158  private void profiling(SpiQuery<?> query, CallOrigin call) {
159
160    // create a query point to identify the query
161    ObjectGraphNode origin = query.setOrigin(call);
162    if (profilingListener.isProfileRequest(origin, query)) {
163      // collect more profiling based on profiling rate etc
164      query.setProfilingListener(profilingListener);
165    }
166  }
167
168  /**
169   * Return true if we should try to tune this query.
170   */
171  private boolean useTuning(SpiQuery<?> query) {
172
173    Boolean autoTune = query.isAutoTune();
174    if (autoTune != null) {
175      // explicitly set...
176      return autoTune;
177
178    } else {
179      // determine using implicit mode...
180      switch (mode) {
181        case DEFAULT_ON:
182          return true;
183
184        case DEFAULT_OFF:
185          return false;
186
187        case DEFAULT_ONIFEMPTY:
188          return query.isDetailEmpty();
189
190        default:
191          throw new PersistenceException("Invalid AutoTuneMode " + mode);
192      }
193    }
194  }
195
196  /**
197   * Return the keys as a set.
198   */
199  public Set<String> keySet() {
200    return tunedQueryInfoMap.keySet();
201  }
202}