mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
1476 - Query plan capture, initial wip capturing bind params (#1477)
* 1476 - Query plan capture, initial wip capturing bind params * No effective change - Reset test default db to H2 * #1476 - Query plan capture, initial wip capturing bind params #1477 Update with consumer * #1476 - Query plan capture, initial wip capturing bind params #1477 Update with query plan request * #1476 - Query plan capture, initial wip capturing bind params #1477 Extract interface * #1476 - Query plan capture, initial wip capturing bind params #1477 Extract interface
This commit is contained in:
@@ -486,6 +486,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean notifyL2CacheInForeground;
|
||||
|
||||
/**
|
||||
* Set to true to support query plan capture.
|
||||
*/
|
||||
private boolean collectQueryPlans;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
*/
|
||||
@@ -2832,6 +2837,7 @@ public class ServerConfig {
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
|
||||
@@ -3197,6 +3203,20 @@ public class ServerConfig {
|
||||
this.idGeneratorAutomatic = idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if query plan capture is enabled.
|
||||
*/
|
||||
public boolean isCollectQueryPlans() {
|
||||
return collectQueryPlans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to enable query plan capture.
|
||||
*/
|
||||
public void setCollectQueryPlans(boolean collectQueryPlans) {
|
||||
this.collectQueryPlans = collectQueryPlans;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Provides access to the meta data in EbeanServer such as query execution statistics.
|
||||
*/
|
||||
public interface MetaInfoManager {
|
||||
|
||||
/**
|
||||
* Collect query plans.
|
||||
*/
|
||||
List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request);
|
||||
|
||||
/**
|
||||
* Visit the metrics resetting and collecting/reporting as desired.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
public interface MetaQueryPlan {
|
||||
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
String getLabel();
|
||||
|
||||
String getSql();
|
||||
|
||||
String getBind();
|
||||
|
||||
String getPlan();
|
||||
|
||||
long getQueryTimeMicros();
|
||||
|
||||
long getCaptureCount();
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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 List<MetaQueryPlan> plans = new ArrayList<>();
|
||||
|
||||
private Connection connection;
|
||||
|
||||
private boolean store;
|
||||
|
||||
private long since;
|
||||
|
||||
private Set<Class<?>> includedBeanTypes;
|
||||
|
||||
private Set<String> includedLabels;
|
||||
|
||||
public List<MetaQueryPlan> getPlans() {
|
||||
return plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
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>
|
||||
*
|
||||
* @param since The time after which the query plan was captured to be included
|
||||
*/
|
||||
public void setSince(long since) {
|
||||
this.since = since;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process consume the query plan.
|
||||
*/
|
||||
public void process(MetaQueryPlan plan) {
|
||||
plans.add(plan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean type should be included in the query plan capture.
|
||||
*/
|
||||
public boolean includeType(Class<?> beanType) {
|
||||
return includedBeanTypes == null || includedBeanTypes.isEmpty() || includedBeanTypes.contains(beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the label should be included in the query plan capture.
|
||||
*/
|
||||
public boolean includeLabel(String label) {
|
||||
return includedLabels == null || includedLabels.isEmpty() || includedLabels.contains(label);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
|
||||
import io.ebean.Pairs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HanaColumnStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaColumnStoreDdl(DatabasePlatform platform) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.config.PropertiesWrapper;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -12,6 +9,9 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class HanaTableDdl extends BaseTableDdl {
|
||||
|
||||
private final HanaHistoryDdl historyDdl;
|
||||
|
||||
@@ -20,7 +20,6 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.IdentityType;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -78,7 +77,7 @@ public class PlatformDdl {
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
|
||||
protected String alterColumn = "alter column";
|
||||
|
||||
|
||||
protected String alterColumnSuffix = "";
|
||||
|
||||
protected String dropUniqueConstraint = "drop constraint";
|
||||
@@ -86,7 +85,7 @@ public class PlatformDdl {
|
||||
protected String addConstraint = "add constraint";
|
||||
|
||||
protected String addColumn = "add column";
|
||||
|
||||
|
||||
protected String addColumnSuffix = "";
|
||||
|
||||
protected String columnSetType = "";
|
||||
@@ -100,11 +99,11 @@ public class PlatformDdl {
|
||||
protected String columnSetNull = "set null";
|
||||
|
||||
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
|
||||
|
||||
|
||||
protected String createTable = "create table";
|
||||
|
||||
|
||||
protected String dropColumn = "drop column";
|
||||
|
||||
|
||||
protected String dropColumnSuffix = "";
|
||||
|
||||
/**
|
||||
@@ -674,12 +673,12 @@ public class PlatformDdl {
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the database-specific "create table" command prefix. For HANA this is
|
||||
* either "create column table" or "create row table", for all other databases
|
||||
* it is "create table".
|
||||
*
|
||||
*
|
||||
* @return The "create table" command prefix
|
||||
*/
|
||||
public String getCreateTableCommandPrefix() {
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
@@ -6,8 +6,10 @@ import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaOrmQueryMetric;
|
||||
import io.ebean.meta.MetaOrmQueryNode;
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,6 +25,11 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
|
||||
return server.collectQueryPlans(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
server.visitMetrics(visitor);
|
||||
|
||||
@@ -54,13 +54,16 @@ import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
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.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.Plugin;
|
||||
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.LoadBeanRequest;
|
||||
import io.ebeaninternal.api.LoadManyRequest;
|
||||
import io.ebeaninternal.api.ScopedTransaction;
|
||||
@@ -120,6 +123,8 @@ import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -2335,10 +2340,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public void slowQueryCheck(long timeMicros, int rowCount, SpiQuery<?> query) {
|
||||
if (timeMicros > slowQueryMicros) {
|
||||
if (slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
if (timeMicros > slowQueryMicros && slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2425,4 +2428,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
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);
|
||||
|
||||
} finally {
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
return request.getPlans();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.SlowQueryListener;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbHistorySupport;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
@@ -87,7 +88,6 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
|
||||
import io.ebeanservice.docstore.api.DocStoreIntegration;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
|
||||
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantCatalogProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantSchemaProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.Formula;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
@@ -30,6 +29,7 @@ import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanDocType;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.ExpressionPath;
|
||||
@@ -1652,6 +1652,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return new DeployUpdateParser(this).parse(ormUpdateStatement);
|
||||
}
|
||||
|
||||
public void collectQueryPlans(QueryPlanRequest request) {
|
||||
for (CQueryPlan queryPlan : queryPlanCache.values()) {
|
||||
if (request.includeLabel(queryPlan.getLabel())) {
|
||||
queryPlan.collectQueryPlan(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the ORM query plan metrics (includes UpdateQuery with updates and deletes).
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
import io.ebeaninternal.api.ConcurrencyMode;
|
||||
@@ -1688,6 +1689,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
}
|
||||
}
|
||||
|
||||
public void collectQueryPlans(QueryPlanRequest request) {
|
||||
for (BeanDescriptor<?> desc : immutableDescriptorList) {
|
||||
if (request.includeType(desc.getBeanType())) {
|
||||
desc.collectQueryPlans(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator to sort the BeanDescriptors by name.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.ebeaninternal.server.el;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Comparator based on a ElGetValue.
|
||||
*/
|
||||
|
||||
@@ -3,10 +3,10 @@ package io.ebeaninternal.server.el;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.StringParser;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.MultiValueWrapper;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebean.util.SplitName;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Generated from C:/dev/ebean/ebean/src/test/resources/EQL.g4 by ANTLR 4.7.1
|
||||
package io.ebeaninternal.server.grammer.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.RuntimeMetaData;
|
||||
import org.antlr.v4.runtime.Vocabulary;
|
||||
import org.antlr.v4.runtime.VocabularyImpl;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.LexerATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
|
||||
@SuppressWarnings({ "all", "warnings", "unchecked", "unused", "cast" })
|
||||
public class EQLLexer extends Lexer {
|
||||
@@ -292,4 +294,4 @@ public class EQLLexer extends Lexer {
|
||||
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
// Generated from C:/dev/ebean/ebean/src/test/resources/EQL.g4 by ANTLR 4.7.1
|
||||
package io.ebeaninternal.server.grammer.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.NoViableAltException;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.RuntimeMetaData;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.Vocabulary;
|
||||
import org.antlr.v4.runtime.VocabularyImpl;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.ParserATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
import org.antlr.v4.runtime.tree.*;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeListener;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressWarnings({ "all", "warnings", "unchecked", "unused", "cast" })
|
||||
public class EQLParser extends Parser {
|
||||
@@ -2822,4 +2832,4 @@ public class EQLParser extends Parser {
|
||||
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +602,9 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
|
||||
if (autoTuneProfiling) {
|
||||
profilingListener.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros);
|
||||
}
|
||||
queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode);
|
||||
if (queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode)) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error updating execution statistics", e);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
class CQueryBindCapture {
|
||||
|
||||
private final double multiplier = 1.3d;
|
||||
|
||||
private final CQueryPlan cQueryPlan;
|
||||
private final QueryPlanLogger planLogger;
|
||||
private final boolean enabled;
|
||||
|
||||
private BindCapture bindCapture;
|
||||
private long queryTimeMicros;
|
||||
private long thresholdMicros;
|
||||
private long captureCount;
|
||||
|
||||
private long lastBindCapture;
|
||||
|
||||
|
||||
CQueryBindCapture(CQueryPlan cQueryPlan, ServerConfig serverConfig) {
|
||||
this.cQueryPlan = cQueryPlan;
|
||||
this.enabled = serverConfig.isCollectQueryPlans();
|
||||
this.planLogger = PlatformQueryPlan.getLogger(serverConfig.getDatabasePlatform().getPlatform());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should capture the bind values for this query.
|
||||
*/
|
||||
boolean collectFor(long timeMicros) {
|
||||
return enabled && (bindCapture == null || timeMicros > thresholdMicros);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
synchronized (this) {
|
||||
this.bindCapture = bindCapture;
|
||||
this.queryTimeMicros = queryTimeMicros;
|
||||
this.thresholdMicros = Math.round(queryTimeMicros * multiplier);
|
||||
captureCount++;
|
||||
lastBindCapture = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collect the query plan using already captured bind values.
|
||||
*/
|
||||
void collectQueryPlan(QueryPlanRequest request) {
|
||||
|
||||
if (request.getSince() > lastBindCapture) {
|
||||
// no bind capture since the last capture
|
||||
return;
|
||||
}
|
||||
|
||||
final BindCapture last = this.bindCapture;
|
||||
if (last == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DQueryPlanOutput queryPlan = planLogger.logQueryPlan(request.getConnection(), cQueryPlan, last);
|
||||
queryPlan.with(queryTimeMicros, captureCount, cQueryPlan.getPlanKey().toString());
|
||||
request.process(queryPlan);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class CQueryBuilderRawSql {
|
||||
|
||||
if (rsql == null) {
|
||||
// this is a ResultSet based RawSql query - just use some placeholder for the SQL
|
||||
return new SqlLimitResponse("--ResultSetBasedRawSql", false);
|
||||
return new SqlLimitResponse(CQueryPlan.RESULT_SET_BASED_RAW_SQL, false);
|
||||
}
|
||||
|
||||
if (!rsql.isParsed()) {
|
||||
|
||||
@@ -117,7 +117,9 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, null);
|
||||
if (queryPlan.executionTime(rowCount, executionTimeMicros, null)) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.ebean.ProfileLocation;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
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;
|
||||
@@ -13,6 +14,7 @@ import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.DataBindCapture;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
import io.ebeaninternal.server.type.RsetDataReader;
|
||||
import io.ebeaninternal.server.type.ScalarDataReader;
|
||||
@@ -50,6 +52,8 @@ public class CQueryPlan {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CQueryPlan.class);
|
||||
|
||||
public static final String RESULT_SET_BASED_RAW_SQL = "--ResultSetBasedRawSql";
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final boolean autoTuned;
|
||||
@@ -92,6 +96,8 @@ public class CQueryPlan {
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
private final CQueryBindCapture bindCapture;
|
||||
|
||||
/**
|
||||
* Create a query plan based on a OrmQueryRequest.
|
||||
*/
|
||||
@@ -115,6 +121,7 @@ public class CQueryPlan {
|
||||
this.encryptedProps = sqlTree.getEncryptedProps();
|
||||
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
|
||||
this.dependentTables = sqlTree.dependentTables();
|
||||
this.bindCapture = new CQueryBindCapture(this, server.getServerConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +147,7 @@ public class CQueryPlan {
|
||||
this.encryptedProps = sqlTree.getEncryptedProps();
|
||||
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
|
||||
this.dependentTables = (rawSql) ? Collections.emptySet() : sqlTree.dependentTables();
|
||||
this.bindCapture = new CQueryBindCapture(this, server.getServerConfig());
|
||||
}
|
||||
|
||||
private String location() {
|
||||
@@ -193,6 +201,16 @@ public class CQueryPlan {
|
||||
return dataBind;
|
||||
}
|
||||
|
||||
private DataBindCapture bindCapture() throws SQLException {
|
||||
DataBindCapture dataBind = DataBindCapture.of(dataTimeZone);
|
||||
if (encryptedProps != null) {
|
||||
for (STreeProperty encryptedProp : encryptedProps) {
|
||||
dataBind.setString(encryptedProp.getEncryptKeyAsString());
|
||||
}
|
||||
}
|
||||
return dataBind;
|
||||
}
|
||||
|
||||
int getAsOfTableCount() {
|
||||
return asOfTableCount;
|
||||
}
|
||||
@@ -263,13 +281,15 @@ public class CQueryPlan {
|
||||
/**
|
||||
* Register an execution time against this query plan;
|
||||
*/
|
||||
void executionTime(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
|
||||
boolean executionTime(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
|
||||
|
||||
stats.add(loadedBeanCount, timeMicros, objectGraphNode);
|
||||
if (objectGraphNode != null) {
|
||||
// collect stats based on objectGraphNode for lazy loading reporting
|
||||
server.collectQueryStats(objectGraphNode, loadedBeanCount, timeMicros);
|
||||
}
|
||||
|
||||
return bindCapture.collectFor(timeMicros);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,4 +320,22 @@ public class CQueryPlan {
|
||||
public TimedMetric createTimedMetric() {
|
||||
return MetricFactory.get().createTimedMetric(MetricType.ORM, label);
|
||||
}
|
||||
|
||||
void captureBindForQueryPlan(CQueryPredicates predicates, long executionTimeMicros) {
|
||||
try {
|
||||
DataBindCapture capture = bindCapture();
|
||||
predicates.bind(capture);
|
||||
bindCapture.setBind(capture.bindCapture(), executionTimeMicros);
|
||||
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error capturing bind values", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void collectQueryPlan(QueryPlanRequest request) {
|
||||
|
||||
if (!getSql().equals(RESULT_SET_BASED_RAW_SQL)) {
|
||||
bindCapture.collectQueryPlan(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +125,10 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
|
||||
rowCount = rset.getInt(1);
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode());
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
if (queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode())) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
return rowCount;
|
||||
|
||||
|
||||
@@ -111,7 +111,9 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, null);
|
||||
if (queryPlan.executionTime(rowCount, executionTimeMicros, null)) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
return rowCount;
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
|
||||
/**
|
||||
* Captured query plan details.
|
||||
*/
|
||||
class DQueryPlanOutput implements MetaQueryPlan {
|
||||
|
||||
private final Class<?> beanType;
|
||||
private final String label;
|
||||
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final String bind;
|
||||
|
||||
private final String plan;
|
||||
|
||||
private String planHash;
|
||||
private long queryTimeMicros;
|
||||
private long captureCount;
|
||||
|
||||
DQueryPlanOutput(Class<?> beanType, String label, String sql, String bind, String plan) {
|
||||
this.beanType = beanType;
|
||||
this.label = label;
|
||||
this.sql = sql;
|
||||
this.bind = bind;
|
||||
this.plan = plan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryPlanHash() {
|
||||
return planHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated bean.
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getBeanType() {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query label if set.
|
||||
*/
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sql of query.
|
||||
*/
|
||||
@Override
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description of the bind values used.
|
||||
*/
|
||||
@Override
|
||||
public String getBind() {
|
||||
return bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan.
|
||||
*/
|
||||
@Override
|
||||
public String getPlan() {
|
||||
return plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query execution time associated with the capture of bind values used
|
||||
* to build the query plan.
|
||||
*/
|
||||
@Override
|
||||
public long getQueryTimeMicros() {
|
||||
return queryTimeMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total count of times bind capture has occurred. We don't want this to be
|
||||
* massive as that implies a high overhead.
|
||||
*/
|
||||
@Override
|
||||
public long getCaptureCount() {
|
||||
return captureCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return " BeanType:" + ((beanType == null) ? "" : beanType.getSimpleName()) + " planHash:" + planHash + " label:" + label + " queryTimeMicros:" + queryTimeMicros + " captureCount:" + captureCount + "\n SQL:" + sql + "\nBIND:" + bind + "\nPLAN:" + plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additionally set the query execution time and the number of bind captures.
|
||||
*/
|
||||
void with(long queryTimeMicros, long captureCount, String planHash) {
|
||||
this.queryTimeMicros = queryTimeMicros;
|
||||
this.captureCount = captureCount;
|
||||
this.planHash = planHash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
public final class PlatformQueryPlan {
|
||||
|
||||
private static QueryPlanLogger explainLogger = new QueryPlanLoggerExplain();
|
||||
|
||||
private static QueryPlanLogger postgresLogger = new QueryPlanLoggerPostgres();
|
||||
|
||||
private static QueryPlanLogger sqlServerLogger = new QueryPlanLoggerSqlServer();
|
||||
|
||||
private static 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public abstract class QueryPlanLogger {
|
||||
|
||||
static final Logger queryPlanLog = LoggerFactory.getLogger(QueryPlanLogger.class);
|
||||
|
||||
public abstract DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind);
|
||||
|
||||
DQueryPlanOutput readQueryPlan(CQueryPlan 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");
|
||||
}
|
||||
sb.setLength(sb.length() - 1);
|
||||
readPlanData(sb, rset);
|
||||
|
||||
return createPlan(plan, bind.toString(), sb.toString());
|
||||
}
|
||||
|
||||
protected DQueryPlanOutput createPlan(CQueryPlan plan, String bind, String planString) {
|
||||
return new DQueryPlanOutput(plan.getBeanType(), plan.getLabel(), plan.getSql(), bind, planString);
|
||||
}
|
||||
|
||||
DQueryPlanOutput readQueryPlanBasic(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
readPlanData(sb, rset);
|
||||
return createPlan(plan, bind.toString(), sb.toString().trim());
|
||||
}
|
||||
|
||||
private void readPlanData(StringBuilder sb, ResultSet rset) throws SQLException {
|
||||
while (rset.next()) {
|
||||
sb.append('\n');
|
||||
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
|
||||
sb.append(rset.getString(i)).append("\t");
|
||||
}
|
||||
sb.setLength(sb.length()-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
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) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger for oracle.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class QueryPlanLoggerOracle extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN PLAN FOR " + plan.getSql())) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
explainStmt.execute();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger that prefixes "EXPLAIN " to the query. This works for Postgres, H2 and MySql.
|
||||
*/
|
||||
public class QueryPlanLoggerPostgres extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
String explain = "EXPLAIN " + 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger for sqlserver. It will return the plan as XML, which can be opened in
|
||||
* Microsoft SQL Server Management Studio.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class QueryPlanLoggerSqlServer extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("SET STATISTICS XML ON");
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement(plan.getSql())) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
|
||||
try (ResultSet rset = explainStmt.executeQuery()) {
|
||||
// unfortunately, this will execute the
|
||||
}
|
||||
if (explainStmt.getMoreResults()) {
|
||||
try (ResultSet rset = explainStmt.getResultSet()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (rset.next()) {
|
||||
sb.append("XML: ").append(rset.getString(1));
|
||||
}
|
||||
return createPlan(plan, bind.toString(), sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan", e);
|
||||
|
||||
} finally {
|
||||
stmt.execute("SET STATISTICS XML OFF");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public class DataBind {
|
||||
|
||||
private List<InputStream> inputStreams;
|
||||
|
||||
private int pos;
|
||||
protected int pos;
|
||||
|
||||
public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) {
|
||||
this.dataTimeZone = dataTimeZone;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCaptureStatement;
|
||||
|
||||
/**
|
||||
* Special DataBind used to capture bind values for obtaining explain plans.
|
||||
*/
|
||||
public class DataBindCapture extends DataBind {
|
||||
|
||||
private final BindCaptureStatement captureStatement;
|
||||
|
||||
/**
|
||||
* Create given the dataTimeZone in use.
|
||||
*/
|
||||
public static DataBindCapture of(DataTimeZone dataTimeZone) {
|
||||
return new DataBindCapture(dataTimeZone, new BindCaptureStatement());
|
||||
}
|
||||
|
||||
private DataBindCapture(DataTimeZone dataTimeZone, BindCaptureStatement pstmt) {
|
||||
super(dataTimeZone, pstmt, null);
|
||||
this.captureStatement = pstmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bind values capture used to obtain explain plans.
|
||||
*/
|
||||
public BindCapture bindCapture() {
|
||||
return captureStatement.bindCapture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArray(String arrayType, Object[] elements) {
|
||||
captureStatement.setArray(++pos, arrayType, elements);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package io.ebeaninternal.server.type;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
|
||||
|
||||
import io.ebean.annotation.DbArray;
|
||||
import io.ebean.annotation.DbEnumType;
|
||||
import io.ebean.annotation.DbEnumValue;
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebeaninternal.json.ModifyAwareList;
|
||||
import io.ebeaninternal.json.ModifyAwareMap;
|
||||
import io.ebeaninternal.json.ModifyAwareOwner;
|
||||
import io.ebeaninternal.json.ModifyAwareSet;
|
||||
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -17,6 +11,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebeaninternal.json.ModifyAwareList;
|
||||
import io.ebeaninternal.json.ModifyAwareMap;
|
||||
import io.ebeaninternal.json.ModifyAwareOwner;
|
||||
import io.ebeaninternal.json.ModifyAwareSet;
|
||||
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.DataInput;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.ebeaninternal.server.type.bindcapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds bind values that can be used to obtain an explain plan.
|
||||
*/
|
||||
public class BindCapture {
|
||||
|
||||
private final List<BindCaptureEntry> entries = new ArrayList<>();
|
||||
|
||||
public void add(BindCaptureEntry entry) {
|
||||
this.entries.add(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare for explain plan statement execution.
|
||||
*/
|
||||
public void prepare(PreparedStatement explainStmt, Connection connection) throws SQLException {
|
||||
for (BindCaptureEntry entry : entries) {
|
||||
entry.bind(explainStmt, connection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return entries.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ebeaninternal.server.type.bindcapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public interface BindCaptureEntry {
|
||||
|
||||
void bind(PreparedStatement statement, Connection connection) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package io.ebeaninternal.server.type.bindcapture;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Date;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* Special PreparedStatement used to capture bind values used to obtain explain plans.
|
||||
*/
|
||||
public class BindCaptureStatement extends BindCaptureStatementBase implements PreparedStatement {
|
||||
|
||||
private final BindCapture capture = new BindCapture();
|
||||
|
||||
/**
|
||||
* Return the captured bind values.
|
||||
*/
|
||||
public BindCapture bindCapture() {
|
||||
return capture;
|
||||
}
|
||||
|
||||
public void setArray(int parameterIndex, String arrayType, Object[] elements) {
|
||||
capture.add(new BindCaptureTypes.TArray(parameterIndex, arrayType, elements));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNull(int parameterIndex, int sqlType) {
|
||||
capture.add(new BindCaptureTypes.Null(parameterIndex, sqlType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoolean(int parameterIndex, boolean x) {
|
||||
capture.add(new BindCaptureTypes.Boolean(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByte(int parameterIndex, byte x) {
|
||||
capture.add(new BindCaptureTypes.Byte(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShort(int parameterIndex, short x) {
|
||||
capture.add(new BindCaptureTypes.TShort(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInt(int parameterIndex, int x) {
|
||||
capture.add(new BindCaptureTypes.TInt(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLong(int parameterIndex, long x) {
|
||||
capture.add(new BindCaptureTypes.TLong(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloat(int parameterIndex, float x) {
|
||||
capture.add(new BindCaptureTypes.TFloat(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDouble(int parameterIndex, double x) {
|
||||
capture.add(new BindCaptureTypes.TDouble(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBigDecimal(int parameterIndex, BigDecimal x) {
|
||||
capture.add(new BindCaptureTypes.TBigDecimal(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setString(int parameterIndex, String x) {
|
||||
capture.add(new BindCaptureTypes.TString(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBytes(int parameterIndex, byte[] x) {
|
||||
capture.add(new BindCaptureTypes.Bytes(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDate(int parameterIndex, Date x) {
|
||||
capture.add(new BindCaptureTypes.TDate(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTime(int parameterIndex, Time x) {
|
||||
capture.add(new BindCaptureTypes.TTime(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimestamp(int parameterIndex, Timestamp x) {
|
||||
capture.add(new BindCaptureTypes.TTimestamp(parameterIndex, x, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) {
|
||||
capture.add(new BindCaptureTypes.TTimestamp(parameterIndex, x, cal));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObject(int parameterIndex, Object x) {
|
||||
capture.add(new BindCaptureTypes.TObject(parameterIndex, x));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryStream(int parameterIndex, InputStream x, int length) {
|
||||
capture.add(new BindCaptureTypes.BinaryStream(parameterIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterStream(int parameterIndex, Reader reader, int length) {
|
||||
capture.add(new BindCaptureTypes.CharacterStream(parameterIndex));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
package io.ebeaninternal.server.type.bindcapture;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.sql.Array;
|
||||
import java.sql.Blob;
|
||||
import java.sql.Clob;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Date;
|
||||
import java.sql.NClob;
|
||||
import java.sql.ParameterMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.Ref;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.RowId;
|
||||
import java.sql.SQLWarning;
|
||||
import java.sql.SQLXML;
|
||||
import java.sql.Time;
|
||||
import java.util.Calendar;
|
||||
|
||||
abstract class BindCaptureStatementBase implements PreparedStatement {
|
||||
|
||||
@Override
|
||||
public ResultSet executeQuery() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setAsciiStream(int parameterIndex, InputStream x, int length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnicodeStream(int parameterIndex, InputStream x, int length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryStream(int parameterIndex, InputStream x, int length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearParameters() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObject(int parameterIndex, Object x, int targetSqlType) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBatch() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterStream(int parameterIndex, Reader reader, int length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRef(int parameterIndex, Ref x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlob(int parameterIndex, Blob x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClob(int parameterIndex, Clob x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArray(int parameterIndex, Array x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSetMetaData getMetaData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDate(int parameterIndex, Date x, Calendar cal) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTime(int parameterIndex, Time x, Calendar cal) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNull(int parameterIndex, int sqlType, String typeName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setURL(int parameterIndex, URL x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterMetaData getParameterMetaData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRowId(int parameterIndex, RowId x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNString(int parameterIndex, String value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNCharacterStream(int parameterIndex, Reader value, long length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNClob(int parameterIndex, NClob value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClob(int parameterIndex, Reader reader, long length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlob(int parameterIndex, InputStream inputStream, long length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNClob(int parameterIndex, Reader reader, long length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSQLXML(int parameterIndex, SQLXML xmlObject) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsciiStream(int parameterIndex, InputStream x, long length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryStream(int parameterIndex, InputStream x, long length) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterStream(int parameterIndex, Reader reader, long length) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setNCharacterStream(int parameterIndex, Reader value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClob(int parameterIndex, Reader reader) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlob(int parameterIndex, InputStream inputStream) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNClob(int parameterIndex, Reader reader) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsciiStream(int parameterIndex, InputStream x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBinaryStream(int parameterIndex, InputStream x) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterStream(int parameterIndex, Reader reader) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet executeQuery(String sql) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate(String sql) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxFieldSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxFieldSize(int max) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxRows() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxRows(int max) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEscapeProcessing(boolean enable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getQueryTimeout() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setQueryTimeout(int seconds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWarning getWarnings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearWarnings() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCursorName(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(String sql) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet getResultSet() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUpdateCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getMoreResults() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFetchDirection(int direction) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFetchDirection() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFetchSize(int rows) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFetchSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResultSetConcurrency() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResultSetType() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBatch(String sql) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBatch() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] executeBatch() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getMoreResults(int current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultSet getGeneratedKeys() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate(String sql, int autoGeneratedKeys) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate(String sql, int[] columnIndexes) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeUpdate(String sql, String[] columnNames) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(String sql, int autoGeneratedKeys) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(String sql, int[] columnIndexes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(String sql, String[] columnNames) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResultSetHoldability() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPoolable(boolean poolable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPoolable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeOnCompletion() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCloseOnCompletion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(Class<T> iface) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWrapperFor(Class<?> iface) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
package io.ebeaninternal.server.type.bindcapture;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.Charset;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Date;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
|
||||
class BindCaptureTypes {
|
||||
|
||||
static class Null implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final int sqlType;
|
||||
|
||||
Null(int parameterIndex, int sqlType) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.sqlType = sqlType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setNull(parameterIndex, sqlType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
static class Boolean implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final boolean x;
|
||||
|
||||
Boolean(int parameterIndex, boolean x) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setBoolean(parameterIndex, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(x);
|
||||
}
|
||||
}
|
||||
|
||||
static class Byte implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final byte x;
|
||||
|
||||
Byte(int parameterIndex, byte x) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setByte(parameterIndex, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(x);
|
||||
}
|
||||
}
|
||||
|
||||
static class Bytes implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final byte[] x;
|
||||
|
||||
Bytes(int parameterIndex, byte[] x) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setBytes(parameterIndex, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(x);
|
||||
}
|
||||
}
|
||||
|
||||
static class TShort implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final short value;
|
||||
|
||||
TShort(int parameterIndex, short value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setShort(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TInt implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final int value;
|
||||
|
||||
TInt(int parameterIndex, int value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setInt(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TLong implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final long value;
|
||||
|
||||
TLong(int parameterIndex, long value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setLong(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TFloat implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final float value;
|
||||
|
||||
TFloat(int parameterIndex, float value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setFloat(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TDouble implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final double value;
|
||||
|
||||
TDouble(int parameterIndex, double value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setDouble(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TBigDecimal implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final BigDecimal value;
|
||||
|
||||
TBigDecimal(int parameterIndex, BigDecimal value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setBigDecimal(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class TString implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final String value;
|
||||
|
||||
TString(int parameterIndex, String value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setString(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static class TDate implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final Date value;
|
||||
|
||||
TDate(int parameterIndex, Date value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setDate(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TTime implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final Time value;
|
||||
|
||||
TTime(int parameterIndex, Time value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setTime(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TTimestamp implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final Timestamp value;
|
||||
private final Calendar timezone;
|
||||
|
||||
TTimestamp(int parameterIndex, Timestamp value, Calendar timezone) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
this.timezone = timezone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
if (timezone == null) {
|
||||
statement.setTimestamp(parameterIndex, value);
|
||||
} else {
|
||||
statement.setTimestamp(parameterIndex, value, timezone);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TObject implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final Object value;
|
||||
|
||||
TObject(int parameterIndex, Object value) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setObject(parameterIndex, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
static class TArray implements BindCaptureEntry {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final String arrayType;
|
||||
private final Object[] elements;
|
||||
|
||||
|
||||
TArray(int parameterIndex, String arrayType, Object[] elements) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.arrayType = arrayType;
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
java.sql.Array array = connection.createArrayOf(arrayType, elements);
|
||||
statement.setArray(parameterIndex, array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Array{" + arrayType + ": " + Arrays.toString(elements) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
static class CharacterStream implements BindCaptureEntry {
|
||||
|
||||
private static final String dummy = "hi";
|
||||
|
||||
private final int parameterIndex;
|
||||
|
||||
CharacterStream(int parameterIndex) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setCharacterStream(parameterIndex, new StringReader(dummy), dummy.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "charStream";
|
||||
}
|
||||
}
|
||||
|
||||
static class BinaryStream implements BindCaptureEntry {
|
||||
|
||||
private static final byte[] dummy = "hi".getBytes(Charset.defaultCharset());
|
||||
|
||||
private final int parameterIndex;
|
||||
|
||||
BinaryStream(int parameterIndex) {
|
||||
this.parameterIndex = parameterIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(PreparedStatement statement, Connection connection) throws SQLException {
|
||||
statement.setBinaryStream(parameterIndex, new ByteArrayInputStream(dummy), dummy.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "binaryStream";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user