Compare commits

...
Author SHA1 Message Date
rob bygrave da874d84a8 [maven-release-plugin] prepare release ebean-11.30.1 2018-12-19 21:16:27 +13:00
rob bygrave 049cacad1c #1583 - ENH: Add type safe ServerConfig getServiceObject(Class<P> cls) and putServiceObject() 2018-12-19 21:14:25 +13:00
rob bygrave ced8c79458 #1582 - ENH: Pass @Cache nearCache option to ServerCacheOptions (for L2 plugins like Redis) 2018-12-19 20:57:45 +13:00
rob bygrave a66edf8077 #1477 Query plan capture - use explain analyze for Postgres
... so that we also get DB side timing
2018-12-19 16:55:24 +13:00
Rob BygraveandGitHub 94591954b3 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
2018-12-19 16:04:28 +13:00
rob bygrave e611353163 No effective change - update javadoc for QueryIterator to use try with resources 2018-12-19 15:22:32 +13:00
rob bygrave e7811910e9 No effective change - update javadoc 2018-12-19 15:15:38 +13:00
rob bygrave 138b587ff7 #1581 - ENH: Add query.asUpdate() ... to convert a query into an UpdateQuery (typically with query beans) 2018-12-19 14:45:33 +13:00
rob bygrave 8fc6c4c648 #1580 - Refactor move io.ebeaninternal.metric to public package of io.ebean.metric (such that it can be used by plugins like redis l2 cache plugin) 2018-12-19 14:16:36 +13:00
rob bygrave ebdc28e1cd #1579 - ENH: for UpdateQuery add setLabel() and update() as top level methods 2018-12-19 13:58:28 +13:00
rob bygrave 51f79e769c [maven-release-plugin] prepare for next development iteration 2018-12-17 23:39:34 +13:00
145 changed files with 2205 additions and 284 deletions
+3 -3
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.29.1</version>
<version>11.30.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -22,7 +22,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.29.1</tag>
<tag>ebean-11.30.1</tag>
</scm>
<profiles>
@@ -117,7 +117,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>4.3</version>
<version>4.4</version>
</dependency>
<dependency>
+1 -1
View File
@@ -30,7 +30,7 @@ import java.util.function.Predicate;
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* List<CustomerDto> beans =
* Ebean.findDto(CustomrDto.class, sql)
* Ebean.findDto(CustomerDto.class, sql)
* .setParameter("name", "Acme%")
* .setParameter("status", "ACTIVE")
* .findList();
@@ -128,6 +128,38 @@ public interface ExpressionList<T> {
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Return the underlying query as an UpdateQuery.
* <p>
* Typically this is used with query beans to covert a query bean
* query into an UpdateQuery like the examples below.
* </p>
*
* <pre>{@code
*
* int rowsUpdated = new QCustomer()
* .name.startsWith("Rob")
* .asUpdate()
* .set("active", false)
* .update();;
*
* }</pre>
*
* <pre>{@code
*
* int rowsUpdated = new QContact()
* .notes.note.startsWith("Make Inactive")
* .email.endsWith("@foo.com")
* .customer.id.equalTo(42)
* .asUpdate()
* .set("inactive", true)
* .setRaw("email = lower(email)")
* .update();
*
* }</pre>
*/
UpdateQuery<T> asUpdate();
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
+35 -6
View File
@@ -244,6 +244,38 @@ public interface Query<T> {
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Convert the query to a UpdateQuery.
* <p>
* Typically this is used with query beans to covert a query bean
* query into an UpdateQuery like the examples below.
* </p>
*
* <pre>{@code
*
* int rowsUpdated = new QCustomer()
* .name.startsWith("Rob")
* .asUpdate()
* .set("active", false)
* .update();;
*
* }</pre>
*
* <pre>{@code
*
* int rowsUpdated = new QContact()
* .notes.note.startsWith("Make Inactive")
* .email.endsWith("@foo.com")
* .customer.id.equalTo(42)
* .asUpdate()
* .set("inactive", true)
* .setRaw("email = lower(email)")
* .update();
*
* }</pre>
*/
UpdateQuery<T> asUpdate();
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -636,15 +668,13 @@ public interface Query<T> {
* .where().eq("status", Status.NEW)
* .order().asc("id");
*
* QueryIterator<Customer> it = query.findIterate();
* try {
* // use try with resources to ensure QueryIterator is closed
*
* try (QueryIterator<Customer> it = query.findIterate()) {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* } finally {
* // close the underlying resources
* it.close();
* }
*
* }</pre>
@@ -1329,7 +1359,6 @@ public interface Query<T> {
* count:1 orderStatus:COMPLETE
*
* }</pre>
*
*/
Query<T> setCountDistinct(CountDistinctOrder orderBy);
+10
View File
@@ -154,9 +154,19 @@ public interface UpdateQuery<T> {
*/
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set the label on the update query.
*/
UpdateQuery<T> setLabel(String label);
/**
* Return the query expression list to add predicates to.
*/
ExpressionList<T> where();
/**
* Execute the update returning the number of rows updated.
*/
int update();
}
+24
View File
@@ -12,6 +12,7 @@ public class ServerCacheOptions {
private int maxIdleSecs;
private int maxSecsToLive;
private int trimFrequency;
private boolean nearCache;
/**
* Construct with no set options.
@@ -40,6 +41,14 @@ public class ServerCacheOptions {
this.trimFrequency = cacheTuning.trimFrequency();
}
/**
* Create with nearCache option.
*/
public ServerCacheOptions(boolean nearCache, CacheBeanTuning tuning) {
this(tuning);
this.nearCache = nearCache;
}
/**
* Apply any settings from the default settings that have not already been
* specifically set.
@@ -70,9 +79,24 @@ public class ServerCacheOptions {
copy.maxIdleSecs = maxIdleSecs;
copy.maxSecsToLive = maxSecsToLive;
copy.trimFrequency = trimFrequency;
copy.nearCache = nearCache;
return copy;
}
/**
* Return true if nearCache was explicitly turned on.
*/
public boolean isNearCache() {
return nearCache;
}
/**
* Turn on nearCache option.
*/
public void setNearCache(boolean nearCache) {
this.nearCache = nearCache;
}
/**
* Return the maximum cache size.
*/
@@ -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.
*/
@@ -594,6 +599,48 @@ public class ServerConfig {
return serviceObject.get(key);
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
*
* <pre>{@code
*
* JedisPool jedisPool = ..
*
* serverConfig.putServiceObject(jedisPool);
*
* }</pre>
*/
public void putServiceObject(Object configObject) {
String key = serviceObjectKey(configObject);
serviceObject.put(key, configObject);
}
private String serviceObjectKey(Object configObject) {
return serviceObjectKey(configObject.getClass());
}
private String serviceObjectKey(Class<?> cls) {
String simpleName = cls.getSimpleName();
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
}
/**
* Used by plugins to obtain service objects.
*
* <pre>{@code
*
* JedisPool jedisPool = serverConfig.getServiceObject(JedisPool.class);
*
* }</pre>
*
* @param cls The
* @param <P>
* @return
*/
public <P> P getServiceObject(Class<P> cls) {
return (P)serviceObject.get(serviceObjectKey(cls));
}
/**
* Return the Jackson JsonFactory to use.
* <p>
@@ -2832,6 +2879,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 +3245,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,
@@ -7,6 +7,11 @@ import java.util.List;
*/
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();
}
+6 -1
View File
@@ -25,6 +25,11 @@ public enum MetricType {
* <p>
* SqlQuery and SqlUpdate without a label have no metrics collected.
*/
SQL
SQL,
/**
* L2 cache metrics.
*/
L2
}
@@ -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);
}
}
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebean.meta.MetricType;
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebeaninternal.server.profile.DMetricFactory;
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebean.meta.MetaQueryMetric;
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebean.meta.MetricVisitor;
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebean.meta.MetricVisitor;
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebean.meta.MetricVisitor;
@@ -1,4 +1,4 @@
package io.ebeaninternal.metric;
package io.ebean.metric;
import io.ebean.meta.MetaTimedMetric;
@@ -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;
@@ -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() {
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.cache;
import io.ebean.annotation.Cache;
import io.ebean.annotation.CacheBeanTuning;
import io.ebean.annotation.CacheQueryTuning;
import io.ebean.cache.QueryCacheEntryValidate;
@@ -123,9 +124,13 @@ class DefaultCacheHolder {
}
private ServerCacheOptions getBeanOptions(Class<?> cls) {
Cache cache = cls.getAnnotation(Cache.class);
boolean nearCache = (cache != null && cache.nearCache());
CacheBeanTuning tuning = cls.getAnnotation(CacheBeanTuning.class);
if (tuning != null) {
return new ServerCacheOptions(tuning).applyDefaults(beanDefault);
return new ServerCacheOptions(nearCache, tuning).applyDefaults(beanDefault);
}
return beanDefault.copy();
}
@@ -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.dto;
import io.ebean.meta.MetricType;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.QueryPlanMetric;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanMetric;
/**
* Request to map a resultSet columns for a query into a DTO bean.
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.dto;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebean.metric.QueryPlanMetric;
import io.ebean.metric.TimedMetric;
abstract class DtoQueryPlanBase implements DtoQueryPlan {
@@ -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;
@@ -18,6 +18,7 @@ import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebean.search.Match;
@@ -302,6 +303,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.asDto(dtoClass);
}
@Override
public UpdateQuery<T> asUpdate() {
return query.asUpdate();
}
@Override
public Query<T> setIncludeSoftDeletes() {
return query.setIncludeSoftDeletes();
@@ -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;
@@ -17,6 +17,7 @@ import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebean.search.Match;
@@ -349,6 +350,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.asDto(dtoClass);
}
@Override
public UpdateQuery<T> asUpdate() {
return exprList.asUpdate();
}
@Override
public Query<T> setIncludeSoftDeletes() {
return exprList.setIncludeSoftDeletes();
@@ -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);
}
}
}
}
@@ -2,9 +2,9 @@ package io.ebeaninternal.server.persist;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetricMap;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.core.PersistRequestCallableSql;
import io.ebeaninternal.server.core.PersistRequestOrmUpdate;
import io.ebeaninternal.server.core.PersistRequestUpdateSql;
@@ -1,10 +1,10 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.QueryPlanMetric;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricMap;
/**
* Default metric factory implementation.
@@ -2,9 +2,9 @@ package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetricType;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
/**
* Default implementation of the profile location factory.
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebean.metric.QueryPlanCollector;
import java.util.ArrayList;
import java.util.List;
@@ -3,9 +3,9 @@ package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
import io.ebean.metric.QueryPlanMetric;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricStats;
class DQueryPlanMetric implements QueryPlanMetric {
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.TimedMetricStats;
import io.ebean.metric.TimedMetricStats;
/**
* Snapshot of the current statistics for a Counter or TimeCounter.
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetric;
import io.ebean.metric.TimedMetric;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebean.metric.TimedMetricMap;
import java.util.concurrent.ConcurrentHashMap;
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricStats;
/**
* Default profile location that uses stack trace.
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.metric.TimedMetric;
import io.ebean.metric.TimedMetric;
/**
* ProfileLocation that collects timing metrics.
@@ -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,15 +4,17 @@ 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;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
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);
}
}
}
@@ -5,8 +5,8 @@ import io.ebean.bean.ObjectGraphNode;
import io.ebean.meta.MetaOrmQueryMetric;
import io.ebean.meta.MetaOrmQueryOrigin;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricStats;
import java.util.ArrayList;
import java.util.Collections;
@@ -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;
}
}
@@ -5,9 +5,9 @@ import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetricMap;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.RelationalQueryEngine;
import io.ebeaninternal.server.core.RelationalQueryRequest;
@@ -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 for Postgres that prefixes "EXPLAIN ANALYZE" to the query.
*/
public class QueryPlanLoggerPostgres extends QueryPlanLogger {
@Override
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan 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);
}
}
}
@@ -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;
}
}
@@ -22,6 +22,7 @@ import io.ebean.QueryIterator;
import io.ebean.QueryType;
import io.ebean.RawSql;
import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.bean.CallStack;
import io.ebean.bean.ObjectGraphNode;
@@ -296,6 +297,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return server.findDto(dtoClass, this);
}
@Override
public UpdateQuery<T> asUpdate() {
return new DefaultUpdateQuery<>(this);
}
@Override
public BeanDescriptor<T> getBeanDescriptor() {
return beanDescriptor;
@@ -48,8 +48,19 @@ public class DefaultUpdateQuery<T> implements UpdateQuery<T> {
return this;
}
@Override
public UpdateQuery<T> setLabel(String label) {
query.setLabel(label);
return this;
}
@Override
public ExpressionList<T> where() {
return query.where();
}
@Override
public int update() {
return query.update();
}
}
@@ -15,6 +15,9 @@ import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricMap;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
@@ -25,9 +28,6 @@ import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.ClockService;
@@ -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";
}
}
}
+3 -4
View File
@@ -13,7 +13,6 @@ import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import org.avaje.agentloader.AgentLoader;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
@@ -148,7 +147,7 @@ public abstract class BaseTestCase {
public boolean isMySql() {
return Platform.MYSQL == platform();
}
public boolean isHana() {
return Platform.HANA == platform();
}
@@ -160,7 +159,7 @@ public abstract class BaseTestCase {
public boolean isPlatformOrderNullsSupport() {
return isH2() || isPostgres();
}
public boolean isPersistBatchOnCascade() {
return spiEbeanServer().getDatabasePlatform().getPersistBatchOnCascade() != PersistBatch.NONE;
}
@@ -217,7 +216,7 @@ public abstract class BaseTestCase {
assertThat(sql).contains(containsIn+" not in ");
}
}
/**
* Platform specific CONCAT clause.
*/
@@ -3,7 +3,6 @@ package io.ebean;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import org.ebeantest.LoggedSqlCollector;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -4,10 +4,12 @@ import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaOrmQueryMetric;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.tests.model.basic.Country;
import org.tests.model.basic.Customer;
import org.tests.model.basic.EBasicWithUniqueCon;
import org.tests.model.basic.ResetBasicData;
import java.sql.Timestamp;
import java.util.List;
@@ -43,6 +45,59 @@ public class UpdateQueryTest extends BaseTestCase {
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateActive");
}
@Test
public void update() {
ResetBasicData.reset();
resetAllMetrics();
UpdateQuery<Customer> update = server().update(Customer.class);
LoggedSqlCollector.start();
int rows = update
.setRaw("status = status")
.setLabel("updateAll")
.update();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(rows).isGreaterThan(0);
assertThat(sql.get(0)).contains("update o_customer set status = status");
BasicMetricVisitor basic = visitMetricsBasic();
List<MetaOrmQueryMetric> ormQueryMetrics = basic.getOrmQueryMetrics();
assertThat(ormQueryMetrics).hasSize(1);
assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateAll");
}
@Test
public void query_asUpdate() {
ResetBasicData.reset();
LoggedSqlCollector.start();
int rows = server().find(Customer.class)
.where()
.gt("id", 1000)
.asUpdate()
.setRaw("status = status")
.setLabel("asUpdate")
.update();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(rows).isEqualTo(0);
assertThat(sql.get(0)).contains("update o_customer set status = status where id > ?");
}
@Test
public void update_withTransactionBatch() {
@@ -1,5 +1,6 @@
package io.ebean.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.dbplatform.IdType;
import io.ebean.datasource.DataSourceConfig;
@@ -121,4 +122,19 @@ public class ServerConfigTest {
serverConfig.setIdGeneratorAutomatic(false);
assertFalse(serverConfig.isIdGeneratorAutomatic());
}
@Test
public void test_putServiceObject() {
ObjectMapper objectMapper = new ObjectMapper();
ServerConfig config = new ServerConfig();
config.putServiceObject(objectMapper);
ObjectMapper mapper0 = config.getServiceObject(ObjectMapper.class);
ObjectMapper mapper1 = (ObjectMapper)config.getServiceObject("objectMapper");
assertThat(objectMapper).isSameAs(mapper0);
assertThat(objectMapper).isSameAs(mapper1);
}
}
@@ -1,12 +1,11 @@
package io.ebean.config.dbplatform;
import io.ebean.config.dbplatform.hana.HanaHistorySupport;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import io.ebean.config.dbplatform.hana.HanaHistorySupport;
public class HanaHistorySupportTest {
private HanaHistorySupport support = new HanaHistorySupport();
@@ -24,7 +23,7 @@ public class HanaHistorySupportTest {
String asOfViewSuffix = support.getAsOfViewSuffix("_with_history");
assertEquals(asOfViewSuffix, " for system_time as of ?");
}
@Test
public void getVersionsBetweenSuffix() {
@@ -2,10 +2,10 @@ package io.ebean.plugin;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -9,7 +9,6 @@ import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.migration.ddl.DdlRunner;
import io.ebeaninternal.dbmigration.ddlgeneration.Helper;
import org.junit.Test;
import javax.persistence.PersistenceException;
@@ -1,14 +1,13 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.junit.Test;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.Column;
import org.junit.Test;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
public class HanaDdlTest {
@@ -1,12 +1,11 @@
package io.ebeaninternal.server.expression.platform;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.Op;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class HanaDbExpressionTest {
private HanaDbExpression expression = new HanaDbExpression();
@@ -54,13 +53,13 @@ public class HanaDbExpressionTest {
String concat = expression.concat("property0", "separator", "property1", "suffix");
assertEquals("concat(property0, 'separator'||property1||'suffix')", concat);
}
@Test
public void testConcatNullSuffix() {
String concat = expression.concat("property0", "separator", "property1", null);
assertEquals("concat(property0, 'separator'||property1)", concat);
}
@Test
public void testJson() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetricType;
import io.ebeaninternal.metric.MetricFactory;
import io.ebean.metric.MetricFactory;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.query;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
public class SqlTreeBuilderTest {
@@ -6,12 +6,12 @@ import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import org.tests.model.basic.Address;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.basic.Address;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import java.util.List;
import java.util.Set;
@@ -6,11 +6,11 @@ import io.ebean.Query;
import io.ebean.SqlRow;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.datasource.DataSourceConfig;
import org.junit.Assert;
import org.tests.model.basic.TOne;
import org.tests.model.basic.TSDetail;
import org.tests.model.basic.TSMaster;
import io.ebean.datasource.DataSourceConfig;
import org.junit.Assert;
import java.util.List;
@@ -2,9 +2,9 @@ package org.tests.basic;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.tests.model.basic.Order;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.basic.Order;
import javax.persistence.PersistenceException;
@@ -2,11 +2,11 @@ package org.tests.basic;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
@@ -3,10 +3,10 @@ package org.tests.basic;
import io.ebean.BaseTestCase;
import io.ebean.BeanState;
import io.ebean.Ebean;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
public class TestTransient extends BaseTestCase {
@@ -6,8 +6,8 @@ import io.ebean.EbeanServer;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Transactional;
import org.tests.model.basic.Customer;
import org.junit.Test;
import org.tests.model.basic.Customer;
import static org.junit.Assert.assertNotNull;
@@ -2,11 +2,11 @@ package org.tests.batchload;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
@@ -2,11 +2,11 @@ package org.tests.batchload;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Order;
import org.tests.model.basic.Order.Status;
import org.tests.model.basic.ResetBasicData;
import org.junit.Test;
public class TestEmptyManyLazyLoad extends BaseTestCase {
@@ -3,9 +3,9 @@ package org.tests.batchload;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.FetchConfig;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

Some files were not shown because too many files have changed in this diff Show More