Compare commits

...
Author SHA1 Message Date
rob bygrave 4509c6b1ed [maven-release-plugin] prepare release ebean-11.37.1 2019-04-12 13:38:41 +12:00
rob bygrave d32559baf4 #1674 - For ProfileLocation use in constructors trim label <init> to init .... (for safe label) 2019-04-12 02:25:05 +12:00
rob bygrave 2639e799c5 #1673 - ENH: Support ProfileLocation on DtoQuery including via orm query.asDto() 2019-04-12 02:01:01 +12:00
rob bygrave 46c66b7169 #1672 - ENH: Add ebean.dumpMetricsOnShutdown=true ... for dumping metrics 2019-04-12 00:55:39 +12:00
rob bygrave 30ae435f33 #1672 - ENH: Add ebean.dumpMetricsOnShutdown=true ... for dumping metrics 2019-04-12 00:45:00 +12:00
rob bygrave 4f4743807c #1671 - Use table alias with delete query for Postgres, Oracle, MySql 2019-04-10 01:25:43 +12:00
rob bygrave 7b7a492241 Bump to 11.37.1 2019-04-09 23:04:34 +12:00
rob bygrave 4af8e53315 #1670 - Expose metrics as ServerMetrics interface (improvement over existing BasicMetricVisitor use) 2019-04-09 22:54:16 +12:00
rob bygrave 1c6fa575f2 #1669 - When deleting complex object graphs, entities are deleted multiple times
Fix by not unregistering the deleted bean hash in the transaction so that
the skip occurs on other root level bean traversals (the delete of the
2nd master bean)
2019-04-09 20:30:17 +12:00
rob bygrave 2430404e1f Merge branch 'FOCONIS-bug-delete-with-version' 2019-04-09 20:27:23 +12:00
rob bygrave d433e0ea46 Merge branch 'bug-delete-with-version' of https://github.com/FOCONIS/ebean into FOCONIS-bug-delete-with-version 2019-04-09 20:06:12 +12:00
rob bygrave 42591a5a87 No effective change - reformat line 2019-04-09 20:05:22 +12:00
Roland PramlandRob Bygrave 65a62bf683 Fix check constraint support for MariaDB >= 10.2.1 (#1668)
* fixed the check constraint support for mysql/mariadb

* update migration scripts
2019-04-09 19:38:57 +12:00
Roland Praml 3e3f3c35e7 add test case to show delete with version 2019-04-08 17:21:23 +02:00
Rob BygraveandGitHub f4db7ca0bb Update sponsors in README 2019-04-08 20:49:28 +12:00
rob bygrave b0e487cfbd Add sponsor payintech.com 2019-04-03 23:24:13 +13:00
rob bygrave 23fd29ddf1 #1659 - CVE-2018-12022 - Bump Jackson version from 2.9.5 to 2.9.8 2019-04-01 23:23:02 +13:00
rob bygrave f21cac5d4f [maven-release-plugin] prepare for next development iteration 2019-04-01 23:13:07 +13:00
rob bygrave c2e5619f70 [maven-release-plugin] prepare release ebean-11.36.4 2019-04-01 23:12:53 +13:00
rob bygrave 18e0ff7067 #1665 - Incorrect query execution metrics for ORM query converted to DTO query via asDto() 2019-04-01 23:11:16 +13:00
rob bygrave 4119c02f0d #1663 Additionally trim the OrmQueryDetail.asString() output removing extra whitespace 2019-04-01 23:09:58 +13:00
rob bygrave 8985d37aed Merge branch 'master' of github.com:ebean-orm/ebean 2019-04-01 22:25:24 +13:00
Rob BygraveandGitHub 23e035894f Add foconis.de as sponsor to readme 2019-04-01 22:24:50 +13:00
rob bygrave 32b8fd0e26 #1663 Additionally trim the OrmQueryDetail.asString() output removing extra whitespace 2019-03-29 00:43:32 +13:00
rob bygrave 4971db54c8 #1663 - AutoTune - merge profiling such that "select (orderDate) fetch customer (id)" instead merges to "select (orderDate,customer)" 2019-03-29 00:19:21 +13:00
rob bygrave e417b3cde2 #1661 - AutoTune - callstack filters _ebean_ methods 2019-03-28 20:47:34 +13:00
rob bygrave d23ca31e42 [maven-release-plugin] prepare for next development iteration 2019-03-27 16:44:00 +13:00
rob bygrave c9b71ae373 [maven-release-plugin] prepare release ebean-11.36.3 2019-03-27 16:43:47 +13:00
rob bygrave f06f21cf5e #1660 - Modify MetaOrmQueryMetric getQueryPlanHash to return hash of sql 2019-03-27 16:12:09 +13:00
rob bygrave f8b97ff397 #1658 - ENH: Add label to ProfileLocation and use as default query label
- Extend/propagate the label from the origin query to secondary queries
(appending the relative path).
2019-03-27 12:57:36 +13:00
rob bygrave fc032c632b #1658 - ENH: Add label to ProfileLocation and use as default query label 2019-03-27 09:37:35 +13:00
rob bygrave 54534ea7f3 #1657 - ERROR BeanDescriptorManager - Error in deployment java.lang.RuntimeException: Expecting [ ... ] to start with "select" 2019-03-21 22:30:27 +13:00
rob bygrave 812dcbb4bb #1653 - ENH: Add support for ClickHouse database
With array type conversion for primitive arrays
2019-03-20 23:13:50 +13:00
rob bygrave a3d4b57b94 Merge branch 'feature/1653-clickhouse' 2019-03-20 22:07:42 +13:00
rob bygrave 61479d365a Merge branch 'master' of github.com:ebean-orm/ebean 2019-03-20 22:05:54 +13:00
ooknightandRob Bygrave 3ba9a52b0e add server config : default enum type (#1655)
ENH: add server config : default enum type
2019-03-20 21:45:44 +13:00
rob bygrave 518ba37fb2 No effective change - Bump maven plugin version 2019-03-16 15:28:44 +13:00
rob bygrave fd6a7f64eb [maven-release-plugin] prepare for next development iteration 2019-03-14 21:17:03 +13:00
rob bygrave 8b073ef10a [maven-release-plugin] prepare release ebean-11.36.2 2019-03-14 21:16:47 +13:00
rob bygrave 6a7555055d No effective change - Update tests from Ebean -> DB 2019-03-14 21:11:59 +13:00
rob bygrave eb31f1bfce No effective change - Add more tests for LocalDate pre 1900 etc 2019-03-14 16:20:14 +13:00
rob bygrave 2e4f99318a No effective change - Ebean -> DB in tests 2019-03-14 16:15:47 +13:00
rob bygrave 409c1d06b2 #1652 - Conversion problem between org.joda.time.LocalDate and java.sql.Date field for dates before Jan 2 1900 2019-03-14 16:08:04 +13:00
rob bygrave 01f2881940 #1653 - ENH: Add support for ClickHouse database
With array type conversion etc
2019-03-14 14:44:30 +13:00
rob bygrave aef512a851 #1653 - ENH: Add support for ClickHouse database 2019-03-14 00:08:13 +13:00
rob bygrave 435e1f61fe #1651 - ENH: Support change to io.ebean.Model that supports named databases (other than default database) 2019-03-13 15:05:08 +13:00
rob bygrave 0230e3e253 Reduce logging for CI server 2019-03-12 15:06:26 +13:00
rob bygrave 770364fd1a [maven-release-plugin] prepare for next development iteration 2019-03-12 14:53:24 +13:00
122 changed files with 2108 additions and 554 deletions
+23
View File
@@ -1,6 +1,29 @@
[![Build Status](https://travis-ci.org/ebean-orm/ebean.svg?branch=master)](https://travis-ci.org/ebean-orm/ebean)
[![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean)
# Sponsors
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://www.foconis.de/" target="_blank">
<img width="222px" src="https://www.foconis.de/templates/yootheme/cache/foconis_logo_322-709da1de.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.payintech.com/" target="_blank">
<img width="222px" src="https://www.payintech.com/wp-content/uploads/2015/10/PayinTech-logo-noir.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.premium-minds.com" target="_blank">
<img width="222px" src="https://ebean.io/images/logo-premium-minds.png">
</a>
</td>
</tr>
</tbody>
</table>
# Need help?
Post questions or issues to the Ebean google group - https://groups.google.com/forum/#!forum/ebean
+7 -7
View File
@@ -9,20 +9,20 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.36.1</version>
<version>11.37.1</version>
<packaging>jar</packaging>
<name>ebean</name>
<url>http://ebean-orm.github.io/</url>
<properties>
<jackson-core.version>2.9.5</jackson-core.version>
<jackson-core.version>2.9.8</jackson-core.version>
<snakeyaml.version>1.21</snakeyaml.version>
</properties>
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.36.1</tag>
<tag>ebean-11.37.1</tag>
</scm>
<profiles>
@@ -111,13 +111,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>persistence-api</artifactId>
<version>2.2.1</version>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>4.6</version>
<version>4.7</version>
</dependency>
<dependency>
@@ -250,7 +250,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.27.1</version>
<version>11.37.1</version>
<scope>test</scope>
</dependency>
@@ -330,7 +330,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.27.1</version>
<version>11.37.1</version>
<executions>
<execution>
<id>test</id>
+6
View File
@@ -109,6 +109,12 @@ public interface DtoQuery<T> {
*/
DtoQuery<T> setLabel(String label);
/**
* Set the profile location of this query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
DtoQuery<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set a timeout on this query.
* <p>
@@ -40,6 +40,11 @@ public interface ProfileLocation {
*/
String shortDescription();
/**
* Return the short label.
*/
String label();
/**
* Add execution time.
*/
@@ -5,7 +5,7 @@ package io.ebean.config;
*/
public class AutoTuneConfig {
private AutoTuneMode mode = AutoTuneMode.DEFAULT_ON;
private AutoTuneMode mode = AutoTuneMode.DEFAULT_OFF;
private String queryTuningFile = "ebean-autotune.xml";
@@ -32,6 +32,7 @@ import io.ebean.meta.MetaInfoManager;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import javax.persistence.EnumType;
import javax.sql.DataSource;
import java.time.Clock;
import java.util.ArrayList;
@@ -229,6 +230,8 @@ public class ServerConfig {
private int persistBatchSize = 20;
private EnumType defaultEnumType = EnumType.ORDINAL;
private boolean disableLazyLoading;
/**
@@ -521,6 +524,10 @@ public class ServerConfig {
*/
private boolean idGeneratorAutomatic = true;
private boolean dumpMetricsOnShutdown;
private String dumpMetricsOptions;
/**
* Construct a Database Configuration for programmatically creating an Database.
*/
@@ -978,6 +985,14 @@ public class ServerConfig {
this.queryBatchSize = queryBatchSize;
}
public EnumType getDefaultEnumType() {
return defaultEnumType;
}
public void setDefaultEnumType(EnumType defaultEnumType) {
this.defaultEnumType = defaultEnumType;
}
/**
* Return true if lazy loading is disabled on queries by default.
*/
@@ -2892,6 +2907,8 @@ public class ServerConfig {
}
loadDocStoreSettings(p);
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions);
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
@@ -2963,6 +2980,7 @@ public class ServerConfig {
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
defaultEnumType = p.getEnum(EnumType.class, "defaultEnumType", defaultEnumType);
disableLazyLoading = p.getBoolean("disableLazyLoading", disableLazyLoading);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
@@ -3275,6 +3293,36 @@ public class ServerConfig {
this.collectQueryPlans = collectQueryPlans;
}
/**
* Return true if metrics should be dumped when the server is shutdown.
*/
public boolean isDumpMetricsOnShutdown() {
return dumpMetricsOnShutdown;
}
/**
* Set to true if metrics should be dumped when the server is shutdown.
*/
public void setDumpMetricsOnShutdown(boolean dumpMetricsOnShutdown) {
this.dumpMetricsOnShutdown = dumpMetricsOnShutdown;
}
/**
* Return the options for dumping metrics.
*/
public String getDumpMetricsOptions() {
return dumpMetricsOptions;
}
/**
* Include 'sql' or 'hash' in options such that they are included in the output.
*
* @param dumpMetricsOptions Example "sql,hash", "sql"
*/
public void setDumpMetricsOptions(String dumpMetricsOptions) {
this.dumpMetricsOptions = dumpMetricsOptions;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -49,6 +49,8 @@ public class DatabasePlatform {
*/
protected boolean useExtraTransactionOnIterateSecondaryQueries;
protected boolean supportsDeleteTableAlias;
/**
* The behaviour used when ending a read only transaction at read committed isolation level.
*/
@@ -176,10 +178,10 @@ public class DatabasePlatform {
* findIterate() and findVisit().
*/
protected boolean forwardOnlyHintOnFindIterate;
/**
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
*
*
* This is {@code false} for HANA
*/
protected boolean supportsResultSetConcurrencyModeUpdatable = true;
@@ -306,6 +308,13 @@ public class DatabasePlatform {
return supportsNativeIlike;
}
/**
* Return true if the platform supports delete statements with table alias.
*/
public boolean isSupportsDeleteTableAlias() {
return supportsDeleteTableAlias;
}
/**
* Return the maximum table name length.
* <p>
@@ -524,7 +533,7 @@ public class DatabasePlatform {
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
}
/**
* Return true if the ResultSet CONCUR_UPDATABLE Hint should be used on
* createNativeSqlTree() PreparedStatements.
@@ -535,7 +544,7 @@ public class DatabasePlatform {
public boolean isSupportsResultSetConcurrencyModeUpdatable() {
return supportsResultSetConcurrencyModeUpdatable;
}
/**
* Set to true if the ResultSet CONCUR_UPDATABLE Hint should be used by default on createNativeSqlTree() PreparedStatements.
*/
@@ -0,0 +1,58 @@
package io.ebean.config.dbplatform.clickhouse;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import java.sql.Types;
public class ClickHousePlatform extends DatabasePlatform {
public ClickHousePlatform() {
super();
this.platform = Platform.CLICKHOUSE;
//this.dbEncrypt =
//this.historySupport =
//this.exceptionTranslator =
this.nativeUuidType = true;
this.dbDefaultValue.setNow("now()");
this.columnAliasPrefix = null;
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(false);
this.dbIdentity.setSupportsSequence(false);
this.dbIdentity.setSupportsIdentity(true);
this.booleanDbType = Types.INTEGER;
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("UInt8"));
// using unsigned as default types ...
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("UInt8", false));
dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("UInt16", false));
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("UInt32", false));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("UInt64", false));
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("Decimal", 16,4));
dbTypeMap.put(DbType.DOUBLE, new DbPlatformType("Float64", false));
dbTypeMap.put(DbType.DATE, new DbPlatformType("Date", false));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("DateTime", false));
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("String", false));
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("String", false));
dbTypeMap.put(DbType.CLOB, new DbPlatformType("String", false));
dbTypeMap.put(DbType.JSONVARCHAR, new DbPlatformType("String", false));
dbTypeMap.put(DbType.UUID, new DbPlatformType("UUID", false));
dbTypeMap.put(DbType.INET, new DbPlatformType("String", false));
dbTypeMap.put(DbType.CDIR, new DbPlatformType("String", false));
}
@Override
public boolean isNativeArrayType() {
return true;
}
}
@@ -23,6 +23,7 @@ public class H2Platform extends DatabasePlatform {
this.dbEncrypt = new H2DbEncrypt();
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
this.supportsDeleteTableAlias = true;
this.dbDefaultValue.setNow("now()");
this.columnAliasPrefix = null;
@@ -23,6 +23,7 @@ public class OraclePlatform extends DatabasePlatform {
public OraclePlatform() {
super();
this.platform = Platform.ORACLE;
this.supportsDeleteTableAlias = true;
this.maxTableNameLength = 30;
this.maxConstraintNameLength = 30;
this.dbEncrypt = new OracleDbEncrypt();
@@ -30,6 +30,7 @@ public class PostgresPlatform extends DatabasePlatform {
super();
this.platform = Platform.POSTGRES;
this.supportsNativeIlike = true;
this.supportsDeleteTableAlias = true;
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
@@ -6,7 +6,7 @@ import java.util.List;
/**
* A simple MetricVisitor that can collect the desired metrics into lists.
*/
public class BasicMetricVisitor extends AbstractMetricVisitor {
public class BasicMetricVisitor extends AbstractMetricVisitor implements ServerMetrics {
private final List<MetaTimedMetric> timed = new ArrayList<>();
private final List<MetaQueryMetric> dtoQuery = new ArrayList<>();
@@ -29,6 +29,7 @@ public class BasicMetricVisitor extends AbstractMetricVisitor {
/**
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
*/
@Override
public List<MetaTimedMetric> getTimedMetrics() {
return timed;
}
@@ -36,6 +37,7 @@ public class BasicMetricVisitor extends AbstractMetricVisitor {
/**
* Return the DTO query metrics.
*/
@Override
public List<MetaQueryMetric> getDtoQueryMetrics() {
return dtoQuery;
}
@@ -43,6 +45,7 @@ public class BasicMetricVisitor extends AbstractMetricVisitor {
/**
* Return the ORM query metrics.
*/
@Override
public List<MetaOrmQueryMetric> getOrmQueryMetrics() {
return ormQuery;
}
@@ -7,6 +7,15 @@ import java.util.List;
*/
public interface MetaInfoManager {
/**
* Return the metrics for the database instance.
* <p>
* This will reset the metrics (reset counters back to zero etc) and
* will only return the non-empty metrics.
* </p>
*/
ServerMetrics collectMetrics();
/**
* Collect query plans.
*/
@@ -1,7 +1,5 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
@@ -9,11 +7,6 @@ import java.util.List;
*/
public interface MetaOrmQueryMetric extends MetaQueryMetric {
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
@@ -1,5 +1,7 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
/**
* Query execution metrics.
*/
@@ -15,6 +17,11 @@ public interface MetaQueryMetric extends MetaTimedMetric {
*/
String getLabel();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* The actual SQL of the query.
*/
@@ -0,0 +1,24 @@
package io.ebean.meta;
import java.util.List;
/**
* Metrics of the Database instance.
*/
public interface ServerMetrics {
/**
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
*/
List<MetaTimedMetric> getTimedMetrics();
/**
* Return the DTO query metrics.
*/
List<MetaQueryMetric> getDtoQueryMetrics();
/**
* Return the ORM query metrics.
*/
List<MetaOrmQueryMetric> getOrmQueryMetrics();
}
@@ -0,0 +1,80 @@
package io.ebean.meta;
import java.util.Comparator;
/**
* Comparator for timed metrics sorted by name and then count.
*/
public class SortMetric {
public static final Comparator<MetaTimedMetric> NAME = new Name();
public static final Comparator<MetaTimedMetric> COUNT = new Count();
public static final Comparator<MetaTimedMetric> TOTAL = new Total();
public static final Comparator<MetaTimedMetric> MEAN = new Mean();
public static final Comparator<MetaTimedMetric> MAX = new Max();
/**
* Sort by name.
*/
public static class Name implements Comparator<MetaTimedMetric> {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
String name = o1.getName();
String name2 = o2.getName();
if (name == null) {
return name2 == null ? 0 : -1;
}
if (name2 == null) {
return 1;
}
int i = name.compareTo(name2);
return i != 0 ? i : Long.compare(o1.getCount(), o2.getCount());
}
}
/**
* Sort by count desc.
*/
public static class Count implements Comparator<MetaTimedMetric> {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getCount(), o1.getCount());
}
}
/**
* Sort by total time desc.
*/
public static class Total implements Comparator<MetaTimedMetric> {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getTotal(), o1.getTotal());
}
}
/**
* Sort by mean desc.
*/
public static class Mean implements Comparator<MetaTimedMetric> {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getMean(), o1.getMean());
}
}
/**
* Sort by max desc.
*/
public static class Max implements Comparator<MetaTimedMetric> {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getMax(), o1.getMax());
}
}
}
@@ -1,5 +1,6 @@
package io.ebean.metric;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetricType;
/**
@@ -27,6 +28,6 @@ public interface MetricFactory {
/**
* Create a Timed metric.
*/
QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, String sql);
QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, ProfileLocation profileLocation, String sql);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebean.ProfileLocation;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
@@ -37,9 +38,23 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
/**
* Return the label for the query.
*/
@Override
String getLabel();
/**
* Return the label with fallback to profile location label.
*/
String getPlanLabel();
/**
* Obtain the location if necessary.
*/
void obtainLocation();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return the associated DTO bean type.
*/
@@ -54,67 +54,69 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Find by Id or unique returning a single bean.
*/
BEAN(FIND_ONE),
BEAN(FIND_ONE, "byId"),
/**
* Find returning a List.
*/
LIST(FIND_MANY),
LIST(FIND_MANY, "findList"),
/**
* Find returning a Set.
*/
SET(FIND_MANY),
SET(FIND_MANY, "findSet"),
/**
* Find returning a Map.
*/
MAP(FIND_MANY),
MAP(FIND_MANY, "findMap"),
/**
* Find iterate type query - findEach(), findIterate() etc.
*/
ITERATE(FIND_ITERATE),
ITERATE(FIND_ITERATE, "findEach"),
/**
* Find the Id's.
*/
ID_LIST(FIND_ID_LIST),
ID_LIST(FIND_ID_LIST, "findIds"),
/**
* Find single attribute.
*/
ATTRIBUTE(FIND_ATTRIBUTE),
ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute"),
/**
* Find rowCount.
*/
COUNT(FIND_COUNT),
COUNT(FIND_COUNT, "findCount"),
/**
* A subquery used as part of a where clause.
*/
SUBQUERY(FIND_SUBQUERY),
SUBQUERY(FIND_SUBQUERY, "subquery"),
/**
* Delete query.
*/
DELETE(FIND_DELETE, true),
DELETE(FIND_DELETE, "delete", true),
/**
* Update query.
*/
UPDATE(FIND_UPDATE, true);
UPDATE(FIND_UPDATE, "update", true);
boolean update;
String profileEventId;
private boolean update;
private String profileEventId;
private String label;
Type(String profileEventId) {
this(profileEventId, false);
Type(String profileEventId, String label) {
this(profileEventId, label, false);
}
Type(String profileEventId, boolean update) {
Type(String profileEventId, String label, boolean update) {
this.profileEventId = profileEventId;
this.label = label;
this.update = update;
}
@@ -128,6 +130,10 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
public String profileEventId() {
return profileEventId;
}
public String label() {
return label;
}
}
enum TemporalMode {
@@ -197,6 +203,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
String getLabel();
/**
* Return the label manually set on the query or from the profile location.
*/
String getPlanLabel();
/**
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
*/
@@ -69,11 +69,6 @@ public interface SpiTransaction extends Transaction {
*/
void registerDeleteBean(Integer hash);
/**
* Unregister the hash of the bean.
*/
void unregisterDeleteBean(Integer hash);
/**
* Return true if this is a bean that has already been saved/deleted.
*/
@@ -183,11 +183,6 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
transaction.registerDeleteBean(hash);
}
@Override
public void unregisterDeleteBean(Integer hash) {
transaction.unregisterDeleteBean(hash);
}
@Override
public boolean isRegisteredDeleteBean(Integer hash) {
return transaction.isRegisteredDeleteBean(hash);
@@ -189,7 +189,7 @@ public class DdlGenerator {
runScript(connection, false, createAllContent, getCreateFileName());
if (extraDdl && jaxbPresent) {
if (currentModel.isTablePartitioning()) {
if (currentModel().isTablePartitioning()) {
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
runScript(connection, false, extraPartitioning, "builtin-partitioning-ddl");
@@ -201,7 +201,7 @@ public class DdlGenerator {
runScript(connection, false, extraApply, "extra-ddl");
}
if (currentModel.isTablePartitioning()) {
if (currentModel().isTablePartitioning()) {
checkInitialTablePartitions(connection);
}
}
@@ -8,6 +8,8 @@ import io.ebean.config.DbMigrationConfig;
import io.ebean.config.PlatformConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform;
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
@@ -802,6 +804,11 @@ public class DefaultDbMigration implements DbMigration {
return new SQLitePlatform();
case HANA:
return new HanaPlatform();
case COCKROACH:
return new CockroachPlatform();
case CLICKHOUSE:
return new ClickHousePlatform();
case GENERIC:
return new DatabasePlatform();
@@ -19,6 +19,11 @@ public interface DdlBuffer {
*/
boolean isEmpty();
/**
* Append a statement allowing for null or empty statements.
*/
DdlBuffer appendStatement(String content) throws IOException;
/**
* Append DDL content to the buffer.
*/
@@ -38,6 +38,15 @@ public class BaseDdlBuffer implements DdlBuffer {
return this;
}
@Override
public DdlBuffer appendStatement(String content) throws IOException {
if (content != null && !content.isEmpty()) {
writer.append(content);
endOfStatement();
}
return this;
}
@Override
public DdlBuffer append(String content) throws IOException {
writer.append(content);
@@ -73,7 +82,9 @@ public class BaseDdlBuffer implements DdlBuffer {
*/
@Override
public DdlBuffer end() throws IOException {
writer.append("\n");
if (!isEmpty()) {
writer.append("\n");
}
return this;
}
@@ -37,6 +37,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns.split;
/**
* Base implementation for 'create table' and 'alter table' statements.
*/
@@ -148,8 +150,7 @@ public class BaseTableDdl implements TableDdl {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
for (String ddlScript : before) {
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
buffer.endOfStatement();
buffer.appendStatement(translate(ddlScript, tableName, columnName, this.defaultValue));
}
}
@@ -159,8 +160,7 @@ public class BaseTableDdl implements TableDdl {
}
// here we run post migration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
buffer.endOfStatement();
buffer.appendStatement(translate(ddlScript, tableName, columnName, defaultValue));
}
if (!after.isEmpty()) {
buffer.end();
@@ -262,7 +262,6 @@ public class BaseTableDdl implements TableDdl {
DdlBuffer apply = writer.apply();
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(tableName).append(" (");
writeTableColumns(apply, columns, useIdentity);
writeCheckConstraints(apply, createTable);
writeUniqueConstraints(apply, createTable);
writeCompoundUniqueConstraints(apply, createTable);
if (!pk.isEmpty()) {
@@ -276,6 +275,7 @@ public class BaseTableDdl implements TableDdl {
}
apply.newLine().append(")");
addTableStorageEngine(apply, createTable);
addTableCommentInline(apply, createTable);
if (partitionMode != null) {
platformDdl.addTablePartition(apply, partitionMode, createTable.getPartitionColumn());
@@ -316,12 +316,11 @@ public class BaseTableDdl implements TableDdl {
private void addComments(DdlBuffer apply, CreateTable createTable) throws IOException {
if (!platformDdl.isInlineComments()) {
String tableComment = createTable.getComment();
if (!StringHelper.isNull(tableComment)) {
if (hasValue(tableComment)) {
platformDdl.addTableComment(apply, createTable.getName(), tableComment);
}
List<Column> columns = createTable.getColumn();
for (Column column : columns) {
for (Column column : createTable.getColumn()) {
if (!StringHelper.isNull(column.getComment())) {
platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment());
}
@@ -329,6 +328,15 @@ public class BaseTableDdl implements TableDdl {
}
}
/**
* Add the table storage engine clause.
*/
private void addTableStorageEngine(DdlBuffer apply, CreateTable createTable) throws IOException {
if (platformDdl.isIncludeStorageEngine()) {
platformDdl.tableStorageEngine(apply, createTable.getStorageEngine());
}
}
/**
* Add the table comment inline with the create table statement.
*/
@@ -358,26 +366,17 @@ public class BaseTableDdl implements TableDdl {
uqName = col.getUnique();
}
String[] columnNames = {col.getName()};
write.apply()
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull()) ? null : columnNames))
.endOfStatement();
write.dropAllForeignKeys()
.append(platformDdl.dropIndex(uqName, tableName))
.endOfStatement();
write.apply().appendStatement(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull()) ? null : columnNames));
write.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(uqName, tableName));
}
for (UniqueConstraint constraint : externalCompoundUnique) {
String uqName = constraint.getName();
String[] columnNames = SplitColumns.split(constraint.getColumnNames());
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
write.apply()
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns))
.endOfStatement();
String[] columnNames = split(constraint.getColumnNames());
String[] nullableColumns = split(constraint.getNullableColumns());
write.dropAllForeignKeys()
.append(platformDdl.dropIndex(uqName, tableName))
.endOfStatement();
write.apply().appendStatement(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, nullableColumns));
write.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(uqName, tableName));
}
}
@@ -394,9 +393,9 @@ public class BaseTableDdl implements TableDdl {
}
String createSeq = platformDdl.createSequence(seqName, initial, allocate);
if (createSeq != null) {
if (hasValue(createSeq)) {
writer.apply().append(createSeq).newLine();
writer.dropAll().append(platformDdl.dropSequence(seqName)).endOfStatement();
writer.dropAll().appendStatement(platformDdl.dropSequence(seqName));
}
}
@@ -425,8 +424,7 @@ public class BaseTableDdl implements TableDdl {
protected void writeInlineCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
List<ForeignKey> foreignKey = createTable.getForeignKey();
for (ForeignKey key : foreignKey) {
for (ForeignKey key : createTable.getForeignKey()) {
String fkConstraint = platformDdl.tableInlineForeignKey(new WriteForeignKey(null, key));
write.apply().append(",").newLine().append(" ").append(fkConstraint);
}
@@ -434,12 +432,10 @@ public class BaseTableDdl implements TableDdl {
protected void writeAddForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
String tableName = createTable.getName();
List<Column> columns = createTable.getColumn();
for (Column column : columns) {
for (Column column : createTable.getColumn()) {
String references = column.getReferences();
if (hasValue(references)) {
writeForeignKey(write, tableName, column);
writeForeignKey(write, createTable.getName(), column);
}
}
@@ -448,11 +444,8 @@ public class BaseTableDdl implements TableDdl {
protected void writeAddCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
String tableName = createTable.getName();
List<ForeignKey> foreignKey = createTable.getForeignKey();
for (ForeignKey key : foreignKey) {
writeForeignKey(write, new WriteForeignKey(tableName, key));
for (ForeignKey key : createTable.getForeignKey()) {
writeForeignKey(write, new WriteForeignKey(createTable.getName(), key));
}
}
@@ -466,19 +459,15 @@ public class BaseTableDdl implements TableDdl {
String tableName = lowerTableName(request.table());
if (request.indexName() != null) {
// no matching unique constraint so add the index
fkeyBuffer.append(platformDdl.createIndex(request.indexName(), tableName, request.cols())).endOfStatement();
fkeyBuffer.appendStatement(platformDdl.createIndex(request.indexName(), tableName, request.cols()));
}
alterTableAddForeignKey(fkeyBuffer, request);
fkeyBuffer.end();
write.dropAllForeignKeys()
.append(platformDdl.alterTableDropForeignKey(tableName, request.fkName())).endOfStatement();
if (request.indexName() != null) {
write.dropAllForeignKeys()
.append(platformDdl.dropIndex(request.indexName(), tableName)).endOfStatement();
write.dropAllForeignKeys().appendStatement(platformDdl.alterTableDropForeignKey(tableName, request.fkName()));
if (hasValue(request.indexName())) {
write.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(request.indexName(), tableName));
}
write.dropAllForeignKeys().end();
@@ -486,10 +475,7 @@ public class BaseTableDdl implements TableDdl {
protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException {
String fkConstraint = platformDdl.alterTableAddForeignKey(request);
if (fkConstraint != null && !fkConstraint.isEmpty()) {
buffer.append(fkConstraint).endOfStatement();
}
buffer.appendStatement(platformDdl.alterTableAddForeignKey(request));
}
protected void appendColumns(String[] columns, DdlBuffer buffer) throws IOException {
@@ -503,13 +489,12 @@ public class BaseTableDdl implements TableDdl {
buffer.append(")");
}
/**
* Add 'drop table' statement to the buffer.
*/
protected void dropTable(DdlBuffer buffer, String tableName) throws IOException {
buffer.append(platformDdl.dropTable(tableName)).endOfStatement();
buffer.appendStatement(platformDdl.dropTable(tableName));
}
/**
@@ -517,43 +502,16 @@ public class BaseTableDdl implements TableDdl {
*/
protected void dropSequence(DdlBuffer buffer, String sequenceName) throws IOException {
buffer.append(platformDdl.dropSequence(sequenceName)).endOfStatement();
}
/**
* Write all the check constraints.
*/
protected void writeCheckConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
List<Column> columns = createTable.getColumn();
for (Column column : columns) {
String checkConstraint = column.getCheckConstraint();
if (hasValue(checkConstraint)) {
writeCheckConstraint(apply, column, checkConstraint);
}
}
}
/**
* Write a check constraint.
*/
protected void writeCheckConstraint(DdlBuffer buffer, Column column, String checkConstraint) throws IOException {
String ckName = column.getCheckConstraintName();
buffer.append(",").newLine();
buffer.append(" constraint ").append(ckName);
buffer.append(" ").append(checkConstraint);
buffer.appendStatement(platformDdl.dropSequence(sequenceName));
}
protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
List<UniqueConstraint> uniqueConstraints = createTable.getUniqueConstraint();
boolean inlineUniqueWhenNull = platformDdl.isInlineUniqueWhenNullable();
for (UniqueConstraint uniqueConstraint : uniqueConstraints) {
for (UniqueConstraint uniqueConstraint : createTable.getUniqueConstraint()) {
if (inlineUniqueWhenNull) {
String uqName = uniqueConstraint.getName();
String[] columns = SplitColumns.split(uniqueConstraint.getColumnNames());
String[] columns = split(uniqueConstraint.getColumnNames());
apply.append(",").newLine();
apply.append(" constraint ").append(uqName).append(" unique");
appendColumns(columns, apply);
@@ -650,54 +608,34 @@ public class BaseTableDdl implements TableDdl {
@Override
public void generate(DdlWrite writer, CreateIndex createIndex) throws IOException {
String[] cols = SplitColumns.split(createIndex.getColumns());
writer.apply()
.append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols))
.endOfStatement();
writer.dropAll()
.append(platformDdl.dropIndex(createIndex.getIndexName(), createIndex.getTableName()))
.endOfStatement();
writer.apply().appendStatement(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), split(createIndex.getColumns())));
writer.dropAll().appendStatement(platformDdl.dropIndex(createIndex.getIndexName(), createIndex.getTableName()));
}
@Override
public void generate(DdlWrite writer, DropIndex dropIndex) throws IOException {
writer.apply()
.append(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName()))
.endOfStatement();
writer.apply().appendStatement(platformDdl.dropIndex(dropIndex.getIndexName(), dropIndex.getTableName()));
}
@Override
public void generate(DdlWrite writer, AddUniqueConstraint constraint) throws IOException {
if (DdlHelp.isDropConstraint(constraint.getColumnNames())) {
String ddl = platformDdl.alterTableDropUniqueConstraint(constraint.getTableName(), constraint.getConstraintName());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterTableDropUniqueConstraint(constraint.getTableName(), constraint.getConstraintName()));
} else {
String[] cols = SplitColumns.split(constraint.getColumnNames());
String[] nullableColumns = SplitColumns.split(constraint.getNullableColumns());
String ddl = platformDdl.alterTableAddUniqueConstraint(constraint.getTableName(), constraint.getConstraintName(), cols, nullableColumns);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
String[] cols = split(constraint.getColumnNames());
String[] nullableColumns = split(constraint.getNullableColumns());
writer.apply().appendStatement(platformDdl.alterTableAddUniqueConstraint(constraint.getTableName(), constraint.getConstraintName(), cols, nullableColumns));
}
}
@Override
public void generate(DdlWrite writer, AlterForeignKey alterForeignKey) throws IOException {
if (DdlHelp.isDropForeignKey(alterForeignKey.getColumnNames())) {
String ddl = platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(), alterForeignKey.getName());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(), alterForeignKey.getName()));
} else {
String ddl = platformDdl.alterTableAddForeignKey(new WriteForeignKey(alterForeignKey));
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterTableAddForeignKey(new WriteForeignKey(alterForeignKey)));
}
}
@@ -907,7 +845,7 @@ public class BaseTableDdl implements TableDdl {
String ddl = platformDdl.alterColumnBaseAttributes(alter);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
writer.apply().appendStatement(ddl);
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == HistorySupport.TRIGGER_BASED) {
// mysql and sql server column type change allowing nulls in the history table column
@@ -919,53 +857,41 @@ public class BaseTableDdl implements TableDdl {
String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
// write the apply to history table
writer.apply().append(histColumnDdl).endOfStatement();
writer.apply().appendStatement(histColumnDdl);
}
}
}
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnDefaultValue(alter.getTableName(), alter.getColumnName(), alter.getDefaultValue());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterColumnDefaultValue(alter.getTableName(), alter.getColumnName(), alter.getDefaultValue()));
}
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) throws IOException {
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName));
}
protected void addCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterTableAddCheckConstraint(alter.getTableName(), alter.getCheckConstraintName(), alter.getCheckConstraint());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterTableAddCheckConstraint(alter.getTableName(), alter.getCheckConstraintName(), alter.getCheckConstraint()));
}
protected void alterColumnNotnull(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isNotnull());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
writer.apply().appendStatement(platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isNotnull()));
}
protected void alterColumnType(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
writer.apply().appendStatement(ddl);
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
// apply same type change to matching column in the history table
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
writer.apply().append(ddl).endOfStatement();
writer.apply().appendStatement(ddl);
}
}
}
@@ -977,17 +903,13 @@ public class BaseTableDdl implements TableDdl {
protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply()
.append(platformDdl.alterTableDropForeignKey(alter.getTableName(), alter.getDropForeignKey()))
.endOfStatement();
writer.apply().appendStatement(platformDdl.alterTableDropForeignKey(alter.getTableName(), alter.getDropForeignKey()));
}
protected void alterColumnDropUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply()
.append(platformDdl.alterTableDropUniqueConstraint(alter.getTableName(), alter.getDropUnique()))
.endOfStatement();
writer.apply().appendStatement(platformDdl.alterTableDropUniqueConstraint(alter.getTableName(), alter.getDropUnique()));
}
protected void alterColumnAddUniqueOneToOneConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
@@ -1004,13 +926,9 @@ public class BaseTableDdl implements TableDdl {
String[] cols = {alter.getColumnName()};
boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
writer.apply()
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols))
.endOfStatement();
writer.apply().appendStatement(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols));
writer.dropAllForeignKeys()
.append(platformDdl.dropIndex(uqName, alter.getTableName()))
.endOfStatement();
writer.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(uqName, alter.getTableName()));
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import java.util.HashMap;
import java.util.Map;
/**
* Logical array type to ClickHouse array type conversion.
*/
class ClickHouseDbArray {
private static final Map<String, String> mapping = new HashMap<>();
static {
mapping.put("uuid[]", "Array(UUID)");
mapping.put("varchar[]", "Array(String)");
mapping.put("integer[]", "Array(UInt32)");
mapping.put("bigint[]", "Array(UInt64)");
}
/**
* Covert the 'logical' array type to a native one (for Postgres and Cockroach).
*/
static String logicalToNative(String logicalArrayType) {
int colonPos = logicalArrayType.indexOf(':');
if (colonPos > -1) {
logicalArrayType = logicalArrayType.substring(0, colonPos);
}
String clickHouseType = mapping.get(logicalArrayType);
if (clickHouseType == null) {
throw new IllegalStateException("No mapping for logical array type " + logicalArrayType);
}
return clickHouseType;
}
}
@@ -0,0 +1,91 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import java.io.IOException;
public class ClickHouseDdl extends PlatformDdl {
private static final String LOG_TABLE = "ENGINE = Log()";
public ClickHouseDdl(DatabasePlatform platform) {
super(platform);
this.includeStorageEngine = true;
this.identitySuffix = "";
}
@Override
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return new ClickHouseDdlHandler(serverConfig, this);
}
@Override
protected String convertArrayType(String logicalArrayType) {
return ClickHouseDbArray.logicalToNative(logicalArrayType);
}
/**
* Add an table storage engine to the create table statement.
*/
@Override
public void tableStorageEngine(DdlBuffer apply, String storageEngine) throws IOException {
if (storageEngine == null) {
// default to Log() table but really should all be explicit (need arguments for MergeTree etc)
storageEngine = LOG_TABLE;
}
apply.append(" ").append(storageEngine);
}
@Override
public String alterTableAddForeignKey(WriteForeignKey request) {
return null;
}
@Override
public String alterTableDropForeignKey(String tableName, String fkName) {
return null;
}
@Override
public String tableInlineForeignKey(WriteForeignKey request) {
return null;
}
@Override
public String dropIndex(String indexName, String tableName) {
return null;
}
@Override
public String createIndex(String indexName, String tableName, String[] columns) {
return null;
}
@Override
public String createCheckConstraint(String ckName, String checkConstraint) {
return null;
}
@Override
protected void writeColumnNotNull(DdlBuffer buffer) {
// do nothing
}
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
// do nothing
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
// do nothing
}
@Override
public boolean isInlineComments() {
return false;
}
}
@@ -0,0 +1,11 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
public class ClickHouseDdlHandler extends BaseDdlHandler {
public ClickHouseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
super(serverConfig, platformDdl, new ClickHouseTableDdl(serverConfig, platformDdl));
}
}
@@ -0,0 +1,28 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.migration.CreateTable;
public class ClickHouseTableDdl extends BaseTableDdl {
public ClickHouseTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) {
super(serverConfig, platformDdl);
}
@Override
protected void writePrimaryKeyConstraint(DdlBuffer buffer, String pkName, String[] pkColumns) {
// do nothing
}
@Override
protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) {
// do nothing
}
@Override
protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) {
// do nothing
}
}
@@ -14,6 +14,10 @@ import java.util.Collection;
*/
public class MySqlDdl extends PlatformDdl {
// check constraint support is disabled by default. See https://groups.google.com/forum/#!topic/ebean/luFN-2xBkUw
// this flag is for compatibility. Use it with care.
private static final boolean USE_CHECK_CONSTRAINT = Boolean.getBoolean("ebean.mysql.useCheckConstraint");
public MySqlDdl(DatabasePlatform platform) {
super(platform);
this.alterColumn = "modify";
@@ -48,11 +52,40 @@ public class MySqlDdl extends PlatformDdl {
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
}
@Override
public String createCheckConstraint(String ckName, String checkConstraint) {
if (USE_CHECK_CONSTRAINT) {
return super.createCheckConstraint(ckName, checkConstraint);
} else {
return null;
}
}
@Override
public String alterTableAddCheckConstraint(String tableName, String checkConstraintName, String checkConstraint) {
if (USE_CHECK_CONSTRAINT) {
return super.alterTableAddCheckConstraint(tableName, checkConstraintName, checkConstraint);
} else {
return null;
}
}
@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
// drop constraint not supported in MySQL 5.7 and 8.0 but starting with MariaDB 10.2.1 CHECK is evaluated
// TODO: Implement for MariaDB >= 10.2.1
return null;
// drop constraint not supported in MySQL 5.7 and 8.0 but starting with MariaDB
// 10.2.1 CHECK is evaluated
if (USE_CHECK_CONSTRAINT) {
StringBuilder sb = new StringBuilder();
// statement for MySQL >= 8.0.16
sb.append("/*!80016 alter table ").append(tableName);
sb.append(" drop check ").append(constraintName).append(" */;\n");
// statement for MariaDB >= 10.2.1
sb.append("/*M!100201 ");
sb.append(super.alterTableDropConstraint(tableName, constraintName));
sb.append(" */");
return sb.toString();
} else {
return null;
}
}
@Override
@@ -104,7 +137,6 @@ public class MySqlDdl extends PlatformDdl {
}
buffer.append(String.format(" comment '%s'", comment));
}
}
@Override
@@ -118,6 +118,8 @@ public class PlatformDdl {
*/
protected boolean inlineForeignKeys;
protected boolean includeStorageEngine;
protected final DbDefaultValue dbDefaultValue;
protected String fallbackArrayType = "varchar(1000)";
@@ -191,6 +193,13 @@ public class PlatformDdl {
return inlineComments;
}
/**
* Return true if the platform includes storage engine clause.
*/
public boolean isIncludeStorageEngine() {
return includeStorageEngine;
}
/**
* Return true if foreign key reference constraints need to inlined with create table.
* Ideally we don't do this as then the constraints are not named. Do this for SQLite.
@@ -204,10 +213,21 @@ public class PlatformDdl {
*/
public void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
apply.append(",");
}
apply.newLine();
writeColumnDefinition(apply, columns.get(i), useIdentity);
if (i < columns.size() - 1) {
apply.append(",");
}
for (Column column : columns) {
String checkConstraint = column.getCheckConstraint();
if (hasValue(checkConstraint)) {
checkConstraint = createCheckConstraint(maxConstraintName(column.getCheckConstraintName()), checkConstraint);
if (hasValue(checkConstraint)) {
apply.append(",").newLine();
apply.append(checkConstraint);
}
}
}
}
@@ -230,13 +250,27 @@ public class PlatformDdl {
}
}
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
buffer.append(" not null");
writeColumnNotNull(buffer);
}
// add check constraints later as we really want to give them a nice name
// so that the database can potentially provide a nice SQL error
}
/**
* Allow for platform overriding (e.g. ClickHouse).
*/
protected void writeColumnNotNull(DdlBuffer buffer) throws IOException {
buffer.append(" not null");
}
/**
* Returns the check constraint.
*/
public String createCheckConstraint(String ckName, String checkConstraint) {
return " constraint " + ckName + " " + checkConstraint;
}
/**
* Convert the DB column default literal to platform specific.
*/
@@ -409,13 +443,17 @@ public class PlatformDdl {
}
protected String translate(ConstraintMode mode) {
switch(mode) {
case SET_NULL: return "set null";
case SET_DEFAULT: return "set default";
case RESTRICT: return "restrict";
case CASCADE: return "cascade";
switch (mode) {
case SET_NULL:
return "set null";
case SET_DEFAULT:
return "set default";
case RESTRICT:
return "restrict";
case CASCADE:
return "cascade";
default:
throw new IllegalStateException("Unknown mode "+mode);
throw new IllegalStateException("Unknown mode " + mode);
}
}
@@ -465,16 +503,17 @@ public class PlatformDdl {
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
writeColumnNotNull(buffer);
}
buffer.append(addColumnSuffix);
buffer.endOfStatement();
// check constraints cannot be added in one statement for h2
if (!StringHelper.isNull(column.getCheckConstraint())) {
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(),
column.getCheckConstraint());
buffer.append(ddl).endOfStatement();
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
if (hasValue(ddl)) {
buffer.append(ddl).endOfStatement();
}
}
} else {
buffer.append(addColumnSuffix);
@@ -485,7 +524,7 @@ public class PlatformDdl {
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("alter table ").append(tableName).append(" ").append(dropColumn).append(" ").append(columnName)
.append(dropColumnSuffix).endOfStatement();
.append(dropColumnSuffix).endOfStatement();
}
/**
@@ -560,7 +599,7 @@ public class PlatformDdl {
}
protected void appendWithSpace(String content, StringBuilder buffer) {
if (content != null && !content.isEmpty()) {
if (hasValue(content)) {
buffer.append(" ").append(content);
}
}
@@ -593,6 +632,13 @@ public class PlatformDdl {
return updateNullWithDefault;
}
/**
* Return true if null or trimmed string is empty.
*/
protected boolean hasValue(String value) {
return value != null && !value.trim().isEmpty();
}
/**
* Null safe Boolean true test.
*/
@@ -607,6 +653,13 @@ public class PlatformDdl {
// do nothing by default (MySql only)
}
/**
* Add an table storage engine to the create table statement.
*/
public void tableStorageEngine(DdlBuffer apply, String storageEngine) throws IOException {
// do nothing by default
}
/**
* Add table comment as a separate statement (from the create table statement).
*/
@@ -643,10 +696,10 @@ public class PlatformDdl {
/**
* Shortens the given name to the maximum constraint name length of the platform in a deterministic way.
*
* <p>
* First, all vowels are removed, If the string is still to long, 31 bits are taken from the hash code
* of the string and base36 encoded (10 digits and 26 chars) string.
*
* <p>
* As 36^6 > 31^2, the resulting string is never longer as 6 chars.
*/
protected String maxConstraintName(String name) {
@@ -654,7 +707,7 @@ public class PlatformDdl {
int hash = name.hashCode() & 0x7FFFFFFF;
name = VowelRemover.trim(name, 4);
if (name.length() > platform.getMaxConstraintNameLength()) {
return name.substring(0, platform.getMaxConstraintNameLength()-7) + "_" + Integer.toString(hash, 36);
return name.substring(0, platform.getMaxConstraintNameLength() - 7) + "_" + Integer.toString(hash, 36);
}
}
return name;
@@ -77,6 +77,8 @@ public class CreateTable {
protected BigInteger sequenceAllocate;
@XmlAttribute(name = "pkName")
protected String pkName;
@XmlAttribute(name = "storageEngine")
protected String storageEngine;
@XmlAttribute(name = "tablespace")
protected String tablespace;
@XmlAttribute(name = "indexTablespace")
@@ -365,6 +367,26 @@ public class CreateTable {
this.pkName = value;
}
/**
* Gets the value of the storageEngine property.
*
* @return possible object is
* {@link String }
*/
public String getStorageEngine() {
return storageEngine;
}
/**
* Sets the value of the storageEngine property.
*
* @param value allowed object is
* {@link String }
*/
public void setStorageEngine(String value) {
this.storageEngine = value;
}
/**
* Gets the value of the tablespace property.
*
@@ -67,14 +67,14 @@ public class CurrentModel {
* Return true if the model contains tables that are partitioned.
*/
public boolean isTablePartitioning() {
return model.isTablePartitioning();
return read().isTablePartitioning();
}
/**
* Return the tables that have partitioning.
*/
public List<MTable> getPartitionedTables() {
return model.getPartitionedTables();
return read().getPartitionedTables();
}
private static DbConstraintNaming.MaxLength maxLength(SpiEbeanServer server, DbConstraintNaming naming) {
@@ -79,6 +79,8 @@ public class MTable {
*/
private String tablespace;
private String storageEngine;
/**
* Tablespace to use for indexes on this table.
*/
@@ -159,6 +161,7 @@ public class MTable {
this.name = createTable.getName();
this.pkName = createTable.getPkName();
this.comment = createTable.getComment();
this.storageEngine = createTable.getStorageEngine();
this.tablespace = createTable.getTablespace();
this.indexTablespace = createTable.getIndexTablespace();
this.withHistory = Boolean.TRUE.equals(createTable.isWithHistory());
@@ -244,6 +247,7 @@ public class MTable {
createTable.setPartitionMode(partitionMeta.getMode().name());
createTable.setPartitionColumn(partitionMeta.getProperty());
}
createTable.setStorageEngine(storageEngine);
createTable.setTablespace(tablespace);
createTable.setIndexTablespace(indexTablespace);
createTable.setSequenceName(sequenceName);
@@ -452,6 +456,10 @@ public class MTable {
this.comment = comment;
}
public void setStorageEngine(String storageEngine) {
this.storageEngine = storageEngine;
}
public String getTablespace() {
return tablespace;
}
@@ -36,6 +36,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
}
MTable table = new MTable(descriptor.getBaseTable());
table.setStorageEngine(descriptor.getStorageEngine());
table.setPartitionMeta(descriptor.getPartitionMeta());
table.setComment(descriptor.getDbComment());
if (descriptor.isHistorySupport()) {
@@ -41,7 +41,7 @@ public class BaseQueryTuner {
*/
private final boolean skipAll;
public BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) {
BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) {
this.server = server;
this.profilingListener = profilingListener;
this.mode = config.getMode();
@@ -84,12 +84,17 @@ public class BaseQueryTuner {
/**
* Auto tune the query and enable profiling.
*/
public boolean tuneQuery(SpiQuery<?> query) {
boolean tuneQuery(SpiQuery<?> query) {
if (skipAll || !tunableQuery(query)) {
return false;
}
if (query.getProfilingListener() != null) {
// profiling secondary query
return false;
}
if (!useTuning(query)) {
if (profiling) {
profiling(query, server.createCallStack());
@@ -138,6 +143,7 @@ public class BaseQueryTuner {
switch (type) {
case COUNT:
case ID_LIST:
case UPDATE:
case DELETE:
case SUBQUERY:
return false;
@@ -103,8 +103,7 @@ public class ProfileManager implements ProfilingListener {
private ProfileOrigin getProfileOrigin(ObjectGraphOrigin originQueryPoint) {
synchronized (monitor) {
ProfileOrigin stats = profileMap.computeIfAbsent(originQueryPoint.getKey(), k -> new ProfileOrigin(originQueryPoint, queryTuningAddVersion, profilingBase, profilingRate));
return stats;
return profileMap.computeIfAbsent(originQueryPoint.getKey(), k -> new ProfileOrigin(originQueryPoint, queryTuningAddVersion, profilingBase, profilingRate));
}
}
@@ -98,7 +98,7 @@ public class ProfileOrigin {
}
}
private OrmQueryDetail buildDetail(BeanDescriptor<?> rootDesc) {
OrmQueryDetail buildDetail(BeanDescriptor<?> rootDesc) {
PathProperties pathProps = new PathProperties();
for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) {
@@ -107,8 +107,7 @@ public class ProfileOrigin {
OrmQueryDetail detail = new OrmQueryDetail();
Collection<Props> pathProperties = pathProps.getPathProps();
for (Props props : pathProperties) {
for (Props props : pathProps.getPathProps()) {
if (!props.isEmpty()) {
detail.fetch(props.getPath(), props.getPropertiesAsString(), null);
}
@@ -151,12 +150,8 @@ public class ProfileOrigin {
* Collect the usage information for from a instance for this node.
*/
public void collectUsageInfo(NodeUsageCollector profile) {
//logger.info("COLLECT USAGE {}", profile.toString());
if (!profile.isEmpty()) {
ProfileOriginNodeUsage nodeStats = getNodeStats(profile.getNode().getPath());
nodeStats.collectUsageInfo(profile);
getNodeStats(profile.getNode().getPath()).collectUsageInfo(profile);
}
}
@@ -55,23 +55,27 @@ public class ProfileOriginNodeUsage {
}
}
BeanProperty toOneIdProperty = null;
boolean addedToPath = false;
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.findPropertyFromPath(propName);
if (beanProp == null) {
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
} else {
if (beanProp instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProp;
String targetIdProp = assocProp.getTargetIdProperty();
String manyPath = SplitName.add(path, assocProp.getName());
pathProps.addToPath(manyPath, targetIdProp);
if (beanProp.isId()) {
// remember and maybe add ToOne property to parent path
toOneIdProperty = beanProp;
} else if (beanProp instanceof BeanPropertyAssoc<?>) {
// intentionally skip
} else {
//noinspection StatementWithEmptyBody
if (beanProp.isLob() && !beanProp.isFetchEager()) {
// AutoTune will not include Lob's marked FetchLazy
// (which is the default for Lob's so typical).
} else {
addedToPath = true;
pathProps.addToPath(path, beanProp.getName());
}
}
@@ -81,9 +85,16 @@ public class ProfileOriginNodeUsage {
if ((modified || addVersionProperty) && desc != null) {
BeanProperty versionProp = desc.getVersionProperty();
if (versionProp != null) {
addedToPath = true;
pathProps.addToPath(path, versionProp.getName());
}
}
if (toOneIdProperty != null && !addedToPath) {
// add ToOne property to parent path
ElPropertyValue assocOne = rootDesc.getElGetValue(path);
pathProps.addToPath(SplitName.parent(path), assocOne.getName());
}
}
}
@@ -144,15 +144,13 @@ public abstract class AbstractSqlQueryRequest {
* Prepare and execute the SQL using the Binder.
*/
public void executeSql(Binder binder, SpiQuery.Type type) throws SQLException {
startNano = System.nanoTime();
executeAsSql(binder);
}
protected void executeAsSql(Binder binder) throws SQLException {
startNano = System.nanoTime();
prepareSql();
Connection conn = trans.getInternalConnection();
pstmt = conn.prepareStatement(sql);
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform;
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
@@ -104,6 +105,9 @@ public class DatabasePlatformFactory {
if (dbName.equals("db2")) {
return new DB2Platform();
}
if (dbName.equals("clickhouse")) {
return new ClickHousePlatform();
}
if (dbName.equals("sqlite")) {
return new SQLitePlatform();
}
@@ -162,6 +166,8 @@ public class DatabasePlatformFactory {
return new SqlAnywherePlatform();
} else if (dbProductName.contains("hdb")) {
return new HanaPlatform();
} else if (dbProductName.contains("clickhouse")) {
return new ClickHousePlatform();
}
// use the standard one
@@ -28,7 +28,7 @@ public class DefaultCallStackFactory implements CallStackFactory {
// find the first non-avaje stackElement
for (; startIndex < stackTrace.length; startIndex++) {
if (!stackTrace[startIndex].getClassName().startsWith(IO_EBEAN)) {
if (!ignore(stackTrace[startIndex])) {
break;
}
}
@@ -51,6 +51,16 @@ public class DefaultCallStackFactory implements CallStackFactory {
return createCallStack(finalTrace);
}
private boolean ignore(StackTraceElement element) {
if (element.getClassName().startsWith(IO_EBEAN)) {
return true;
}
if (element.getMethodName().startsWith("_ebean_")) {
return true;
}
return false;
}
private CallStack createCallStack(StackTraceElement[] finalTrace) {
return new CallStack(finalTrace, finalTrace[0].hashCode(), pathHash(finalTrace));
}
@@ -10,6 +10,7 @@ import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanRequest;
import io.ebean.meta.ServerMetrics;
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +36,11 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
server.visitMetrics(visitor);
}
@Override
public ServerMetrics collectMetrics() {
return visitBasic();
}
@Override
public BasicMetricVisitor visitBasic() {
BasicMetricVisitor basic = new BasicMetricVisitor();
@@ -505,6 +505,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private void shutdownPlugins() {
if (serverConfig.isDumpMetricsOnShutdown()) {
new DumpMetrics(this, serverConfig.getDumpMetricsOptions()).dump();
}
for (Plugin plugin : serverPlugins) {
try {
plugin.shutdown();
@@ -1234,7 +1238,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
SpiOrmQueryRequest<T> request = createQueryRequest(spiQuery, t);
request.profileLocationById();
if (request.isUseDocStore()) {
return docStore().find(request);
}
@@ -1588,7 +1591,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private <T> List<T> findList(Query<T> query, Transaction t, boolean findOne) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
request.profileLocationAll();
request.resetBeanCacheAutoMode(findOne);
Object result = request.getFromQueryCache();
if (result != null) {
@@ -37,6 +37,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
super(server, query, null);
this.queryEngine = engine;
this.query = query;
query.obtainLocation();
}
/**
@@ -44,6 +45,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
*/
@Override
public void executeSql(Binder binder, SpiQuery.Type type) throws SQLException {
startNano = System.nanoTime();
SpiQuery<?> ormQuery = query.getOrmQuery();
if (ormQuery != null) {
ormQuery.setType(type);
@@ -0,0 +1,174 @@
package io.ebeaninternal.server.core;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetaOrmQueryMetric;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.ServerMetrics;
import io.ebean.meta.SortMetric;
import io.ebeaninternal.api.SpiEbeanServer;
import java.util.Comparator;
import java.util.List;
class DumpMetrics {
private final SpiEbeanServer server;
private final String options;
private final String nameFormat;
private final String nameFormatTimed;
private boolean dumpHash;
private boolean dumpSql;
private boolean dumpLoc;
private Comparator<MetaTimedMetric> sortBy = SortMetric.NAME;
DumpMetrics(SpiEbeanServer server, String options) {
this.server = server;
this.options = options;
int width = 0;
if (options != null) {
dumpLoc = options.contains("loc");
dumpSql = options.contains("sql");
dumpHash = options.contains("hash");
for (int i = 5; i < 10; i++) {
width = Math.max(width, optionWidth(i * 10));
}
for (String option : new String[]{"Total", "Count", "Mean", "Max"}) {
sortOption(option);
}
}
if (width == 0) {
width = 80;
}
nameFormat = "%1$-" + width + "s";
nameFormatTimed = "%1$-" + (width + 6) + "s";
}
private int optionWidth(int check) {
return options.contains("w" + check) ? check : 0;
}
private void sortOption(String option) {
if (options.contains("sort" + option)) {
sortBy = setSortOption(option);
}
}
private Comparator<MetaTimedMetric> setSortOption(String option) {
switch (option.toUpperCase()) {
case "TOTAL":
return SortMetric.TOTAL;
case "COUNT":
return SortMetric.COUNT;
case "MEAN":
return SortMetric.MEAN;
case "MAX":
return SortMetric.MAX;
}
return SortMetric.NAME;
}
void dump() {
out("-- Dumping metrics for " + server.getName() + " -- ");
ServerMetrics serverMetrics = server.getMetaInfoManager().collectMetrics();
for (MetaTimedMetric metric : serverMetrics.getTimedMetrics()) {
log(metric);
}
List<MetaOrmQueryMetric> ormQueryMetrics = serverMetrics.getOrmQueryMetrics();
if (!ormQueryMetrics.isEmpty()) {
out("\n-- ORM queries --");
ormQueryMetrics.sort(sortBy);
for (MetaOrmQueryMetric metric : ormQueryMetrics) {
logQuery(metric);
}
}
List<MetaQueryMetric> dtoQueryMetrics = serverMetrics.getDtoQueryMetrics();
if (!dtoQueryMetrics.isEmpty()) {
out("\n-- DTO queries --");
dtoQueryMetrics.sort(sortBy);
for (MetaQueryMetric metric : dtoQueryMetrics) {
logDtoQuery(metric);
}
}
}
private void out(String sb) {
System.out.println(sb);
}
private void logQuery(MetaOrmQueryMetric metric) {
StringBuilder sb = new StringBuilder();
appendQueryName(metric, sb);
appendCounters(metric, sb);
if (dumpHash) {
sb.append("\n hash:").append(metric.getQueryPlanHash());
}
appendProfileAndSql(metric, sb);
out(sb.toString());
}
private void logDtoQuery(MetaQueryMetric metric) {
StringBuilder sb = new StringBuilder();
appendQueryName(metric, sb);
appendCounters(metric, sb);
appendProfileAndSql(metric, sb);
out(sb.toString());
}
private void appendQueryName(MetaQueryMetric metric, StringBuilder sb) {
sb.append("query:").append(padName(metric.getName())).append(" ");
}
private void appendProfileAndSql(MetaQueryMetric metric, StringBuilder sb) {
ProfileLocation profileLocation = metric.getProfileLocation();
if (dumpLoc && profileLocation != null) {
sb.append("\n loc:").append(profileLocation.shortDescription());
}
if (dumpSql) {
sb.append(" \n\n sql:").append(metric.getSql()).append("\n\n");
}
}
private void log(MetaTimedMetric metric) {
StringBuilder sb = new StringBuilder();
sb.append(padNameTimed(metric.getName())).append(" ");
appendCounters(metric, sb);
out(sb.toString());
}
private void appendCounters(MetaTimedMetric timedMetric, StringBuilder sb) {
sb.append(" count:").append(pad(timedMetric.getCount()))
.append(" total:").append(pad(timedMetric.getTotal()))
.append(" mean:").append(pad(timedMetric.getMean()))
.append(" max:").append(pad(timedMetric.getMax()));
}
private String padName(String name) {
return String.format(nameFormat, name);
}
private String padNameTimed(String name) {
return String.format(nameFormatTimed, name);
}
private String pad(long value) {
return String.format("%1$-8s", value);
}
}
@@ -102,20 +102,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public void profileLocationById() {
if (query.getProfileLocation() == null) {
query.setProfileLocation(beanDescriptor.profileLocationById());
}
}
@Override
public void profileLocationAll() {
if (query.getProfileLocation() == null && query.isFindAll()) {
query.setProfileLocation(beanDescriptor.profileLocationAll());
}
}
@Override
public boolean isDeleteByStatement() {
if (!transaction.isPersistCascade() || beanDescriptor.isDeleteByStatement()) {
@@ -607,11 +607,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
transaction.registerDeleteBean(hash);
}
public void unregisterDeleteBean() {
Integer hash = getBeanHash();
transaction.unregisterDeleteBean(hash);
}
public boolean isRegisteredForDeleteBean() {
if (transaction == null) {
return false;
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.ClickHouseDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
@@ -50,6 +51,8 @@ public class PlatformDdlBuilder {
return new SqlServerDdl(platform);
case HANA:
return new HanaColumnStoreDdl(platform);
case CLICKHOUSE:
return new ClickHouseDdl(platform);
default:
return new PlatformDdl(platform);
}
@@ -162,16 +162,6 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
*/
boolean isUseDocStore();
/**
* Set profile location for "find by id" if not set.
*/
void profileLocationById();
/**
* Set profile location for "find all" if not set.
*/
void profileLocationAll();
/**
* Return true if delete by statement is allowed for this type given cascade rules etc.
*/
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
@@ -136,8 +135,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private final Map<String, String> namedQuery;
private final short profileBeanId;
private final ProfileLocation locationById;
private final ProfileLocation locationAll;
private final boolean multiValueSupported;
@@ -211,6 +208,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private final String draftTable;
private final PartitionMeta partitionMeta;
private final String storageEngine;
/**
* DB table comment.
@@ -447,8 +445,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.name = InternString.intern(deploy.getName());
this.baseTableAlias = "t0";
this.fullName = InternString.intern(deploy.getFullName());
this.locationById = ProfileLocation.createAt(fullName + ".byId");
this.locationAll = ProfileLocation.createAt(fullName + ".all");
this.profileBeanId = deploy.getProfileId();
this.beanType = deploy.getBeanType();
this.rootBeanType = PersistenceContextUtil.root(beanType);
@@ -492,6 +488,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.dependentTables = deploy.getDependentTables();
this.dbComment = deploy.getDbComment();
this.partitionMeta = deploy.getPartitionMeta();
this.storageEngine = deploy.getStorageEngine();
this.autoTunable = EntityType.ORM == entityType && (beanFinder == null);
// helper object used to derive lists of properties
@@ -580,20 +577,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
/**
* Return a location for "find by id".
*/
public ProfileLocation profileLocationById() {
return locationById;
}
/**
* Return a location for "find all".
*/
public ProfileLocation profileLocationAll() {
return locationAll;
}
/**
* Return the id used in profiling to identify the bean type.
*/
@@ -1970,10 +1953,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
query.setPersistenceContext(pc);
return query
// .select(getIdProperty().getName())
// we do not select the id because we
// probably have to load the entire bean
.setId(id).findOne();
// .select(getIdProperty().getName())
// we do not select the id because we
// probably have to load the entire bean
.setId(id).findOne();
}
/**
@@ -2874,6 +2857,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return partitionMeta;
}
/**
* Return the storage engine.
*/
public String getStorageEngine() {
return storageEngine;
}
/**
* Return the dependent tables for a view based entity.
* <p>
@@ -433,7 +433,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
} else {
for (XmRawSql sql : entityDeploy.getRawSql()) {
RawSqlBuilder builder = RawSqlBuilder.parse(sql.getQuery().getValue());
RawSqlBuilder builder;
try {
builder = RawSqlBuilder.parse(sql.getQuery().getValue());
} catch (RuntimeException e) {
builder = RawSqlBuilder.unparsed(sql.getQuery().getValue());
}
for (XmColumnMapping columnMapping : sql.getColumnMapping()) {
builder.columnMapping(columnMapping.getColumn(), columnMapping.getProperty());
}
@@ -136,6 +136,8 @@ public class DeployBeanDescriptor<T> {
private List<IndexDefinition> indexDefinitions;
private String storageEngine;
/**
* The base database table.
*/
@@ -275,6 +277,14 @@ public class DeployBeanDescriptor<T> {
return Modifier.isAbstract(beanType.getModifiers());
}
public void setStorageEngine(String storageEngine) {
this.storageEngine = storageEngine;
}
public String getStorageEngine() {
return storageEngine;
}
/**
* Set to true for @History entity beans that have history.
*/
@@ -315,7 +325,7 @@ public class DeployBeanDescriptor<T> {
this.partitionMeta = partitionMeta;
}
public PartitionMeta getPartitionMeta() {
public PartitionMeta getPartitionMeta() {
if (partitionMeta != null) {
DeployBeanProperty beanProperty = getBeanProperty(partitionMeta.getProperty());
if (beanProperty != null) {
@@ -10,6 +10,7 @@ import io.ebean.annotation.History;
import io.ebean.annotation.Index;
import io.ebean.annotation.InvalidateQueryCache;
import io.ebean.annotation.ReadAudit;
import io.ebean.annotation.StorageEngine;
import io.ebean.annotation.UpdateMode;
import io.ebean.annotation.View;
import io.ebean.config.TableName;
@@ -154,6 +155,11 @@ public class AnnotationClass extends AnnotationParser {
}
}
StorageEngine storage = AnnotationUtil.findAnnotationRecursive(cls, StorageEngine.class);
if (storage != null) {
descriptor.setStorageEngine(storage.value());
}
DbPartition partition = AnnotationUtil.findAnnotationRecursive(cls, DbPartition.class);
if (partition != null) {
descriptor.setPartitionMeta(new PartitionMeta(partition.mode(), partition.property()));
@@ -64,7 +64,7 @@ public class DeployCreateProperties {
* </p>
*/
private boolean ignoreFieldByName(String fieldName) {
if (fieldName.startsWith("_ebean_")) {
if (fieldName.startsWith("_ebean_") || fieldName.equals("_$targetDatabase")) {
// ignore Ebean internal fields
return true;
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.dto;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetricType;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.QueryPlanMetric;
@@ -14,6 +15,8 @@ public class DtoMappingRequest {
private final String label;
private final ProfileLocation profileLocation;
private final String sql;
private final boolean relaxedMode;
@@ -22,7 +25,8 @@ public class DtoMappingRequest {
public DtoMappingRequest(SpiDtoQuery query, String sql, DtoColumn[] columnMeta) {
this.type = query.getType();
this.label = query.getLabel();
this.label = query.getPlanLabel();
this.profileLocation = query.getProfileLocation();
this.sql = sql;
this.relaxedMode = query.isRelaxedMode();
this.columnMeta = columnMeta;
@@ -45,6 +49,6 @@ public class DtoMappingRequest {
}
public QueryPlanMetric createMetric() {
return MetricFactory.get().createQueryPlanMetric(MetricType.DTO, type, label, sql);
return MetricFactory.get().createQueryPlanMetric(MetricType.DTO, type, label, profileLocation, sql);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.loadcontext;
import io.ebean.FetchConfig;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
@@ -77,6 +78,20 @@ public abstract class DLoadBaseContext {
return (lazyBatchSize > 1) ? lazyBatchSize : defaultBatchSize;
}
/**
* If the parent has a query plan label then extend it with the path and
* set onto the secondary query.
*/
void setLabel(SpiQuery<?> query) {
String label = parent.getPlanLabel();
if (label != null) {
label += "_" + fullPath;
query.setLabel(label);
query.setProfileLocation(parent.getProfileLocation());
}
}
protected PersistenceContext getPersistenceContext() {
return parent.getPersistenceContext();
}
@@ -43,6 +43,7 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
setLabel(query);
parent.propagateQueryState(query, desc.isDocStoreMapped());
query.setParentNode(objectGraphNode);
query.setLazyLoadProperty(lazyLoadProperty);
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.loadcontext;
import io.ebean.CacheMode;
import io.ebean.ProfileLocation;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.CallStack;
import io.ebean.bean.EntityBeanIntercept;
@@ -12,6 +13,7 @@ import io.ebeaninternal.api.LoadSecondaryQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuerySecondary;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -54,7 +56,9 @@ public class DLoadContext implements LoadContext {
*/
private final String relativePath;
private final ObjectGraphOrigin origin;
private final boolean useProfiling;
private final String planLabel;
private final ProfileLocation profileLocation;
private final ProfilingListener profilingListener;
private final Map<String, ObjectGraphNode> nodePathMap = new HashMap<>();
@@ -83,7 +87,9 @@ public class DLoadContext implements LoadContext {
this.disableReadAudit = false;
this.includeSoftDeletes = false;
this.relativePath = null;
this.useProfiling = false;
this.planLabel = null;
this.profileLocation = null;
this.profilingListener = null;
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
}
@@ -109,7 +115,9 @@ public class DLoadContext implements LoadContext {
this.disableReadAudit = query.isDisableReadAudit();
this.disableLazyLoading = query.isDisableLazyLoading();
this.useBeanCache = query.getUseBeanCache();
this.useProfiling = query.getProfilingListener() != null;
this.profilingListener = query.getProfilingListener();
this.planLabel = query.getPlanLabel();
this.profileLocation = query.getProfileLocation();
ObjectGraphNode parentNode = query.getParentNode();
if (parentNode != null) {
@@ -126,6 +134,19 @@ public class DLoadContext implements LoadContext {
registerSecondaryQueries(secondaryQueries);
}
/**
* Return the query plan label of the origin query.
*/
public String getPlanLabel() {
return planLabel;
}
/**
* Return the profile location of the origin query.
*/
public ProfileLocation getProfileLocation() {
return profileLocation;
}
/**
* Register the +query and +lazy secondary queries with their appropriate LoadBeanContext or LoadManyContext.
@@ -211,10 +232,7 @@ public class DLoadContext implements LoadContext {
@Override
public ObjectGraphNode getObjectGraphNode(String path) {
ObjectGraphNode node = nodePathMap.computeIfAbsent(path, this::createObjectGraphNode);
return node;
return nodePathMap.computeIfAbsent(path, this::createObjectGraphNode);
}
private ObjectGraphNode createObjectGraphNode(String path) {
@@ -351,8 +369,8 @@ public class DLoadContext implements LoadContext {
if (disableReadAudit) {
query.setDisableReadAuditing();
}
if (useProfiling) {
query.setAutoTune(true);
if (profilingListener != null) {
query.setProfilingListener(profilingListener);
}
if (tenantId != null) {
query.setTenantId(tenantId);
@@ -59,6 +59,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
public void configureQuery(SpiQuery<?> query) {
setLabel(query);
parent.propagateQueryState(query, docStoreMapped);
query.setParentNode(objectGraphNode);
if (queryProps != null) {
@@ -882,7 +882,6 @@ public final class DefaultPersister implements Persister {
// bean to handle bi-directional cascading
request.registerDeleteBean();
deleteAssocMany(request);
request.unregisterDeleteBean();
unloadedForeignKeys = getDeleteUnloadedForeignKeys(request);
if (unloadedForeignKeys != null) {
@@ -5,14 +5,16 @@ import io.ebean.ProfileLocation;
/**
* Fixed / given location. Used internally for find by id and find all.
*/
class BasicProfileLocation implements ProfileLocation {
final class BasicProfileLocation implements ProfileLocation {
private final String location;
private final String shortDescription;
private final String label;
BasicProfileLocation(String location) {
this.location = location;
this.shortDescription = shortDesc(location);
this.label = UtilLocation.label(shortDescription);
}
@Override
@@ -30,6 +32,11 @@ class BasicProfileLocation implements ProfileLocation {
return location;
}
@Override
public String label() {
return label;
}
@Override
public String shortDescription() {
return shortDescription;
@@ -38,9 +45,9 @@ class BasicProfileLocation implements ProfileLocation {
private String shortDesc(String location) {
int lastPer = location.lastIndexOf('.');
if (lastPer > -1) {
lastPer = location.lastIndexOf('.', lastPer-1);
lastPer = location.lastIndexOf('.', lastPer - 1);
if (lastPer > -1) {
return location.substring(lastPer+1);
return location.substring(lastPer + 1);
}
}
return location;
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetricType;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.QueryPlanMetric;
@@ -22,8 +23,8 @@ public class DMetricFactory implements MetricFactory {
}
@Override
public QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, String sql) {
return new DQueryPlanMetric(new DQueryPlanMeta(type, label, sql), new DTimedMetric(metricType, label));
public QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, ProfileLocation profileLocation, String sql) {
return new DQueryPlanMetric(new DQueryPlanMeta(type, label, profileLocation, sql), new DTimedMetric(metricType, label));
}
}
@@ -15,6 +15,8 @@ class DProfileLocation implements ProfileLocation {
private String shortDescription;
private String label;
private final int lineNumber;
DProfileLocation() {
@@ -40,20 +42,29 @@ class DProfileLocation implements ProfileLocation {
@Override
public String obtain() {
// atomic assignment so happy with this
// atomic assignments so happy enough with this (racing but atomic)
if (location == null) {
location = create();
shortDescription = shortDesc(location);
final String loc = create();
final String shortDesc = shortDesc(loc);
label = UtilLocation.label(shortDesc);
shortDescription = shortDesc;
location = loc;
}
return location;
}
@Override
public String label() {
return label;
}
@Override
public String shortDescription() {
return shortDescription;
}
private String create() {
// relatively expensive but we only do it once per profile location
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (int i = 3; i < trace.length; i++) {
if (!trace[i].getClassName().startsWith(IO_EBEAN)) {
@@ -1,25 +1,43 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
class DQueryPlanMeta {
private final Class<?> type;
private final String label;
private final ProfileLocation profileLocation;
private final String name;
private final String sql;
DQueryPlanMeta(Class<?> type, String label, String sql) {
DQueryPlanMeta(Class<?> type, String label, ProfileLocation profileLocation, String sql) {
this.type = type;
this.label = label;
this.profileLocation = profileLocation;
this.sql = sql;
String name = type.getSimpleName();
if (label != null) {
name += "_" + label;
}
this.name = name;
}
public Class<?> getType() {
return type;
}
public String getName() {
return name;
}
public String getLabel() {
return label;
}
public ProfileLocation getProfileLocation() {
return profileLocation;
}
public String getSql() {
return sql;
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
@@ -60,6 +61,11 @@ class DQueryPlanMetric implements QueryPlanMetric {
return meta.getLabel();
}
@Override
public ProfileLocation getProfileLocation() {
return meta.getProfileLocation();
}
@Override
public String getSql() {
return meta.getSql();
@@ -67,7 +73,7 @@ class DQueryPlanMetric implements QueryPlanMetric {
@Override
public String getName() {
return stats.getName();
return meta.getName();
}
@Override
@@ -0,0 +1,25 @@
package io.ebeaninternal.server.profile;
final class UtilLocation {
static String label(String shortDescription) {
int pos = shortDescription.indexOf("(");
if (pos == -1) {
return shortDescription;
} else {
return trimInit(shortDescription.substring(0, pos));
}
}
/**
* Trim constructor init to be "safe" without greater than or less than chars.
*/
private static String trimInit(String desc) {
if (desc.endsWith("<init>")) {
// trim to make a safe label
return desc.substring(0, desc.length() - 6) + "init";
}
return desc;
}
}
@@ -135,14 +135,22 @@ class CQueryBuilder {
private <T> String buildDeleteSql(OrmQueryRequest<T> request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {
String alias = alias(rootTableAlias);
if (!sqlTree.isIncludeJoins()) {
// simple - delete from table ...
return aliasStrip(buildSql("delete", request, predicates, sqlTree).getSql());
if (dbPlatform.isSupportsDeleteTableAlias()) {
// delete from table <alias> ...
return aliasReplace(buildSql("delete", request, predicates, sqlTree).getSql(), alias);
} else if (dbPlatform.getPlatform() == Platform.MYSQL) {
return aliasReplace(buildSql("delete " + alias, request, predicates, sqlTree).getSql(), alias);
} else {
// simple - delete from table ...
return aliasStrip(buildSql("delete", request, predicates, sqlTree).getSql());
}
}
// wrap as - delete from table where id in (select id ...)
String sql = buildSql(null, request, predicates, sqlTree).getSql();
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
sql = aliasReplace(sql, alias(rootTableAlias));
sql = aliasReplace(sql, alias);
return sql;
}
@@ -425,7 +433,7 @@ class CQueryBuilder {
try {
// For SqlServer we need either "selectMethod=cursor" in the connection string or fetch explicitly a cursorable
// statement here by specifying ResultSet.CONCUR_UPDATABLE
PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, dbPlatform.isSupportsResultSetConcurrencyModeUpdatable() ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
PreparedStatement statement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, dbPlatform.isSupportsResultSetConcurrencyModeUpdatable() ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
predicates.bind(statement, connection);
ResultSet resultSet = statement.executeQuery();
@@ -702,21 +710,21 @@ class CQueryBuilder {
}
private String toSql(CountDistinctOrder orderBy) {
switch(orderBy) {
case ATTR_ASC:
return " order by r1.attribute_";
case ATTR_DESC:
return " order by r1.attribute_ desc";
case COUNT_ASC_ATTR_ASC:
return " order by count(*), r1.attribute_";
case COUNT_ASC_ATTR_DESC:
return " order by count(*), r1.attribute_ desc";
case COUNT_DESC_ATTR_ASC:
return " order by count(*) desc, r1.attribute_";
case COUNT_DESC_ATTR_DESC:
return " order by count(*) desc, r1.attribute_ desc";
default:
throw new IllegalArgumentException("Illegal enum: "+ orderBy);
switch (orderBy) {
case ATTR_ASC:
return " order by r1.attribute_";
case ATTR_DESC:
return " order by r1.attribute_ desc";
case COUNT_ASC_ATTR_ASC:
return " order by count(*), r1.attribute_";
case COUNT_ASC_ATTR_DESC:
return " order by count(*), r1.attribute_ desc";
case COUNT_DESC_ATTR_ASC:
return " order by count(*) desc, r1.attribute_";
case COUNT_DESC_ATTR_DESC:
return " order by count(*) desc, r1.attribute_ desc";
default:
throw new IllegalArgumentException("Illegal enum: " + orderBy);
}
}
@@ -65,6 +65,8 @@ public class CQueryPlan {
private final String label;
private final String name;
private final CQueryPlanKey planKey;
private final boolean rawSql;
@@ -72,6 +74,7 @@ public class CQueryPlan {
private final boolean rowNumberIncluded;
private final String sql;
private final String sqlHash;
private final String logWhereSql;
@@ -110,11 +113,13 @@ public class CQueryPlan {
this.planKey = request.getQueryPlanKey();
SpiQuery<?> query = request.getQuery();
this.profileLocation = query.getProfileLocation();
this.label = query.getLabel();
this.label = query.getPlanLabel();
this.name = deriveName(label, query.getType());
this.location = location();
this.autoTuned = query.isAutoTuned();
this.asOfTableCount = query.getAsOfTableCount();
this.sql = sqlRes.getSql();
this.sqlHash = md5Hash(sql);
this.rowNumberIncluded = sqlRes.isIncludesRowNumberColumn();
this.sqlTree = sqlTree;
this.rawSql = rawSql;
@@ -135,12 +140,14 @@ public class CQueryPlan {
this.beanType = request.getBeanDescriptor().getBeanType();
SpiQuery<?> query = request.getQuery();
this.profileLocation = query.getProfileLocation();
this.label = query.getLabel();
this.label = query.getPlanLabel();
this.name = deriveName(label, query.getType());
this.location = location();
this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
this.autoTuned = false;
this.asOfTableCount = 0;
this.sql = sql;
this.sqlHash = md5Hash(sql);
this.sqlTree = sqlTree;
this.rawSql = rawSql;
this.rowNumberIncluded = rowNumberIncluded;
@@ -151,6 +158,16 @@ public class CQueryPlan {
this.bindCapture = initBindCapture(server.getServerConfig(), query);
}
private String deriveName(String label, SpiQuery.Type type) {
if (label == null) {
return beanType.getSimpleName() + "." + type.label();
}
if (label.startsWith(beanType.getSimpleName())) {
return label;
}
return beanType.getSimpleName() + "_" + label;
}
private CQueryBindCapture initBindCapture(ServerConfig serverConfig, SpiQuery<?> query) {
if (serverConfig.isCollectQueryPlans() && !query.getType().isUpdate()) {
return new CQueryBindCapture(this, PlatformQueryPlan.getLogger(serverConfig.getDatabasePlatform().getPlatform()));
@@ -189,6 +206,10 @@ public class CQueryPlan {
return label;
}
public String getName() {
return name;
}
public String getLocation() {
return location;
}
@@ -245,21 +266,25 @@ public class CQueryPlan {
private String calcAuditQueryKey() {
// rawSql needs to include the MD5 hash of the sql
return rawSql ? planKey.getPartialKey() + "_" + getSqlMd5Hash() : planKey.getPartialKey();
return rawSql ? planKey.getPartialKey() + "_" + sqlHash : planKey.getPartialKey();
}
/**
* Return the MD5 hash of the underlying sql.
* Return the MD5 hash of the sql.
*/
private String getSqlMd5Hash() {
private String md5Hash(String sql) {
try {
return Md5.hash(sql);
} catch (Exception e) {
logger.error("Failed to MD5 hash the rawSql query", e);
logger.error("Failed to MD5 hash the query", e);
return "error";
}
}
public String getSqlHash() {
return sqlHash;
}
public String getSql() {
return sql;
}
@@ -164,7 +164,7 @@ public final class CQueryPlanStats {
@Override
public String toString() {
return "location:" + getLocation() + " metrics:" + metrics + " sql:" + getSql();
return "label:" + getLabel() + " location:" + getLocation() + " metrics:" + metrics + " sql:" + getSql();
}
@Override
@@ -184,7 +184,7 @@ public final class CQueryPlanStats {
@Override
public String getName() {
return queryPlan.getLabel();
return queryPlan.getName();
}
@Override
@@ -239,7 +239,7 @@ public final class CQueryPlanStats {
@Override
public String getQueryPlanHash() {
return queryPlan.getPlanKey().toString();
return queryPlan.getSqlHash();
}
@Override
@@ -26,7 +26,7 @@ public abstract class QueryPlanLogger {
}
protected DQueryPlanOutput createPlan(CQueryPlan plan, String bind, String planString) {
return new DQueryPlanOutput(plan.getBeanType(), plan.getLabel(), plan.getSql(), bind, planString);
return new DQueryPlanOutput(plan.getBeanType(), plan.getName(), plan.getSql(), bind, planString);
}
DQueryPlanOutput readQueryPlanBasic(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.DtoQuery;
import io.ebean.ProfileLocation;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -39,6 +40,8 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
private String label;
private ProfileLocation profileLocation;
/**
* Bind parameters when using the query language.
*/
@@ -52,6 +55,7 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
this.descriptor = descriptor;
this.ormQuery = ormQuery;
this.label = ormQuery.getLabel();
this.profileLocation = ormQuery.getProfileLocation();
}
/**
@@ -166,6 +170,35 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
return label;
}
@Override
public String getPlanLabel() {
if (label != null) {
return label;
}
if (profileLocation != null) {
return profileLocation.label();
}
return null;
}
@Override
public void obtainLocation() {
if (profileLocation != null) {
profileLocation.obtain();
}
}
@Override
public DtoQuery<T> setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
@Override
public int getFirstRow() {
return firstRow;
@@ -361,6 +361,17 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return label;
}
@Override
public String getPlanLabel() {
if (label != null) {
return label;
}
if (profileLocation != null) {
return profileLocation.label();
}
return null;
}
@Override
public Query<T> setLabel(String label) {
this.label = label;
@@ -121,7 +121,10 @@ public class OrmQueryDetail implements Serializable {
}
if (fetchPaths != null) {
for (OrmQueryProperties join : fetchPaths.values()) {
join.append(" fetch ", sb);
if (sb.length() > 0) {
sb.append(" ");
}
join.append("fetch ", sb);
}
}
return sb.toString();
@@ -294,7 +294,7 @@ public class OrmQueryProperties implements Serializable {
sb.append(path).append(" ");
}
if (!isEmpty()) {
sb.append("(").append(rawProperties).append(") ");
sb.append("(").append(rawProperties).append(")");
}
return sb.toString();
}
@@ -194,11 +194,6 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
throw new IllegalStateException(notExpectedMessage);
}
@Override
public void unregisterDeleteBean(Integer persistedBean) {
throw new IllegalStateException(notExpectedMessage);
}
/**
* Return true if this is a bean that has already been saved/deleted.
*/
@@ -436,16 +436,6 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
deletingBeansHash.add(persistingBean);
}
/**
* Unregister the persisted bean.
*/
@Override
public void unregisterDeleteBean(Integer persistedBean) {
if (deletingBeansHash != null) {
deletingBeansHash.remove(persistedBean);
}
}
/**
* Return true if this is a bean that has already been saved/deleted.
*/
@@ -123,11 +123,6 @@ class NoTransaction implements SpiTransaction {
}
@Override
public void unregisterDeleteBean(Integer hash) {
}
@Override
public boolean isRegisteredDeleteBean(Integer hash) {
return false;
@@ -154,6 +154,8 @@ public final class DefaultTypeManager implements TypeManager {
private final boolean offlineMigrationGeneration;
private final EnumType defaultEnumType;
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
/**
@@ -201,6 +203,8 @@ public final class DefaultTypeManager implements TypeManager {
this.offlineMigrationGeneration = DbOffline.isGenerateMigration();
this.defaultEnumType = config.getDefaultEnumType();
initialiseStandard(jsonDateTime, config);
initialiseJavaTimeTypes(jsonDateTime, config);
initialiseJodaTypes(jsonDateTime, config);
@@ -645,8 +649,13 @@ public final class DefaultTypeManager implements TypeManager {
private ScalarTypeEnum<?> createEnumScalarTypePerSpec(Class<?> enumType, EnumType type) {
if (type == null) {
// default as per spec is ORDINAL
return new ScalarTypeEnumStandard.OrdinalEnum(enumType);
if(defaultEnumType == EnumType.ORDINAL) {
return new ScalarTypeEnumStandard.OrdinalEnum(enumType);
} else {
return new ScalarTypeEnumStandard.StringEnum(enumType);
}
} else if (type == EnumType.ORDINAL) {
return new ScalarTypeEnumStandard.OrdinalEnum(enumType);
@@ -18,13 +18,53 @@ abstract class ScalarTypeArrayBase<T> extends ScalarTypeJsonCollection<T> {
return null;
} else {
try {
return fromArray((Object[]) array.getArray());
return fromArray(convertArray(array.getArray()));
} finally {
array.free();
}
}
}
private Object[] convertArray(Object array) {
if (array instanceof Object[]) {
return (Object[]) array;
}
if (array instanceof long[]) {
return convertLongs((long[]) array);
}
if (array instanceof int[]) {
return convertInts((int[]) array);
}
if (array instanceof double[]) {
return convertDoubles((double[]) array);
}
throw new IllegalArgumentException("Unable to convert array " + array);
}
private Object[] convertLongs(long[] o) {
Long[] list = new Long[o.length];
for (int i = 0; i < o.length; i++) {
list[i] = o[i];
}
return list;
}
private Object[] convertInts(int[] o) {
Integer[] list = new Integer[o.length];
for (int i = 0; i < o.length; i++) {
list[i] = o[i];
}
return list;
}
private Object[] convertDoubles(double[] o) {
Double[] list = new Double[o.length];
for (int i = 0; i < o.length; i++) {
list[i] = o[i];
}
return list;
}
protected abstract T fromArray(Object[] array1);
}
@@ -27,7 +27,7 @@ public class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate<LocalDate> {
@Override
public LocalDate convertFromDate(Date date) {
return new LocalDate(date.getTime());
return LocalDate.fromDateFields(date);
}
@Override
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class Md5 {
public final class Md5 {
/**
* Return the MD5 hash of the underlying sql.
@@ -12,8 +12,7 @@ public class Md5 {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(content.getBytes(StandardCharsets.UTF_8));
return digestToHex(digest);
return digestToHex(md.digest(content.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new RuntimeException("MD5 hashing failed", e);
}
@@ -116,6 +116,7 @@
<xsd:attribute name="sequenceInitial" type="xsd:positiveInteger"/>
<xsd:attribute name="sequenceAllocate" type="xsd:positiveInteger"/>
<xsd:attribute name="pkName" type="xsd:string"/>
<xsd:attribute name="storageEngine" type="xsd:string"/>
<xsd:attributeGroup ref="tablespaceAttributes"/>
</xsd:complexType>
</xsd:element>
+8 -4
View File
@@ -2,9 +2,9 @@ package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.ServerMetrics;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
@@ -64,12 +64,12 @@ public abstract class BaseTestCase {
server().getMetaInfoManager().resetAllMetrics();
}
protected BasicMetricVisitor visitMetricsBasic() {
return server().getMetaInfoManager().visitBasic();
protected ServerMetrics collectMetrics() {
return server().getMetaInfoManager().collectMetrics();
}
protected List<MetaTimedMetric> visitTimedMetrics() {
return visitMetricsBasic().getTimedMetrics();
return collectMetrics().getTimedMetrics();
}
protected List<MetaTimedMetric> sqlMetrics() {
@@ -170,6 +170,10 @@ public abstract class BaseTestCase {
return isH2() || isPostgres();
}
public boolean isPlatformSupportsDeleteTableAlias() {
return spiEbeanServer().getDatabasePlatform().isSupportsDeleteTableAlias();
}
public boolean isPersistBatchOnCascade() {
return spiEbeanServer().getDatabasePlatform().getPersistBatchOnCascade() != PersistBatch.NONE;
}
+53 -57
View File
@@ -1,12 +1,13 @@
package io.ebean;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.ServerMetrics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.tests.model.basic.Contact;
import org.tests.model.basic.ResetBasicData;
@@ -19,23 +20,25 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
@BeforeClass
public static void resetStats() {
Ebean.getDefaultServer().getMetaInfoManager().resetAllMetrics();
DB.getDefault().getMetaInfoManager().resetAllMetrics();
}
@AfterClass
public static void reportStats() {
BasicMetricVisitor basic = DB.getDefault().getMetaInfoManager().visitBasic();
for (MetaQueryMetric metric : basic.getDtoQueryMetrics()) {
ServerMetrics metrics = DB.getDefault().getMetaInfoManager().collectMetrics();
for (MetaQueryMetric metric : metrics.getDtoQueryMetrics()) {
System.out.println(metric);
}
System.out.println("-- transaction metrics --");
for (MetaTimedMetric metric : basic.getTimedMetrics()) {
for (MetaTimedMetric metric : metrics.getTimedMetrics()) {
System.out.println(metric);
}
}
@Ignore
private static final ProfileLocation loc0 = ProfileLocation.create();
@ForPlatform(Platform.H2)
@Test
public void testPlanHits() {
@@ -43,27 +46,27 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
resetAllMetrics();
String[] prefix = { "Bl", "B", "Red", "jim" };
String[] prefix = {"Bl", "B", "Red", "jim"};
for (String val : prefix) {
List<ContactDto> list = Ebean.find(Contact.class)
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where()
.istartsWith(concat("lastName", ", ", "firstName"), val).orderBy().asc("lastName").setMaxRows(10)
.asDto(ContactDto.class).setLabel("prefixLoop").findList();
System.out.println("List:" + list);
DB.find(Contact.class)
.setProfileLocation(loc0)
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where()
.istartsWith(concat("lastName", ", ", "firstName"), val).orderBy().asc("lastName").setMaxRows(10)
.asDto(ContactDto.class).setLabel("prefixLoop").findList();
}
BasicMetricVisitor basic = visitMetricsBasic();
ServerMetrics metrics = collectMetrics();
List<MetaQueryMetric> stats = basic.getDtoQueryMetrics();
List<MetaQueryMetric> stats = metrics.getDtoQueryMetrics();
for (MetaQueryMetric stat : stats) {
System.out.println(stat);
long meanMicros = stat.getMean();
assertThat(meanMicros).isLessThan(900_000);
assertThat(stat.getProfileLocation()).isSameAs(loc0);
}
assertThat(stats).hasSize(1);
assertThat(stats.get(0).getCount()).isEqualTo(3);
assertThat(stats.get(0).getCount()).isEqualTo(4);
}
@Test
@@ -73,11 +76,11 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
DtoQuery<ContactDto> query = Ebean.find(Contact.class)
// we must explicitly add the id property for DTO query (if we want it)
.select("id, email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").asDto(ContactDto.class).setLabel("explicitId")
.setRelaxedMode();
DtoQuery<ContactDto> query = DB.find(Contact.class)
// we must explicitly add the id property for DTO query (if we want it)
.select("id, email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").asDto(ContactDto.class).setLabel("explicitId")
.setRelaxedMode();
List<ContactDto> dtos = query.findList();
@@ -86,12 +89,11 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
for (ContactDto dto : dtos) {
assertThat(dto.getEmail()).isNotNull();
assertThat(dto.getFullName()).isNotNull();
System.out.println(dto);
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select t0.id, t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
@Test
@@ -101,9 +103,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
DtoQuery<ContactDto> query = Ebean.find(Contact.class)
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").asDto(ContactDto.class);
DtoQuery<ContactDto> query = DB.find(Contact.class)
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").asDto(ContactDto.class);
List<ContactDto> dtos = query.findList();
@@ -112,12 +114,11 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
for (ContactDto dto : dtos) {
assertThat(dto.getEmail()).isNotNull();
assertThat(dto.getFullName()).isNotNull();
System.out.println(dto);
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
@Test
@@ -127,27 +128,26 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactDto> contactDtos = Ebean.find(Contact.class).setLabel("emailFullName")
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").setMaxRows(10).asDto(ContactDto.class).findList();
List<ContactDto> contactDtos = DB.find(Contact.class).setLabel("emailFullName")
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").setMaxRows(10).asDto(ContactDto.class).findList();
assertThat(contactDtos).isNotEmpty();
for (ContactDto dto : contactDtos) {
assertThat(dto.getEmail()).isNotNull();
assertThat(dto.getFullName()).isNotNull();
System.out.println(dto);
}
List<String> sql = LoggedSqlCollector.stop();
if (isSqlServer()) {
assertThat(sql.get(0)).contains("select top 10 t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
} else {
assertThat(sql.get(0)).contains("select t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
}
@@ -158,14 +158,13 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactDto> contactDtos = Ebean.find(Contact.class)
.select("id, email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").setMaxRows(10).asDto(ContactDto.class).findList();
List<ContactDto> contactDtos = DB.find(Contact.class)
.select("id, email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").setMaxRows(10).asDto(ContactDto.class).findList();
assertThat(contactDtos).isNotEmpty();
for (ContactDto dto : contactDtos) {
System.out.println(dto);
assertThat(dto.getId()).isNotNull();
assertThat(dto.getFullName()).isNotNull();
assertThat(dto.getEmail()).isNotNull();
@@ -174,11 +173,11 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
if (isSqlServer()) {
assertThat(sql.get(0)).contains("select top 10 t0.id, t0.email, "
+ concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
+ concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
} else {
assertThat(sql.get(0)).contains("select t0.id, t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
}
@@ -189,22 +188,20 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactDto> contactDtos = Ebean.find(Contact.class)
.select(concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("lastName").orderBy()
.asc("lastName").asDto(ContactDto.class).setFirstRow(2).setMaxRows(5).findList();
List<ContactDto> contactDtos = DB.find(Contact.class)
.select(concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("lastName").orderBy()
.asc("lastName").asDto(ContactDto.class).setFirstRow(2).setMaxRows(5).findList();
assertThat(contactDtos).isNotEmpty();
for (ContactDto dto : contactDtos) {
System.out.println(dto);
assertThat(dto.getFullName()).isNotNull();
assertThat(dto.getId()).isNull();
assertThat(dto.getEmail()).isNull();
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0))
.contains("select " + concat("t0.last_name", ", ", "t0.first_name") + " fullName from contact t0 where");
assertThat(sql.get(0)).contains("select " + concat("t0.last_name", ", ", "t0.first_name") + " fullName from contact t0 where");
}
@Test
@@ -214,9 +211,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactTotals> contactDtos = Ebean.find(Contact.class).select("lastName, count(*) as totalCount").where()
.isNotNull("lastName").having().gt("count(*)", 1).orderBy().desc("count(*)").asDto(ContactTotals.class)
.findList();
List<ContactTotals> contactDtos = DB.find(Contact.class).select("lastName, count(*) as totalCount").where()
.isNotNull("lastName").having().gt("count(*)", 1).orderBy().desc("count(*)").asDto(ContactTotals.class)
.findList();
assertThat(contactDtos).isNotEmpty();
@@ -226,8 +223,7 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains(
"select t0.last_name, count(*) totalCount from contact t0 where t0.last_name is not null group by t0.last_name having count(*) > ?");
assertThat(sql.get(0)).contains("select t0.last_name, count(*) totalCount from contact t0 where t0.last_name is not null group by t0.last_name having count(*) > ?");
}
@Test
@@ -235,8 +231,8 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
ResetBasicData.reset();
List<ContactTotals> contactDtos = Ebean.find(Contact.class).select("lastName, count(*) as totalCount").where()
.isNotNull("lastName").asDto(ContactTotals.class).findList();
List<ContactTotals> contactDtos = DB.find(Contact.class).select("lastName, count(*) as totalCount").where()
.isNotNull("lastName").asDto(ContactTotals.class).findList();
assertThat(contactDtos).isNotEmpty();
}
+15 -2
View File
@@ -4,6 +4,7 @@ import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.ServerMetrics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
@@ -24,6 +25,7 @@ public class DtoQueryTest extends BaseTestCase {
public void dto_findList_constructorMatch() {
ResetBasicData.reset();
resetAllMetrics();
DtoQuery<DCust> dtoQuery = server().findDto(DCust.class, "select id, name from o_customer");
@@ -31,6 +33,17 @@ public class DtoQueryTest extends BaseTestCase {
log.info(list.toString());
assertThat(list).isNotEmpty();
ServerMetrics metrics = collectMetrics();
List<MetaQueryMetric> stats = metrics.getDtoQueryMetrics();
for (MetaQueryMetric stat : stats) {
long meanMicros = stat.getMean();
assertThat(meanMicros).isLessThan(900_000);
}
assertThat(stats).hasSize(1);
assertThat(stats.get(0).getCount()).isEqualTo(1);
}
@Test
@@ -188,7 +201,7 @@ public class DtoQueryTest extends BaseTestCase {
MetaQueryMetric queryMetric = stats.get(0);
assertThat(queryMetric.getLabel()).isEqualTo("basic");
assertThat(queryMetric.getCount()).isEqualTo(3);
assertThat(queryMetric.getName()).isEqualTo("basic");
assertThat(queryMetric.getName()).isEqualTo("DCust_basic");
server().findDto(DCust.class, "select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name")
@@ -196,7 +209,7 @@ public class DtoQueryTest extends BaseTestCase {
.setParameter("name", "rob")
.findList();
BasicMetricVisitor metric2 = server().getMetaInfoManager().visitBasic();
ServerMetrics metric2 = server().getMetaInfoManager().collectMetrics();
stats = metric2.getDtoQueryMetrics();
assertThat(stats).hasSize(2);
@@ -127,7 +127,7 @@ public class NamedDtoQueryTest extends BaseTestCase {
MetaQueryMetric queryMetric = stats.get(0);
assertThat(queryMetric.getLabel()).isEqualTo("basic");
assertThat(queryMetric.getCount()).isEqualTo(3);
assertThat(queryMetric.getName()).isEqualTo("basic");
assertThat(queryMetric.getName()).isEqualTo("DCust_basic");
server().createNamedDtoQuery(DCust.class, "findByName_c4")
+5 -5
View File
@@ -2,8 +2,8 @@ package io.ebean;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaOrmQueryMetric;
import io.ebean.meta.ServerMetrics;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.tests.model.basic.Country;
@@ -38,8 +38,8 @@ public class UpdateQueryTest extends BaseTestCase {
assertThat(query.getGeneratedSql()).contains("update o_customer set status=?, updtime=? where status = ? and id > ?");
BasicMetricVisitor basic = visitMetricsBasic();
List<MetaOrmQueryMetric> ormQueryMetrics = basic.getOrmQueryMetrics();
ServerMetrics metrics = collectMetrics();
List<MetaOrmQueryMetric> ormQueryMetrics = metrics.getOrmQueryMetrics();
assertThat(ormQueryMetrics).hasSize(1);
assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateActive");
@@ -68,8 +68,8 @@ public class UpdateQueryTest extends BaseTestCase {
assertThat(sql.get(0)).contains("update o_customer set status = status");
BasicMetricVisitor basic = visitMetricsBasic();
List<MetaOrmQueryMetric> ormQueryMetrics = basic.getOrmQueryMetrics();
ServerMetrics metrics = collectMetrics();
List<MetaOrmQueryMetric> ormQueryMetrics = metrics.getOrmQueryMetrics();
assertThat(ormQueryMetrics).hasSize(1);
assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateAll");
@@ -0,0 +1,5 @@
package io.ebean.server.type;
public enum MySex {
MALE, FEMALE
}
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.type.DefaultTypeManager;
import io.ebeaninternal.server.type.RsetDataReader;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeEnumStandard;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.ivo.Money;
@@ -95,6 +96,21 @@ public class TestTypeManager extends BaseTestCase {
}
@Test
public void testWithConfig() {
DefaultTypeManager typeManager1 = createTypeManager();
ScalarType<?> type1 = typeManager1.createEnumScalarType(MySex.class, null);
assertThat(type1 instanceof ScalarTypeEnumStandard.OrdinalEnum);
//
DefaultTypeManager typeManager2 = createTypeManagerDefaultEnumTypeString();
ScalarType<?> type2 = typeManager2.createEnumScalarType(MySex.class, null);
assertThat(type2 instanceof ScalarTypeEnumStandard.StringEnum);
//
DefaultTypeManager typeManager3 = createTypeManagerDefaultEnumTypeString();
ScalarType<?> type3 = typeManager3.createEnumScalarType(MySex.class, EnumType.ORDINAL);
assertThat(type3 instanceof ScalarTypeEnumStandard.OrdinalEnum);
}
private DefaultTypeManager createTypeManager() {
ServerConfig serverConfig = new ServerConfig();
@@ -106,6 +122,18 @@ public class TestTypeManager extends BaseTestCase {
return new DefaultTypeManager(serverConfig, bootupClasses);
}
private DefaultTypeManager createTypeManagerDefaultEnumTypeString() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setDatabasePlatform(new H2Platform());
serverConfig.setDefaultEnumType(EnumType.STRING);
BootupClasses bootupClasses = new BootupClasses();
bootupClasses.getAttributeConverters().add(MoneyTypeConverter.class);
return new DefaultTypeManager(serverConfig, bootupClasses);
}
/**
* Test double DataReader implementation.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
@@ -62,6 +63,23 @@ public class BaseTableDdlTest {
assertThat(ddl).contains("alter table mytable add column col_name varchar2(20)");
}
@Test
public void testAddColumn_withTypeConversion_clickHouseVarchar() throws IOException {
ClickHouseTableDdl ddlGen = new ClickHouseTableDdl(serverConfig, PlatformDdlBuilder.create(new ClickHousePlatform()));
DdlWrite write = new DdlWrite();
Column column = new Column();
column.setName("col_name");
column.setType("varchar(20)");
ddlGen.alterTableAddColumn(write.apply(), "mytable", column, false, false);
String ddl = write.apply().getBuffer();
assertThat(ddl).contains("alter table mytable add column col_name String");
}
@Test
public void testAlterColumnComment() throws IOException {
@@ -0,0 +1,24 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ClickHouseDbArrayTest {
@Test
public void logicalToNative() {
assertThat(ClickHouseDbArray.logicalToNative("uuid[]")).isEqualTo("Array(UUID)");
assertThat(ClickHouseDbArray.logicalToNative("varchar[]")).isEqualTo("Array(String)");
assertThat(ClickHouseDbArray.logicalToNative("integer[]")).isEqualTo("Array(UInt32)");
assertThat(ClickHouseDbArray.logicalToNative("bigint[]")).isEqualTo("Array(UInt64)");
}
@Test
public void logicalToNative_withFallbackDefined() {
assertThat(ClickHouseDbArray.logicalToNative("uuid[]:(1000)")).isEqualTo("Array(UUID)");
}
}
@@ -0,0 +1,143 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import org.junit.Test;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import static org.assertj.core.api.Assertions.assertThat;
public class ProfileOriginTest extends BaseTestCase {
private BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
@Test
public void buildDetail() {
NodeUsageCollector c = node("customer");
c.addUsed("id");
c.addUsed("name");
ProfileOrigin po = new ProfileOrigin(null, false, 1, 1);
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString().trim()).isEqualTo("fetch customer (name)");
}
@Test
public void buildDetail_selectFetch() {
NodeUsageCollector c = node("customer");
c.addUsed("id");
c.addUsed("name");
ProfileOrigin po = new ProfileOrigin(null, false, 1, 1);
po.collectUsageInfo(c);
c = node(null);
c.addUsed("orderDate");
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (name)");
}
@Test
public void buildDetail_expect_mergeFetchToSelect() {
NodeUsageCollector c = node("customer");
c.addUsed("id");
ProfileOrigin po = new ProfileOrigin(null, false, 1, 1);
po.collectUsageInfo(c);
c = node(null);
c.addUsed("orderDate");
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString().trim()).isEqualTo("select (orderDate,customer)");
}
@Test
public void buildDetail_expect_mergeFetchToParentFetch() {
ProfileOrigin po = new ProfileOrigin(null, false, 1, 1);
NodeUsageCollector c = node(null);
c.addUsed("orderDate");
c.addUsed("customer");
po.collectUsageInfo(c);
c = node("customer");
c.addUsed("billingAddress");
po.collectUsageInfo(c);
c = node("customer.billingAddress");
c.addUsed("id");
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
}
@Test
public void buildDetail_expect_mergeMulit() {
ProfileOrigin po = new ProfileOrigin(null, false, 1, 1);
//fetch details (id,orderQty,shipQty,unitPrice)
NodeUsageCollector c = node(null);
c.addUsed("details");
po.collectUsageInfo(c);
c = node("details");
c.addUsed("id");
c.addUsed("orderQty");
c.addUsed("shipQty");
c.addUsed("unitPrice");
c.addUsed("product");
po.collectUsageInfo(c);
//fetch details.product (id,name)
c = node("details.product");
c.addUsed("id");
c.addUsed("name");
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("fetch details (orderQty,shipQty,unitPrice) fetch details.product (name)");
}
private NodeUsageCollector node(String path) {
ObjectGraphNode node = new ObjectGraphNode((ObjectGraphOrigin)null, path);
return new NodeUsageCollector(node, null);
}
@Test
public void testQueries() {
ResetBasicData.reset();
//DB.createQuery(Order.class, "select (orderDate) fetch customer (billingAddress)").findList();
// we prefer this first query other the second one
DB.createQuery(Order.class, "select (orderDate,customer)").findList();
DB.createQuery(Order.class, "select (orderDate) fetch customer (id)").findList();
}
}
@@ -3,10 +3,14 @@ package io.ebeaninternal.server.expression;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Query;
import org.ebeantest.LoggedSqlCollector;
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.Test;
import org.tests.model.nofk.EUserNoFk;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@@ -26,6 +30,29 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
assertThat(sqlOf(query)).contains("select t0.id from o_customer t0 where not exists (select 1 from contact x where x.customer_id = t0.id");
}
@Test
public void deleteQuery_isEmpty() {
ResetBasicData.reset();
LoggedSqlCollector.start();
Ebean.find(EUserNoFk.class)
.where().isEmpty("files")
.delete();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
if (isPlatformSupportsDeleteTableAlias()) {
assertThat(sql.get(0)).contains("delete from euser_no_fk t0 where not exists (select 1 from efile_no_fk x where x.owner_user_id = t0.user_id)");
} else if (isMySql()) {
assertThat(sql.get(0)).contains("delete t0 from euser_no_fk t0 where not exists (select 1 from efile_no_fk x where x.owner_user_id = t0.user_id)");
} else {
assertThat(sql.get(0)).contains("delete from euser_no_fk where not exists (select 1 from efile_no_fk x where x.owner_user_id = user_id)");
}
}
@Test
public void isNotEmpty() {
@@ -15,6 +15,8 @@ public class BasicProfileLocationTest {
assertThat(loc.obtain()).endsWith(":12)");
assertThat(loc.shortDescription()).isEqualTo("NativeMethodAccessorImpl.invoke0(Native Method:12)");
assertThat(loc.label()).isEqualTo("NativeMethodAccessorImpl.invoke0");
}
@Test
@@ -23,6 +25,7 @@ public class BasicProfileLocationTest {
BasicProfileLocation loc = new BasicProfileLocation("com.foo.Bar.all");
assertThat(loc.obtain()).isEqualTo("com.foo.Bar.all");
assertThat(loc.shortDescription()).isEqualTo("Bar.all");
assertThat(loc.label()).isEqualTo("Bar.all");
}
@Test
@@ -31,5 +34,6 @@ public class BasicProfileLocationTest {
BasicProfileLocation loc = new BasicProfileLocation("foo.Bar.all");
assertThat(loc.obtain()).isEqualTo("foo.Bar.all");
assertThat(loc.shortDescription()).isEqualTo("Bar.all");
assertThat(loc.label()).isEqualTo("Bar.all");
}
}
@@ -0,0 +1,67 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricType;
import io.ebean.meta.SortMetric;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
public class SortMetricTest {
private Comparator<MetaTimedMetric> sortMetric = SortMetric.NAME;
@Test
public void compare_list() {
List<DTimeMetricStats> list = new ArrayList<>();
list.add(create("d"));
list.add(create("b"));
list.add(create("c"));
list.add(create(null));
list.add(create("a"));
list.sort(sortMetric);
String names = list.stream().map(DTimeMetricStats::getName).collect(Collectors.joining());
assertEquals("nullabcd", names);
}
@Test
public void compare_when_same() {
assertEquals(0, sortMetric.compare(create("foo"), create("foo")));
assertEquals(0, sortMetric.compare(create(null), create(null)));
}
@Test
public void compare_when_less() {
assertEquals(-1, sortMetric.compare(create("a"), create("b")));
assertEquals(-1, sortMetric.compare(create("foo"), create("goo")));
}
@Test
public void compare_when_more() {
assertEquals(1, sortMetric.compare(create("b"), create("a")));
assertEquals(1, sortMetric.compare(create("goo"), create("foo")));
}
@Test
public void compare_when_nulls() {
assertEquals(0, sortMetric.compare(create(null), create(null)));
assertEquals(1, sortMetric.compare(create("foo"), create(null)));
assertEquals(-1, sortMetric.compare(create(null), create("foo")));
}
private DTimeMetricStats create(String name) {
return new DTimeMetricStats(MetricType.L2, name, 0, 0, 0, 0, 0);
}
}
@@ -0,0 +1,15 @@
package io.ebeaninternal.server.profile;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class UtilLocationTest {
@Test
public void label() {
assertThat(UtilLocation.label("foo")).isEqualTo("foo");
assertThat(UtilLocation.label("ProfileLocationTest$Other.<init>(ProfileLocationTest.java:47)")).isEqualTo("ProfileLocationTest$Other.init");
}
}
@@ -62,14 +62,14 @@ public class OrmQueryPropertiesTest {
public void append_when_someProperties() {
OrmQueryProperties p1 = new OrmQueryProperties(null, "id,name");
assertThat(append("select ", p1)).isEqualTo("select (id,name) ");
assertThat(append("select ", p1)).isEqualTo("select (id,name)");
}
@Test
public void append_when_somePropertiesWithOptions() {
OrmQueryProperties p1 = new OrmQueryProperties(null, "id,name +cache");
assertThat(append("select ", p1)).isEqualTo("select (id,name +cache) ");
assertThat(append("select ", p1)).isEqualTo("select (id,name +cache)");
}
@Test
@@ -83,7 +83,7 @@ public class OrmQueryPropertiesTest {
public void append_when_path_and_somePropertiesWithOptions() {
OrmQueryProperties p1 = new OrmQueryProperties("customer", "id,name +cache");
assertThat(append("fetch ", p1)).isEqualTo("fetch customer (id,name +cache) ");
assertThat(append("fetch ", p1)).isEqualTo("fetch customer (id,name +cache)");
}
}

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