Merge pull request #1899 from ebean-orm/feature/queryPlanCapture

Refactor for query plan capture with initial threshold micros
This commit is contained in:
Rob Bygrave
2019-12-24 00:54:37 +13:00
committed by GitHub
32 changed files with 730 additions and 252 deletions
@@ -492,6 +492,11 @@ public class ServerConfig {
*/
private boolean collectQueryPlans;
/**
* The default threshold in micros for collecting query plans.
*/
private long collectQueryPlanThresholdMicros = Long.MAX_VALUE;
/**
* The time in millis used to determine when a query is alerted for being slow.
*/
@@ -2852,6 +2857,7 @@ public class ServerConfig {
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
collectQueryPlanThresholdMicros = p.getLong("collectQueryPlanThresholdMicros", collectQueryPlanThresholdMicros);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
@@ -3237,6 +3243,20 @@ public class ServerConfig {
this.collectQueryPlans = collectQueryPlans;
}
/**
* Return the query plan collection threshold in microseconds.
*/
public long getCollectQueryPlanThresholdMicros() {
return collectQueryPlanThresholdMicros;
}
/**
* Set the query plan collection threshold in microseconds.
*/
public void setCollectQueryPlanThresholdMicros(long collectQueryPlanThresholdMicros) {
this.collectQueryPlanThresholdMicros = collectQueryPlanThresholdMicros;
}
/**
* Return true if metrics should be dumped when the server is shutdown.
*/
@@ -33,11 +33,6 @@ public interface MetaInfoManager {
*/
List<MetricData> collectMetricsAsData();
/**
* Collect query plans.
*/
List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request);
/**
* Visit the metrics resetting and collecting/reporting as desired.
*/
@@ -54,4 +49,21 @@ public interface MetaInfoManager {
*/
void resetAllMetrics();
/**
* Initiate query plan collection by turning on "bind capture" on matching query plans.
* <p>
* Also refer to ServerConfig collectQueryPlans that needs to be set to true
* and collectQueryPlanThresholdMicros which is a global defaults that can also
* initiate query plan capture.
*
* @return The query plans that have had bind capture turned on by this request.
*/
List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest);
/**
* Collect query plans in the foreground.
*/
List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request);
}
@@ -0,0 +1,67 @@
package io.ebean.meta;
import java.util.HashSet;
import java.util.Set;
/**
* Initiate query plan collection for plans by their hash or all query plans.
*/
public class QueryPlanInit {
private boolean all;
private Set<String> hashes = new HashSet<>();
private long thresholdMicros;
/**
* Return true if this initiates bind collection on all query plans.
*/
public boolean isAll() {
return all;
}
/**
* Set to true to initiate bind collection on all query plans.
*/
public void setAll(boolean all) {
this.all = all;
}
/**
* Return the query execution time threshold which must be exceeded to initiate
* query plan collection.
*/
public long getThresholdMicros() {
return thresholdMicros;
}
/**
* Set the query execution time threshold which must be exceeded to initiate
* query plan collection.
*/
public void setThresholdMicros(long thresholdMicros) {
this.thresholdMicros = thresholdMicros;
}
/**
* Return true if the query plan should be initiated based on it's hash.
*/
public boolean includeHash(String hash) {
return all || hashes.contains(hash);
}
/**
* Return the specific hashes that we want to collect query plans on.
*/
public Set<String> getHashes() {
return hashes;
}
/**
* Set the specific hashes that we want to collect query plans on.
*/
public void setHashes(Set<String> hashes) {
this.hashes = hashes;
}
}
@@ -1,97 +1,66 @@
package io.ebean.meta;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Request used to capture query plans.
*/
public class QueryPlanRequest {
private final List<MetaQueryPlan> plans = new ArrayList<>();
private Connection connection;
private boolean store;
private long since;
private Set<Class<?>> includedBeanTypes;
private int maxCount;
private Set<String> includedLabels;
public List<MetaQueryPlan> getPlans() {
return plans;
}
private long maxTimeMillis;
/**
* Return the connection to use to capture the query plans.
*/
public Connection getConnection() {
return connection;
}
/**
* Set the connection to use to capture the query plans.
*/
public void setConnection(Connection connection) {
this.connection = connection;
}
/**
* Return true if the captured query plan is stored.
*/
public boolean isStore() {
return store;
}
/**
* Set to true to store the captured query plan.
*/
public void setStore(boolean store) {
this.store = store;
}
/**
* Return the epoch time after which the query plan was capture (to be included).
* Return the epoch time in millis for minimum bind capture time.
* <p>
* When set this ensures that the bind values used to get the query plan
* have been around for a while (e.g. 5 mins) and so reasonably represent
* bind values that match the slowest execution for this query plan.
*/
public long getSince() {
return since;
}
/**
* Set the epoch time after which the query plan was captured.
* <p>
* This is used to only capture plans that have changed since a given time (like the time of last capture).
* </p>
* Set the epoch time (e.g. 5 mins ago) such that the query bind values
* reasonably represent bind values that match the slowest execution for this query plan.
*
* @param since The time after which the query plan was captured to be included
* @param since The minimum age of the bind values capture.
*/
public void setSince(long since) {
this.since = since;
}
/**
* Process consume the query plan.
* Return the maximum number of plans to capture.
*/
public void process(MetaQueryPlan plan) {
plans.add(plan);
public int getMaxCount() {
return maxCount;
}
/**
* Return true if the bean type should be included in the query plan capture.
* Set the maximum number of plans to capture.
*/
public boolean includeType(Class<?> beanType) {
return includedBeanTypes == null || includedBeanTypes.isEmpty() || includedBeanTypes.contains(beanType);
public void setMaxCount(int maxCount) {
this.maxCount = maxCount;
}
/**
* Return true if the label should be included in the query plan capture.
* Return the maximum amount of time we want to use to capture plans.
* <p>
* Query plan collection will stop once this time is exceeded.
*/
public boolean includeLabel(String label) {
return includedLabels == null || includedLabels.isEmpty() || includedLabels.contains(label);
public long getMaxTimeMillis() {
return maxTimeMillis;
}
/**
* Set the maximum amount of time we want to use to capture plans.
* <p>
* Query plan collection will stop once this time is exceeded.
*/
public void setMaxTimeMillis(long maxTimeMillis) {
this.maxTimeMillis = maxTimeMillis;
}
}
@@ -0,0 +1,46 @@
package io.ebeaninternal.api;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
/**
* Extra metrics collected to measure internal behaviour.
*/
public class ExtraMetrics {
private final TimedMetric bindCapture;
private final TimedMetric planCollect;
/**
* Create the extra metrics.
*/
public ExtraMetrics() {
final MetricFactory factory = MetricFactory.get();
this.bindCapture = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.bindcapture");
this.planCollect = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.collect");
}
/**
* Timed metric for bind capture used with query plan collection.
*/
public TimedMetric getBindCapture() {
return bindCapture;
}
/**
* Timed metric for query plan collection.
*/
public TimedMetric getPlanCollect() {
return planCollect;
}
/**
* Collect the metrics.
*/
public void visitMetrics(MetricVisitor visitor) {
bindCapture.visit(visitor);
planCollect.visit(visitor);
}
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
class NoopQueryBindCapture implements SpiQueryBindCapture {
@Override
public boolean collectFor(long timeMicros) {
return false;
}
@Override
public void setBind(BindCapture bindCapture, long timeMicros, long startNanos) {
// do nothing
}
@Override
public void queryPlanInit(long thresholdMicros) {
// do nothing
}
}
@@ -0,0 +1,20 @@
package io.ebeaninternal.api;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.QueryPlanRequest;
import java.util.Collections;
import java.util.List;
class NoopQueryPlanManager implements QueryPlanManager {
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return SpiQueryBindCapture.NOOP;
}
@Override
public List<MetaQueryPlan> collect(QueryPlanRequest request) {
return Collections.emptyList();
}
}
@@ -0,0 +1,24 @@
package io.ebeaninternal.api;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.QueryPlanRequest;
import java.util.List;
/**
* Manage query plan capture.
*/
public interface QueryPlanManager {
QueryPlanManager NOOP = new NoopQueryPlanManager();
/**
* Create the bind capture for the given query plan.
*/
SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan);
/**
* Collect the database query plans.
*/
List<MetaQueryPlan> collect(QueryPlanRequest request);
}
@@ -0,0 +1,15 @@
package io.ebeaninternal.api;
import io.ebean.meta.MetaQueryPlan;
/**
* Internal database query plan being capture.
*/
public interface SpiDbQueryPlan extends MetaQueryPlan {
/**
* Extend with queryTimeMicros and captureCount.
*/
SpiDbQueryPlan with(long queryTimeMicros, long captureCount);
}
@@ -12,7 +12,6 @@ import io.ebean.TxScope;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.BeanLoader;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.readaudit.ReadAuditLogger;
@@ -311,4 +310,9 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader,
* Execute the sql update regardless of transaction batch mode.
*/
int executeNow(SpiSqlUpdate sqlUpdate);
/**
* Create a query bind capture for the given query plan.
*/
SpiQueryBindCapture createQueryBindCapture(SpiQueryPlan queryPlan);
}
@@ -0,0 +1,35 @@
package io.ebeaninternal.api;
import io.ebean.meta.QueryPlanRequest;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
/**
* Capture query bind values and with those actual database query plans.
*/
public interface SpiQueryBindCapture {
/**
* NOOP implementation.
*/
SpiQueryBindCapture NOOP = new NoopQueryBindCapture();
/**
* Return true if the query just executed should be bind captured to collect
* the query plan from (as the query time is large / interesting).
*/
boolean collectFor(long timeMicros);
/**
* Set the captured bind values that we can use later to collect a query plan.
*
* @param bindCapture The bind values of the query
* @param queryTimeMicros The query execution time
* @param startNanos The nanos start of this bind capture
*/
void setBind(BindCapture bindCapture, long queryTimeMicros, long startNanos);
/**
* Update the threshold micros triggering the bind capture.
*/
void queryPlanInit(long thresholdMicros);
}
@@ -0,0 +1,48 @@
package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
/**
* The internal ORM "query plan".
*/
public interface SpiQueryPlan {
/**
* The related entity bean type
*/
Class<?> getBeanType();
/**
* The plan name.
*/
String getName();
/**
* The hash for the query plan.
*/
String getHash();
/**
* The SQL for the query plan.
*/
String getSql();
/**
* The related profile location.
*/
ProfileLocation getProfileLocation();
/**
* Initiate bind capture with the give threshold.
*/
void queryPlanInit(long thresholdMicros);
/**
* Return as Database query plan.
*
* @param bind Description of the bind values used
* @param planString The raw database query plan
*/
SpiDbQueryPlan createMeta(String bind, String planString);
}
@@ -9,6 +9,7 @@ import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricData;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.meta.ServerMetrics;
import io.ebean.meta.ServerMetricsAsJson;
@@ -27,8 +28,13 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
}
@Override
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
return server.collectQueryPlans(request);
public List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
return server.queryPlanInit(initRequest);
}
@Override
public List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) {
return server.queryPlanCollectNow(request);
}
@Override
@@ -38,7 +38,6 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.cache.ServerCacheManager;
@@ -56,6 +55,7 @@ import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
@@ -63,9 +63,10 @@ import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.ExtraMetrics;
import io.ebeaninternal.api.LoadBeanRequest;
import io.ebeaninternal.api.LoadManyRequest;
import io.ebeaninternal.api.QueryPlanManager;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
@@ -74,6 +75,8 @@ import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
@@ -138,7 +141,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -162,6 +164,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final TransactionManager transactionManager;
private final QueryPlanManager queryPlanManager;
private final ExtraMetrics extraMetrics;
private final DataTimeZone dataTimeZone;
/**
@@ -248,15 +254,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Create the DefaultServer.
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.logManager = config.getLogManager();
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.metaInfoManager = new DefaultMetaInfoManager(this);
this.serverCacheManager = cache;
this.databasePlatform = config.getDatabasePlatform();
this.backgroundExecutor = config.getBackgroundExecutor();
this.extraMetrics = config.getExtraMetrics();
this.serverName = serverConfig.getName();
this.lazyLoadBatchSize = serverConfig.getLazyLoadBatchSize();
this.queryBatchSize = serverConfig.getQueryBatchSize();
@@ -267,10 +271,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.currentTenantProvider = serverConfig.getCurrentTenantProvider();
this.slowQueryMicros = config.getSlowQueryMicros();
this.slowQueryListener = config.getSlowQueryListener();
this.beanDescriptorManager = config.getBeanDescriptorManager();
beanDescriptorManager.setEbeanServer(this);
this.updateAllPropertiesInBatch = serverConfig.isUpdateAllPropertiesInBatch();
this.callStackFactory = initCallStackFactory(serverConfig);
@@ -291,6 +293,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager.getDataSource());
this.metaInfoManager = new DefaultMetaInfoManager(this);
this.serverPlugins = config.getPlugins();
this.ddlGenerator = new DdlGenerator(this, serverConfig);
@@ -2385,7 +2389,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return Collections.emptySet();
}
/**
* Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties).
*/
@@ -2430,21 +2433,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
relationalQueryEngine.visitMetrics(visitor);
persister.visitMetrics(visitor);
}
extraMetrics.visitMetrics(visitor);
visitor.visitEnd();
}
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
Connection connection = null;
try {
connection = getDataSource().getConnection();
request.setConnection(connection);
beanDescriptorManager.collectQueryPlans(request);
} catch (SQLException e) {
throw new RuntimeException(e);
@Override
public SpiQueryBindCapture createQueryBindCapture(SpiQueryPlan plan) {
return queryPlanManager.createBindCapture(plan);
}
} finally {
JdbcClose.close(connection);
}
return request.getPlans();
List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
return beanDescriptorManager.queryPlanInit(initRequest);
}
List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) {
return queryPlanManager.collect(request);
}
}
@@ -23,6 +23,8 @@ import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.ExtraMetrics;
import io.ebeaninternal.api.QueryPlanManager;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
@@ -64,9 +66,15 @@ import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.CQueryEngine;
import io.ebeaninternal.server.query.CQueryPlanManager;
import io.ebeaninternal.server.query.DefaultOrmQueryEngine;
import io.ebeaninternal.server.query.DefaultRelationalQueryEngine;
import io.ebeaninternal.server.query.DtoQueryEngine;
import io.ebeaninternal.server.query.QueryPlanLogger;
import io.ebeaninternal.server.query.QueryPlanLoggerExplain;
import io.ebeaninternal.server.query.QueryPlanLoggerOracle;
import io.ebeaninternal.server.query.QueryPlanLoggerPostgres;
import io.ebeaninternal.server.query.QueryPlanLoggerSqlServer;
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import io.ebeaninternal.server.text.json.DJsonContext;
@@ -164,8 +172,10 @@ public class InternalConfiguration {
private final SpiLogManager logManager;
private final ExtraMetrics extraMetrics = new ExtraMetrics();
InternalConfiguration(boolean online, ClusterManager clusterManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.online = online;
this.serverConfig = serverConfig;
@@ -242,6 +252,10 @@ public class InternalConfiguration {
return clockService;
}
public ExtraMetrics getExtraMetrics() {
return extraMetrics;
}
/**
* Check if this is a SpiServerPlugin and if so 'collect' it to give the complete list
* later on the DefaultServer for late call to configure().
@@ -433,8 +447,8 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -527,7 +541,7 @@ public class InternalConfiguration {
}
private boolean isMySql(Platform platform) {
return platform.base() == Platform.MYSQL;
return platform.base() == Platform.MYSQL;
}
public DataTimeZone getDataTimeZone() {
@@ -636,4 +650,28 @@ public class InternalConfiguration {
return new DefaultServerCacheManager(builder);
}
public QueryPlanManager initQueryPlanManager(DataSource dataSource) {
if (!serverConfig.isCollectQueryPlans()) {
return QueryPlanManager.NOOP;
}
long threshold = serverConfig.getCollectQueryPlanThresholdMicros();
return new CQueryPlanManager(dataSource, threshold, queryPlanLogger(databasePlatform.getPlatform()), extraMetrics);
}
/**
* Returns the logger to log query plans for the given platform.
*/
QueryPlanLogger queryPlanLogger(Platform platform) {
switch (platform.base()) {
case POSTGRES:
return new QueryPlanLoggerPostgres();
case SQLSERVER:
return new QueryPlanLoggerSqlServer();
case ORACLE:
return new QueryPlanLoggerOracle();
default:
return new QueryPlanLoggerExplain();
}
}
}
@@ -27,8 +27,9 @@ import io.ebean.event.changelog.ChangeType;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.meta.QueryPlanInit;
import io.ebean.plugin.BeanDocType;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
@@ -1682,10 +1683,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return new DeployUpdateParser(this).parse(ormUpdateStatement);
}
void collectQueryPlans(QueryPlanRequest request) {
void queryPlanInit(QueryPlanInit request, List<MetaQueryPlan> list) {
for (CQueryPlan queryPlan : queryPlanCache.values()) {
if (request.includeLabel(queryPlan.getLabel())) {
queryPlan.collectQueryPlan(request);
if (request.includeHash(queryPlan.getHash())) {
queryPlan.queryPlanInit(request.getThresholdMicros());
list.add(queryPlan.createMeta(null, null));
}
}
}
@@ -1714,19 +1716,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Trim query plans not used since the passed in epoch time.
*/
List<CQueryPlan> trimQueryPlans(long unusedSince) {
List<CQueryPlan> list = new ArrayList<>();
void trimQueryPlans(long unusedSince) {
Iterator<CQueryPlan> it = queryPlanCache.values().iterator();
while (it.hasNext()) {
CQueryPlan queryPlan = it.next();
if (queryPlan.getLastQueryTime() < unusedSince) {
it.remove();
list.add(queryPlan);
}
}
return list;
}
/**
@@ -20,8 +20,9 @@ import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.meta.QueryPlanInit;
import io.ebean.plugin.BeanType;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ConcurrencyMode;
@@ -55,7 +56,6 @@ import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeInteger;
import io.ebeaninternal.server.type.TypeManager;
@@ -244,17 +244,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* Run periodic trim of query plans.
*/
public void scheduleBackgroundTrim() {
backgroundExecutor.executePeriodically(this::trimQueryPlans, 30L, TimeUnit.SECONDS);
backgroundExecutor.executePeriodically(this::trimQueryPlans, 60L, TimeUnit.SECONDS);
}
private void trimQueryPlans() {
long lastUsed = System.currentTimeMillis() - (queryPlanTTLSeconds * 1000L);
for (BeanDescriptor<?> descriptor : immutableDescriptorList) {
if (!descriptor.isEmbedded()) {
List<CQueryPlan> trimmedPlans = descriptor.trimQueryPlans(lastUsed);
if (!trimmedPlans.isEmpty()) {
logger.trace("trimmed {} query plans for type:{}", trimmedPlans.size(), descriptor.getName());
}
descriptor.trimQueryPlans(lastUsed);
}
}
}
@@ -1696,12 +1693,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
public void collectQueryPlans(QueryPlanRequest request) {
public List<MetaQueryPlan> queryPlanInit(QueryPlanInit request) {
List<MetaQueryPlan> list = new ArrayList<>();
for (BeanDescriptor<?> desc : immutableDescriptorList) {
if (request.includeType(desc.getBeanType())) {
desc.collectQueryPlans(request);
}
desc.queryPlanInit(request, list);
}
return list;
}
/**
@@ -1713,7 +1710,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
@Override
public int compare(BeanDescriptor<?> o1, BeanDescriptor<?> o2) {
return o1.getName().compareTo(o2.getName());
}
}
@@ -1,14 +1,16 @@
package io.ebeaninternal.server.query;
import io.ebean.meta.QueryPlanRequest;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
class CQueryBindCapture {
class CQueryBindCapture implements SpiQueryBindCapture {
private static final double multiplier = 1.3d;
private final CQueryPlan cQueryPlan;
private final QueryPlanLogger planLogger;
private final CQueryPlanManager manager;
private final SpiQueryPlan queryPlan;
private BindCapture bindCapture;
private long queryTimeMicros;
@@ -17,52 +19,58 @@ class CQueryBindCapture {
private long lastBindCapture;
CQueryBindCapture(CQueryPlan cQueryPlan, QueryPlanLogger planLogger) {
this.cQueryPlan = cQueryPlan;
this.planLogger = planLogger;
CQueryBindCapture(CQueryPlanManager manager, SpiQueryPlan queryPlan, long thresholdMicros) {
this.manager = manager;
this.queryPlan = queryPlan;
this.thresholdMicros = thresholdMicros;
}
/**
* Return true if we should capture the bind values for this query.
*/
boolean collectFor(long timeMicros) {
return (bindCapture == null || timeMicros > thresholdMicros);
@Override
public boolean collectFor(long timeMicros) {
return timeMicros > thresholdMicros && captureCount < 10;
}
/**
* Set the captured bind values that we can use later to collect a query plan.
*
* @param bindCapture The bind values of the query
* @param queryTimeMicros The query execution time
*/
void setBind(BindCapture bindCapture, long queryTimeMicros) {
@Override
public void setBind(BindCapture bindCapture, long queryTimeMicros, long startNanos) {
synchronized (this) {
this.thresholdMicros = Math.round(queryTimeMicros * multiplier);
this.captureCount++;
this.bindCapture = bindCapture;
this.queryTimeMicros = queryTimeMicros;
this.thresholdMicros = Math.round(queryTimeMicros * multiplier);
captureCount++;
lastBindCapture = System.currentTimeMillis();
manager.notifyBindCapture(this, startNanos);
}
}
@Override
public void queryPlanInit(long thresholdMicros) {
// effective enable bind capture for this plan
this.thresholdMicros = thresholdMicros;
this.captureCount = 0;
}
/**
* Collect the query plan using already captured bind values.
*/
void collectQueryPlan(QueryPlanRequest request) {
if (bindCapture == null || request.getSince() > lastBindCapture) {
public boolean collectQueryPlan(CQueryPlanRequest request) {
if (bindCapture == null || request.getSince() < lastBindCapture) {
// no bind capture since the last capture
return;
return false;
}
final BindCapture last = this.bindCapture;
DQueryPlanOutput queryPlan = planLogger.logQueryPlan(request.getConnection(), cQueryPlan, last);
SpiDbQueryPlan queryPlan = manager.collectPlan(request.getConnection(), this.queryPlan, last);
if (queryPlan != null) {
queryPlan.with(queryTimeMicros, captureCount, cQueryPlan.getHash());
request.process(queryPlan);
request.add(queryPlan.with(queryTimeMicros, captureCount));
// effectively turn off bind capture for this plan
thresholdMicros = Long.MAX_VALUE;
return true;
}
return false;
}
}
@@ -1,15 +1,15 @@
package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.SqlLimitResponse;
import io.ebean.meta.MetricType;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
@@ -47,7 +47,7 @@ import java.util.Set;
* for performance tuning.
* </p>
*/
public class CQueryPlan {
public class CQueryPlan implements SpiQueryPlan {
private static final Logger logger = LoggerFactory.getLogger(CQueryPlan.class);
@@ -55,8 +55,6 @@ public class CQueryPlan {
private final SpiEbeanServer server;
private final boolean autoTuned;
private final ProfileLocation profileLocation;
private final String location;
@@ -98,13 +96,12 @@ public class CQueryPlan {
private final Set<String> dependentTables;
private final CQueryBindCapture bindCapture;
private final SpiQueryBindCapture bindCapture;
/**
* Create a query plan based on a OrmQueryRequest.
*/
CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) {
this.server = request.getServer();
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
@@ -114,7 +111,6 @@ public class CQueryPlan {
this.label = query.getPlanLabel();
this.name = deriveName(label, query.getType());
this.location = location();
this.autoTuned = query.isAutoTuned();
this.asOfTableCount = query.getAsOfTableCount();
this.sql = sqlRes.getSql();
this.rowNumberIncluded = sqlRes.isIncludesRowNumberColumn();
@@ -124,7 +120,7 @@ public class CQueryPlan {
this.encryptedProps = sqlTree.getEncryptedProps();
this.stats = new CQueryPlanStats(this);
this.dependentTables = sqlTree.dependentTables();
this.bindCapture = initBindCapture(server.getServerConfig(), query);
this.bindCapture = initBindCapture(query);
this.hash = md5Hash();
}
@@ -132,7 +128,6 @@ public class CQueryPlan {
* Create a query plan for a raw sql query.
*/
CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTree sqlTree, boolean rowNumberIncluded, String logWhereSql) {
this.server = request.getServer();
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
@@ -142,7 +137,6 @@ public class CQueryPlan {
this.name = deriveName(label, query.getType());
this.location = location();
this.planKey = buildPlanKey(sql, rowNumberIncluded, logWhereSql);
this.autoTuned = false;
this.asOfTableCount = 0;
this.sql = sql;
this.sqlTree = sqlTree;
@@ -152,7 +146,7 @@ public class CQueryPlan {
this.encryptedProps = sqlTree.getEncryptedProps();
this.stats = new CQueryPlanStats(this);
this.dependentTables = sqlTree.dependentTables();
this.bindCapture = initBindCapture(server.getServerConfig(), query);
this.bindCapture = initBindCaptureRaw(sql);
this.hash = md5Hash();
}
@@ -166,12 +160,12 @@ public class CQueryPlan {
return "orm." + beanType.getSimpleName() + "_" + label;
}
private CQueryBindCapture initBindCapture(ServerConfig serverConfig, SpiQuery<?> query) {
if (serverConfig.isCollectQueryPlans() && !query.getType().isUpdate()) {
return new CQueryBindCapture(this, PlatformQueryPlan.getLogger(serverConfig.getDatabasePlatform().getPlatform()));
} else {
return null;
}
private SpiQueryBindCapture initBindCapture(SpiQuery<?> query) {
return query.getType().isUpdate() ? SpiQueryBindCapture.NOOP : server.createQueryBindCapture(this);
}
private SpiQueryBindCapture initBindCaptureRaw(String sql) {
return sql.equals(RESULT_SET_BASED_RAW_SQL) ? SpiQueryBindCapture.NOOP : server.createQueryBindCapture(this);
}
private String location() {
@@ -187,14 +181,27 @@ public class CQueryPlan {
return beanType + " hash:" + planKey;
}
@Override
public Class<?> getBeanType() {
return beanType;
}
public Set<String> getDependentTables() {
return dependentTables;
@Override
public String getName() {
return name;
}
@Override
public String getHash() {
return hash;
}
@Override
public String getSql() {
return sql;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
@@ -203,14 +210,24 @@ public class CQueryPlan {
return label;
}
public String getName() {
return name;
public Set<String> getDependentTables() {
return dependentTables;
}
public String getLocation() {
return location;
}
@Override
public void queryPlanInit(long thresholdMicros) {
bindCapture.queryPlanInit(thresholdMicros);
}
@Override
public DQueryPlanOutput createMeta(String bind, String planString) {
return new DQueryPlanOutput(getBeanType(), name, hash, sql, profileLocation, bind, planString);
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(dataTimeZone, rset);
}
@@ -242,10 +259,6 @@ public class CQueryPlan {
return asOfTableCount;
}
boolean isAutoTuned() {
return autoTuned;
}
/**
* Return a key used in audit logging to identify the query.
*/
@@ -277,14 +290,6 @@ public class CQueryPlan {
}
}
String getHash() {
return hash;
}
public String getSql() {
return sql;
}
SqlTree getSqlTree() {
return sqlTree;
}
@@ -346,20 +351,13 @@ public class CQueryPlan {
}
void captureBindForQueryPlan(CQueryPredicates predicates, long executionTimeMicros) {
final long startNanos = System.nanoTime();
try {
DataBindCapture capture = bindCapture();
predicates.bind(capture);
bindCapture.setBind(capture.bindCapture(), executionTimeMicros);
bindCapture.setBind(capture.bindCapture(), executionTimeMicros, startNanos);
} catch (SQLException e) {
logger.error("Error capturing bind values", e);
}
}
public void collectQueryPlan(QueryPlanRequest request) {
if (!getSql().equals(RESULT_SET_BASED_RAW_SQL) && bindCapture != null) {
bindCapture.collectQueryPlan(request);
}
}
}
@@ -0,0 +1,89 @@
package io.ebeaninternal.server.query;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.metric.TimedMetric;
import io.ebeaninternal.api.ExtraMetrics;
import io.ebeaninternal.api.QueryPlanManager;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import static java.util.Collections.emptyList;
public class CQueryPlanManager implements QueryPlanManager {
private static final Logger log = LoggerFactory.getLogger(CQueryPlanManager.class);
private static final Object dummy = new Object();
private final ConcurrentHashMap<CQueryBindCapture, Object> plans = new ConcurrentHashMap<>();
private final DataSource dataSource;
private final long defaultThreshold;
private final QueryPlanLogger planLogger;
private final TimedMetric timeCollection;
private final TimedMetric timeBindCapture;
public CQueryPlanManager(DataSource dataSource, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) {
this.dataSource = dataSource;
this.defaultThreshold = defaultThreshold;
this.planLogger = planLogger;
this.timeCollection = extraMetrics.getPlanCollect();
this.timeBindCapture = extraMetrics.getBindCapture();
}
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return new CQueryBindCapture(this, queryPlan, defaultThreshold);
}
public void notifyBindCapture(CQueryBindCapture planBind, long startNanos) {
plans.put(planBind, dummy);
timeBindCapture.addSinceNanos(startNanos);
}
@Override
public List<MetaQueryPlan> collect(QueryPlanRequest request) {
if (plans.isEmpty()) {
return emptyList();
}
long startNanos = System.nanoTime();
try {
return collectPlans(request);
} finally {
timeCollection.addSinceNanos(startNanos);
}
}
private List<MetaQueryPlan> collectPlans(QueryPlanRequest request) {
try (Connection connection = dataSource.getConnection()) {
CQueryPlanRequest req = new CQueryPlanRequest(connection, request, plans.keySet().iterator());
while (req.hasNext()) {
req.nextCapture();
}
return req.getPlans();
} catch (SQLException e) {
log.error("Error during query plan collection", e);
return emptyList();
}
}
public SpiDbQueryPlan collectPlan(Connection connection, SpiQueryPlan queryPlan, BindCapture last) {
return planLogger.collectPlan(connection, queryPlan, last);
}
}
@@ -0,0 +1,86 @@
package io.ebeaninternal.server.query;
import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.QueryPlanRequest;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Captures database query plans.
*/
class CQueryPlanRequest {
private final List<MetaQueryPlan> plans = new ArrayList<>();
private final Connection connection;
private final long since;
private final int maxCount;
private final long maxTime;
private Iterator<CQueryBindCapture> iterator;
CQueryPlanRequest(Connection connection, QueryPlanRequest req, Iterator<CQueryBindCapture> iterator) {
this.connection = connection;
this.iterator = iterator;
this.maxCount = req.getMaxCount();
long reqSince = req.getSince();
this.since = (reqSince == 0) ? Long.MAX_VALUE: reqSince;
long maxTimeMillis = req.getMaxTimeMillis();
this.maxTime = maxTimeMillis > 0 ? System.currentTimeMillis() + maxTimeMillis : 0;
}
/**
* Return the connection used to collect the db query plan.
*/
Connection getConnection() {
return connection;
}
/**
* Add the collected query plan.
*/
void add(MetaQueryPlan dbQueryPlan) {
plans.add(dbQueryPlan);
}
/**
* Return the min epoch time in millis for minimum bind capture age.
*/
long getSince() {
return since;
}
/**
* Return the captured query plans.
*/
List<MetaQueryPlan> getPlans() {
return plans;
}
/**
* Return true to continue database query plan capture.
*/
boolean hasNext() {
return moreByCount() && moreByTime() && iterator.hasNext();
}
/**
* Capture the next database query plan.
*/
void nextCapture() {
final CQueryBindCapture next = iterator.next();
if (next.collectQueryPlan(this)) {
iterator.remove();
}
}
private boolean moreByCount() {
return maxCount == 0 || maxCount > plans.size();
}
private boolean moreByTime() {
return maxTime == 0 || maxTime > System.currentTimeMillis();
}
}
@@ -2,11 +2,12 @@ package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetaQueryPlan;
import io.ebeaninternal.api.SpiDbQueryPlan;
/**
* Captured query plan details.
*/
class DQueryPlanOutput implements MetaQueryPlan {
class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
private final Class<?> beanType;
private final String label;
@@ -20,13 +21,14 @@ class DQueryPlanOutput implements MetaQueryPlan {
private long queryTimeMicros;
private long captureCount;
DQueryPlanOutput(Class<?> beanType, String label, String sql, String bind, String plan, ProfileLocation profileLocation) {
DQueryPlanOutput(Class<?> beanType, String label, String hash, String sql, ProfileLocation profileLocation, String bind, String plan) {
this.beanType = beanType;
this.label = label;
this.hash = hash;
this.sql = sql;
this.profileLocation = profileLocation;
this.bind = bind;
this.plan = plan;
this.profileLocation = profileLocation;
}
@Override
@@ -104,9 +106,10 @@ class DQueryPlanOutput implements MetaQueryPlan {
/**
* Additionally set the query execution time and the number of bind captures.
*/
void with(long queryTimeMicros, long captureCount, String hash) {
@Override
public DQueryPlanOutput with(long queryTimeMicros, long captureCount) {
this.queryTimeMicros = queryTimeMicros;
this.captureCount = captureCount;
this.hash = hash;
return this;
}
}
@@ -1,36 +0,0 @@
package io.ebeaninternal.server.query;
import io.ebean.annotation.Platform;
final class PlatformQueryPlan {
private static final QueryPlanLogger explainLogger = new QueryPlanLoggerExplain();
private static final QueryPlanLogger postgresLogger = new QueryPlanLoggerPostgres();
private static final QueryPlanLogger sqlServerLogger = new QueryPlanLoggerSqlServer();
private static final QueryPlanLogger oracleLogger = new QueryPlanLoggerOracle();
/**
* Returns the logger to log query plans for the given platform.
*/
public static QueryPlanLogger getLogger(Platform platform) {
switch (platform) {
case POSTGRES:
return postgresLogger;
case SQLSERVER:
case SQLSERVER16:
case SQLSERVER17:
return sqlServerLogger;
case ORACLE:
return oracleLogger;
default:
return explainLogger;
}
}
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,9 +14,9 @@ public abstract class QueryPlanLogger {
static final Logger queryPlanLog = LoggerFactory.getLogger(QueryPlanLogger.class);
public abstract DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind);
abstract SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind);
DQueryPlanOutput readQueryPlan(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
SpiDbQueryPlan readQueryPlan(SpiQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
sb.append(rset.getMetaData().getColumnLabel(i)).append("\t");
@@ -25,11 +27,11 @@ public abstract class QueryPlanLogger {
return createPlan(plan, bind.toString(), sb.toString());
}
DQueryPlanOutput createPlan(CQueryPlan plan, String bind, String planString) {
return new DQueryPlanOutput(plan.getBeanType(), plan.getName(), plan.getSql(), bind, planString, plan.getProfileLocation());
SpiDbQueryPlan createPlan(SpiQueryPlan plan, String bind, String planString) {
return plan.createMeta(bind, planString);
}
DQueryPlanOutput readQueryPlanBasic(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
SpiDbQueryPlan readQueryPlanBasic(SpiQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
StringBuilder sb = new StringBuilder();
readPlanData(sb, rset);
return createPlan(plan, bind.toString(), sb.toString().trim());
@@ -41,7 +43,7 @@ public abstract class QueryPlanLogger {
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
sb.append(rset.getString(i)).append("\t");
}
sb.setLength(sb.length()-1);
sb.setLength(sb.length() - 1);
}
}
}
@@ -1,30 +1,30 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
/**
* A QueryPlanLogger that prefixes "EXPLAIN " to the query. This works for Postgres, H2 and MySql.
*/
public class QueryPlanLoggerExplain extends QueryPlanLogger {
@Override
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN " + plan.getSql())) {
bind.prepare(explainStmt, conn);
try (ResultSet rset = explainStmt.executeQuery()) {
return readQueryPlan(plan, bind, rset);
}
} catch (SQLException e) {
queryPlanLog.error("Could not log query plan", e);
return null;
}
return null;
}
}
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
import java.sql.Connection;
@@ -17,8 +19,7 @@ import java.sql.Statement;
public class QueryPlanLoggerOracle extends QueryPlanLogger {
@Override
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
try (Statement stmt = conn.createStatement()) {
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN PLAN FOR " + plan.getSql())) {
bind.prepare(explainStmt, conn);
@@ -27,11 +28,10 @@ public class QueryPlanLoggerOracle extends QueryPlanLogger {
try (ResultSet rset = stmt.executeQuery("select plan_table_output from table(dbms_xplan.display())")) {
return readQueryPlan(plan, bind, rset);
}
} catch (SQLException e) {
queryPlanLog.error("Could not log query plan", e);
return null;
}
return null;
}
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
import java.sql.Connection;
@@ -13,19 +15,16 @@ import java.sql.SQLException;
public class QueryPlanLoggerPostgres extends QueryPlanLogger {
@Override
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
String explain = "explain analyze " + plan.getSql();
try (PreparedStatement explainStmt = conn.prepareStatement(explain)) {
bind.prepare(explainStmt, conn);
try (ResultSet rset = explainStmt.executeQuery()) {
return readQueryPlanBasic(plan, bind, rset);
}
} catch (SQLException e) {
queryPlanLog.error("Could not log query plan: " + explain, e);
throw new IllegalStateException("Failed to obtain explain plan: " + explain, e);
return null;
}
}
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiDbQueryPlan;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.type.bindcapture.BindCapture;
import java.sql.Connection;
@@ -17,8 +19,7 @@ import java.sql.Statement;
public class QueryPlanLoggerSqlServer extends QueryPlanLogger {
@Override
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
try (Statement stmt = conn.createStatement()) {
stmt.execute("set statistics xml on");
stmt.execute("begin transaction");
@@ -41,14 +42,14 @@ public class QueryPlanLoggerSqlServer extends QueryPlanLogger {
} catch (SQLException e) {
queryPlanLog.error("Could not log query plan", e);
return null;
} finally {
stmt.execute("set statistics xml off");
}
} catch (SQLException e) {
queryPlanLog.error("Could not log query plan", e);
return null;
}
return null;
}
}