mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb43bd5dd | ||
|
|
9665de2dbe | ||
|
|
e2666f0c4c | ||
|
|
89c83c90d1 | ||
|
|
cdd25351a6 | ||
|
|
17e0413837 | ||
|
|
b04419d9c7 | ||
|
|
0453892ebd | ||
|
|
55493c5b7d | ||
|
|
eb2f7cd852 | ||
|
|
f44eb234c8 | ||
|
|
404b57ab21 | ||
|
|
a54f09ca4b | ||
|
|
a3bb909308 | ||
|
|
7eb443859e | ||
|
|
8b39788ae3 | ||
|
|
1ec132b7dd | ||
|
|
343a975073 | ||
|
|
944f7bd086 | ||
|
|
3639c2a540 | ||
|
|
a9cc8ed702 | ||
|
|
0351a15067 | ||
|
|
c7300a1707 | ||
|
|
c48008c4b2 | ||
|
|
e5d8804591 | ||
|
|
f6dc294bc2 | ||
|
|
692baf8bb6 | ||
|
|
e1c00f3856 | ||
|
|
cf173d0695 | ||
|
|
a3798c6ca3 | ||
|
|
7bcf1da823 | ||
|
|
2ccd8dabeb | ||
|
|
f9800dd59e | ||
|
|
fb3e8b9fc4 | ||
|
|
18be5989c7 | ||
|
|
be91e3f3f7 | ||
|
|
03a9aa3dec | ||
|
|
a2a301beda | ||
|
|
695e4f653b | ||
|
|
e84c75929c | ||
|
|
7592178b6b | ||
|
|
70d65b734f | ||
|
|
f9a3dfeda0 | ||
|
|
eb85931fe2 | ||
|
|
1745334737 | ||
|
|
ea92ddfd89 | ||
|
|
3400928798 | ||
|
|
4e665fd66f | ||
|
|
09cd2c12e2 | ||
|
|
e3e45929f0 | ||
|
|
2caeafaaa2 | ||
|
|
5cd21de174 | ||
|
|
78df595861 | ||
|
|
3c89a0537f | ||
|
|
282463c2e8 | ||
|
|
03bb7a4aaf | ||
|
|
e4fa031cd3 | ||
|
|
00d227c598 | ||
|
|
fb86cb91bc | ||
|
|
f11ee20b73 | ||
|
|
3665c5bfaf | ||
|
|
638446680c | ||
|
|
bd9bfa25be | ||
|
|
753d17a8d2 | ||
|
|
f5cfce3f44 | ||
|
|
1c6363e85c | ||
|
|
b8bcbd8af9 | ||
|
|
d70768277b | ||
|
|
b39b4a2e8c | ||
|
|
93b2f4035c | ||
|
|
31ee0c4582 | ||
|
|
6c2dad4851 |
@@ -9,11 +9,11 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.13.2</version>
|
||||
<version>6.15.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
<url>http://www.avaje.org</url>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -152,7 +152,7 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-agent</artifactId>
|
||||
<version>4.7.1</version>
|
||||
<version>4.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
<plugin>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
|
||||
<version>4.7.1</version>
|
||||
<version>4.8.1</version>
|
||||
<executions>
|
||||
<!-- Not going to enhance Model bean -->
|
||||
<execution>
|
||||
|
||||
@@ -45,7 +45,7 @@ final class DRawSqlColumnsParser {
|
||||
String colInfo = sqlSelect.substring(start, pos++);
|
||||
colInfo = colInfo.trim();
|
||||
|
||||
String[] split = colInfo.split(" ");
|
||||
String[] split = colInfo.split("\\s(?=[^\\)]*(?:\\(|$))");
|
||||
if (split.length > 1) {
|
||||
ArrayList<String> tmp = new ArrayList<String>(split.length);
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
@@ -82,11 +82,13 @@ final class DRawSqlColumnsParser {
|
||||
|
||||
private void nextComma() {
|
||||
boolean inQuote = false;
|
||||
int inbrackets = 0;
|
||||
while (pos < end) {
|
||||
char c = sqlSelect.charAt(pos);
|
||||
if (c == '\'') {
|
||||
inQuote = !inQuote;
|
||||
} else if (!inQuote && c == ',') {
|
||||
if (c == '\'') inQuote = !inQuote;
|
||||
else if (c == '(') inbrackets++;
|
||||
else if (c == ')') inbrackets--;
|
||||
else if (!inQuote && inbrackets == 0 && c == ',') {
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
|
||||
@@ -46,6 +46,7 @@ class DRawSqlParser {
|
||||
|
||||
private DRawSqlParser(String sqlString) {
|
||||
sqlString = sqlString.trim();
|
||||
sqlString = sqlString.replace('\n',' ');
|
||||
this.sql = sqlString;
|
||||
this.hasPlaceHolders = findAndRemovePlaceHolders();
|
||||
this.textParser = new SimpleTextParser(sqlString);
|
||||
@@ -120,8 +121,7 @@ class DRawSqlParser {
|
||||
// trim of distinct keyword
|
||||
String distinct = preWhereExprSql.substring(0, 9);
|
||||
if (!distinct.equalsIgnoreCase("distinct ")) {
|
||||
throw new RuntimeException("Expecting [" + preWhereExprSql
|
||||
+ "] to start with \"select distinct\"");
|
||||
throw new RuntimeException("Expecting [" + preWhereExprSql + "] to start with \"select distinct\"");
|
||||
}
|
||||
preWhereExprSql = preWhereExprSql.substring(9);
|
||||
}
|
||||
|
||||
@@ -1020,7 +1020,7 @@ public interface EbeanServer {
|
||||
SqlFutureList findFutureList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -1042,6 +1042,38 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
|
||||
@@ -245,7 +245,7 @@ public interface ExpressionList<T> extends Serializable {
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -265,6 +265,38 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
@@ -753,7 +785,7 @@ public interface ExpressionList<T> extends Serializable {
|
||||
* qualified) will still be translated to their physical name.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> raw(String raw, Object[] values);
|
||||
ExpressionList<T> raw(String raw, Object... values);
|
||||
|
||||
/**
|
||||
* Add raw expression with no parameters.
|
||||
|
||||
@@ -17,6 +17,33 @@ import java.util.concurrent.Future;
|
||||
* limit the result set.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <h4>Example: typical use including total row count</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // We want to find the first 50 new orders
|
||||
* // ... so we don't really need setFirstRow(0)
|
||||
*
|
||||
* PagedList<Order> pagedList
|
||||
* = ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .setFirstRow(0)
|
||||
* .setMaxRows(50)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // Optional: initiate the loading of the total
|
||||
* // row count in a background thread
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* // fetch and return the list in the foreground thread
|
||||
* List<Order> orders = pagedList.getList();
|
||||
*
|
||||
* // get the total row count (from the future)
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h4>Example: typical use including total row count</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -156,11 +183,15 @@ public interface PagedList<T> {
|
||||
|
||||
/**
|
||||
* Return the index position of this page. Zero based.
|
||||
* <p>
|
||||
* Note that if firstRows/maxRows is used rather than pageIndex/pageSize then
|
||||
* this always returns 0.
|
||||
* </p>
|
||||
*/
|
||||
int getPageIndex();
|
||||
|
||||
/**
|
||||
* Return the page size used for this query.
|
||||
* Return the page size used for this query. This is the same value as maxRows used by the query.
|
||||
*/
|
||||
int getPageSize();
|
||||
|
||||
|
||||
@@ -822,7 +822,7 @@ public interface Query<T> extends Serializable {
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -867,6 +867,36 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Set a named bind parameter. Named parameters have a colon to prefix the name.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A database table or column comment.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface DbComment {
|
||||
|
||||
/**
|
||||
* The database table or column comment.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
@@ -73,16 +73,6 @@ public interface EntityBean extends Serializable {
|
||||
*/
|
||||
EntityBeanIntercept _ebean_intercept();
|
||||
|
||||
/**
|
||||
* Create a copy of this entity bean.
|
||||
* <p>
|
||||
* This occurs when a bean is changed. The copy represents the bean as it was
|
||||
* initially (oldValues) before any changes where made. This is used for
|
||||
* optimistic concurrency control.
|
||||
* </p>
|
||||
*/
|
||||
Object _ebean_createCopy();
|
||||
|
||||
/**
|
||||
* Set the value of a field of an entity bean of this type.
|
||||
* <p>
|
||||
|
||||
@@ -21,6 +21,8 @@ public class AutoTuneConfig {
|
||||
|
||||
private double profilingRate = 0.01;
|
||||
|
||||
private int profilingUpdateFrequency;
|
||||
|
||||
private int garbageCollectionWait = 100;
|
||||
|
||||
private boolean skipCollectionOnShutdown;
|
||||
@@ -56,6 +58,20 @@ public class AutoTuneConfig {
|
||||
this.profilingFile = profilingFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the frequency in seconds the profiling should be collected and automatically applied to the tuning.
|
||||
*/
|
||||
public int getProfilingUpdateFrequency() {
|
||||
return profilingUpdateFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frequency in seconds the profiling should be collected and automatically applied to the tuning.
|
||||
*/
|
||||
public void setProfilingUpdateFrequency(int profilingUpdateFrequency) {
|
||||
this.profilingUpdateFrequency = profilingUpdateFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode used when autoTune has not been explicit defined on a
|
||||
* query.
|
||||
@@ -214,5 +230,6 @@ public class AutoTuneConfig {
|
||||
profilingBase = p.getInt("autoTune.profilingBase", profilingBase);
|
||||
profilingRate = p.getDouble("autoTune.profilingRate", profilingRate);
|
||||
profilingFile = p.get("autoTune.profilingFile", profilingFile);
|
||||
profilingUpdateFrequency = p.getInt("autoTune.profilingUpdateFrequency", profilingUpdateFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,393 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.config.dbplatform.DbPlatformName;
|
||||
import com.avaje.ebean.dbmigration.DbMigration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Configuration for the DB migration processing.
|
||||
*/
|
||||
public class DbMigrationConfig {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
|
||||
|
||||
/**
|
||||
* The database platform to generate migration DDL for.
|
||||
*/
|
||||
protected DbPlatformName platform;
|
||||
|
||||
/**
|
||||
* Set to true if the DB migration should be generated on server start.
|
||||
*/
|
||||
protected boolean generate;
|
||||
|
||||
/**
|
||||
* Set to true to suppress the output of the rollback script.
|
||||
*/
|
||||
protected boolean suppressRollback;
|
||||
|
||||
/**
|
||||
* The migration version name (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
protected String version;
|
||||
|
||||
/**
|
||||
* Description text that can be appended to the version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Resource path for the migration xml and sql.
|
||||
* Typically you would change 'app' to be a better/more unique.
|
||||
*/
|
||||
private String resourcePath = "dbmigration/app";
|
||||
protected String migrationPath = "dbmigration";
|
||||
|
||||
/**
|
||||
* Subdirectory the model xml files go into.
|
||||
*/
|
||||
protected String modelPath = "model";
|
||||
|
||||
/**
|
||||
* Subdirectory the drop ddl scripts go into.
|
||||
*/
|
||||
protected String dropPath = "drop";
|
||||
|
||||
/**
|
||||
* Subdirectory the rollback ddl scripts go into.
|
||||
*/
|
||||
protected String rollbackPath = "rollback";
|
||||
|
||||
/**
|
||||
* Apply script suffix.
|
||||
*/
|
||||
protected String applySuffix = ".sql";
|
||||
|
||||
/**
|
||||
* Default drop script suffix to ddl so that it isn't picked up by FlywayDb.
|
||||
*/
|
||||
protected String dropSuffix = ".drop.ddl";
|
||||
|
||||
/**
|
||||
* Default rollback script suffix to ddl so that it isn't picked up by FlywayDb.
|
||||
*/
|
||||
protected String rollbackSuffix = ".rollback.ddl";
|
||||
|
||||
protected String modelSuffix = ".model.xml";
|
||||
|
||||
/**
|
||||
* Return the DB platform to generate migration DDL for.
|
||||
*
|
||||
* We typically need to explicitly specify this as migration can often be generated
|
||||
* when running against H2.
|
||||
*/
|
||||
public DbPlatformName getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB platform to generate migration DDL for.
|
||||
*/
|
||||
public void setPlatform(DbPlatformName platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource path for db migrations.
|
||||
*/
|
||||
public String getResourcePath() {
|
||||
return resourcePath;
|
||||
public String getMigrationPath() {
|
||||
return migrationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource path for db migrations.
|
||||
* <p>
|
||||
* Typically this would be something like "dbmigration/myapp" where myapp gives it a
|
||||
* unique resource path in the case there are multiple EbeanServer applications in the
|
||||
* single classpath.
|
||||
* The default of "dbmigration" is reasonable in most cases. You may look to set this
|
||||
* to be something like "dbmigration/myapp" where myapp gives it a unique resource path
|
||||
* in the case there are multiple EbeanServer applications in the single classpath.
|
||||
* </p>
|
||||
*/
|
||||
public void setResourcePath(String resourcePath) {
|
||||
this.resourcePath = resourcePath;
|
||||
public void setMigrationPath(String migrationPath) {
|
||||
this.migrationPath = migrationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path for the model files (defaults to model).
|
||||
*/
|
||||
public String getModelPath() {
|
||||
return modelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relative path for the model files.
|
||||
*/
|
||||
public void setModelPath(String modelPath) {
|
||||
this.modelPath = modelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path for the drop ddl scripts (defaults to drop).
|
||||
*/
|
||||
public String getDropPath() {
|
||||
return dropPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relative path for the drop ddl scripts (defaults to drop).
|
||||
*/
|
||||
public void setDropPath(String dropPath) {
|
||||
this.dropPath = dropPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path for the rollback ddl scripts (defaults to rollback).
|
||||
*/
|
||||
public String getRollbackPath() {
|
||||
return rollbackPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relative path for the rollback ddl scripts (defaults to rollback).
|
||||
*/
|
||||
public void setRollbackPath(String rollbackPath) {
|
||||
this.rollbackPath = rollbackPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the model suffix (defaults to model.xml)
|
||||
*/
|
||||
public String getModelSuffix() {
|
||||
return modelSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model suffix.
|
||||
*/
|
||||
public void setModelSuffix(String modelSuffix) {
|
||||
this.modelSuffix = modelSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the rollback script should not be output.
|
||||
*/
|
||||
public boolean isSuppressRollback() {
|
||||
return suppressRollback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to suppress the output of the rollback script.
|
||||
*/
|
||||
public void setSuppressRollback(boolean suppressRollback) {
|
||||
this.suppressRollback = suppressRollback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the apply script suffix (defaults to sql).
|
||||
*/
|
||||
public String getApplySuffix() {
|
||||
return applySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the apply script suffix (defaults to sql).
|
||||
*/
|
||||
public void setApplySuffix(String applySuffix) {
|
||||
this.applySuffix = applySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the drop script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
|
||||
*/
|
||||
public String getDropSuffix() {
|
||||
return dropSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the drop script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
|
||||
*/
|
||||
public void setDropSuffix(String dropSuffix) {
|
||||
this.dropSuffix = dropSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rollback script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
|
||||
*/
|
||||
public String getRollbackSuffix() {
|
||||
return rollbackSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rollback script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
|
||||
*/
|
||||
public void setRollbackSuffix(String rollbackSuffix) {
|
||||
this.rollbackSuffix = rollbackSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration version.
|
||||
* <p>
|
||||
* Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration name.
|
||||
* <p>
|
||||
* Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model, rollback and drop paths to be empty such that all the migration files are generated
|
||||
* into a single directory.
|
||||
*/
|
||||
public void singleDirectory() {
|
||||
this.dropPath = "";
|
||||
this.rollbackPath = "";
|
||||
this.modelPath = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the PropertiesWrapper.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper properties) {
|
||||
resourcePath = properties.get("migration.resourcePath", resourcePath);
|
||||
|
||||
migrationPath = properties.get("migration.migrationPath", migrationPath);
|
||||
if (properties.getBoolean("migration.singleDirectory", false)) {
|
||||
singleDirectory();
|
||||
} else {
|
||||
modelPath = properties.get("migration.modelPath", modelPath);
|
||||
rollbackPath = properties.get("migration.rollbackPath", rollbackPath);
|
||||
dropPath = properties.get("migration.dropPath", dropPath);
|
||||
}
|
||||
applySuffix = properties.get("migration.applySuffix", applySuffix);
|
||||
dropSuffix = properties.get("migration.dropSuffix", dropSuffix);
|
||||
rollbackSuffix = properties.get("migration.rollbackSuffix", rollbackSuffix);
|
||||
modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
|
||||
|
||||
platform = properties.getEnum(DbPlatformName.class, "migration.platform", platform);
|
||||
suppressRollback = properties.getBoolean("migration.suppressRollback", suppressRollback);
|
||||
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the migration should be generated.
|
||||
* <p>
|
||||
* It is expected that when an environment variable <code>ddl.migration.enabled</code>
|
||||
* is set to <code>true</code> then the DB migration will generate the migration DDL.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isGenerateOnStart() {
|
||||
|
||||
// environment properties take precedence
|
||||
String envGenerate = readEnvironment("ddl.migration.generate");
|
||||
if (envGenerate != null) {
|
||||
return "true".equalsIgnoreCase(envGenerate.trim());
|
||||
}
|
||||
return generate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by EbeanServer on start.
|
||||
*
|
||||
* <p>
|
||||
* If enabled this generates the migration xml and DDL scripts.
|
||||
* </p>
|
||||
*/
|
||||
public void generateOnStart(EbeanServer server) {
|
||||
|
||||
if (isGenerateOnStart()) {
|
||||
if (platform == null) {
|
||||
logger.warn("No platform set for migration DDL generation");
|
||||
} else {
|
||||
// generate the migration xml and platform specific DDL
|
||||
DbMigration migration = new DbMigration(server);
|
||||
migration.setPlatform(platform);
|
||||
try {
|
||||
migration.generateMigration();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error generating DB migration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration version (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
public String getVersion() {
|
||||
String envVersion = readEnvironment("ddl.migration.version");
|
||||
if (!isEmpty(envVersion)) {
|
||||
return envVersion.trim();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration name which is short description text that can be appended to
|
||||
* the migration version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* </p>
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
* </p>
|
||||
*/
|
||||
public String getName() {
|
||||
String envName = readEnvironment("ddl.migration.name");
|
||||
if (!isEmpty(envName)) {
|
||||
return envName.trim();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system or environment property.
|
||||
*/
|
||||
protected String readEnvironment(String key) {
|
||||
|
||||
String val = System.getProperty(key);
|
||||
if (val == null) {
|
||||
val = System.getenv(key);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the string is null or empty.
|
||||
*/
|
||||
protected boolean isEmpty(String val) {
|
||||
return val == null || val.trim().isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ public class PropertiesWrapper {
|
||||
* Return a Enum property value.
|
||||
*/
|
||||
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
|
||||
String level = get(key, defaultValue.name());
|
||||
return Enum.valueOf(enumType, level.toUpperCase());
|
||||
String level = get(key, null);
|
||||
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -212,6 +212,12 @@ public class ServerConfig {
|
||||
|
||||
private boolean ddlRun;
|
||||
|
||||
private boolean ddlCreateOnly;
|
||||
|
||||
private String ddlInitSql;
|
||||
|
||||
private String ddlSeedSql;
|
||||
|
||||
private boolean useJtaTransactionManager;
|
||||
|
||||
/**
|
||||
@@ -1529,19 +1535,81 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run the DDL generation on startup.
|
||||
* Set to true to generate the "create all" DDL on startup.
|
||||
*
|
||||
* Typically we want this on when we are running tests locally (and often using H2)
|
||||
* and we want to create the full DB schema from scratch to run tests.
|
||||
*/
|
||||
public void setDdlGenerate(boolean ddlGenerate) {
|
||||
this.ddlGenerate = ddlGenerate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run the generated DDL on startup.
|
||||
* Set to true to run the generated "create all DDL" on startup.
|
||||
*
|
||||
* Typically we want this on when we are running tests locally (and often using H2)
|
||||
* and we want to create the full DB schema from scratch to run tests.
|
||||
*/
|
||||
public void setDdlRun(boolean ddlRun) {
|
||||
this.ddlRun = ddlRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the "drop all ddl" should be skipped.
|
||||
*
|
||||
* Typically we want to do this when using H2 (in memory) as our test database and the drop statements
|
||||
* are not required so skipping the drop table statements etc makes it faster with less noise in the logs.
|
||||
*/
|
||||
public boolean isDdlCreateOnly() {
|
||||
return ddlCreateOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the "drop all ddl" should be skipped.
|
||||
*
|
||||
* Typically we want to do this when using H2 (in memory) as our test database and the drop statements
|
||||
* are not required so skipping the drop table statements etc makes it faster with less noise in the logs.
|
||||
*/
|
||||
public void setDdlCreateOnly(boolean ddlCreateOnly) {
|
||||
this.ddlCreateOnly = ddlCreateOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL script to execute after the "create all" DDL has been run.
|
||||
* <p>
|
||||
* Typically this is a sql script that inserts test seed data when running tests.
|
||||
* Place a sql script in src/test/resources that inserts test seed data.
|
||||
* </p>
|
||||
*/
|
||||
public String getDdlSeedSql() {
|
||||
return ddlSeedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a SQL script to execute after the "create all" DDL has been run.
|
||||
* <p>
|
||||
* Typically this is a sql script that inserts test seed data when running tests.
|
||||
* Place a sql script in src/test/resources that inserts test seed data.
|
||||
* </p>
|
||||
*/
|
||||
public void setDdlSeedSql(String ddlSeedSql) {
|
||||
this.ddlSeedSql = ddlSeedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SQL script to execute before the "create all" DDL has been run.
|
||||
*/
|
||||
public String getDdlInitSql() {
|
||||
return ddlInitSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a SQL script to execute before the "create all" DDL has been run.
|
||||
*/
|
||||
public void setDdlInitSql(String ddlInitSql) {
|
||||
this.ddlInitSql = ddlInitSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DDL should be generated.
|
||||
*/
|
||||
@@ -2232,6 +2300,9 @@ public class ServerConfig {
|
||||
|
||||
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
|
||||
ddlRun = p.getBoolean("ddl.run", ddlRun);
|
||||
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
|
||||
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
|
||||
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
|
||||
|
||||
classes = getClasses(p);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class DB2SequenceIdGenerator extends SequenceIdGenerator {
|
||||
*/
|
||||
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
super(be, ds, seqName, batchSize);
|
||||
this.baseSql = "select nextval for " + seqName;
|
||||
this.baseSql = "values nextval for " + seqName;
|
||||
this.unionBaseSql = " union " + baseSql;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import com.avaje.ebean.dbmigration.model.CurrentModel;
|
||||
import com.avaje.ebean.dbmigration.model.MConfiguration;
|
||||
import com.avaje.ebean.dbmigration.model.MigrationModel;
|
||||
import com.avaje.ebean.dbmigration.model.ModelContainer;
|
||||
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
|
||||
import com.avaje.ebean.dbmigration.model.ModelDiff;
|
||||
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -57,6 +57,13 @@ public class DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
|
||||
|
||||
private static final String initialVersion = "1.0";
|
||||
|
||||
/**
|
||||
* Set to true if DbMigration run with online EbeanServer instance.
|
||||
*/
|
||||
protected final boolean online;
|
||||
|
||||
protected SpiEbeanServer server;
|
||||
|
||||
protected DbMigrationConfig migrationConfig;
|
||||
@@ -71,7 +78,19 @@ public class DbMigration {
|
||||
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
*/
|
||||
public DbMigration() {
|
||||
this.online = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create using online EbeanServer.
|
||||
*/
|
||||
public DbMigration(EbeanServer server) {
|
||||
this.online = true;
|
||||
setServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +144,9 @@ public class DbMigration {
|
||||
*/
|
||||
public void setPlatform(DatabasePlatform databasePlatform) {
|
||||
this.databasePlatform = databasePlatform;
|
||||
DbOffline.setPlatform(databasePlatform.getName());
|
||||
if (!online) {
|
||||
DbOffline.setPlatform(databasePlatform.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,16 +196,19 @@ public class DbMigration {
|
||||
public void generateMigration() throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
DbOffline.setRunningMigration();
|
||||
if (!online) {
|
||||
DbOffline.setRunningMigration();
|
||||
}
|
||||
|
||||
setDefaults();
|
||||
|
||||
try {
|
||||
MigrationModel migrationModel = new MigrationModel(migrationConfig.getResourcePath());
|
||||
ModelContainer migrated = migrationModel.read();
|
||||
int nextMajorVersion = migrationModel.getNextMajorVersion();
|
||||
|
||||
logger.info("next migration version {}", nextMajorVersion);
|
||||
File migrationDir = getMigrationDirectory();
|
||||
File modelDir = getModelDirectory(migrationDir);
|
||||
|
||||
MigrationModel migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
|
||||
ModelContainer migrated = migrationModel.read();
|
||||
|
||||
CurrentModel currentModel = new CurrentModel(server, constraintNaming);
|
||||
ModelContainer current = currentModel.read();
|
||||
@@ -200,46 +224,89 @@ public class DbMigration {
|
||||
// there were actually changes to write
|
||||
Migration dbMigration = diff.getMigration();
|
||||
|
||||
File writePath = getWritePath();
|
||||
logger.info("migration writing version {} to {}", nextMajorVersion, writePath.getAbsolutePath());
|
||||
writeMigrationXml(dbMigration, writePath, nextMajorVersion);
|
||||
String fullVersion = getFullVersion(migrationModel);
|
||||
|
||||
if (databasePlatform != null) {
|
||||
// writer needs the current model to provide table/column details for
|
||||
// history ddl generation (triggers, history tables etc)
|
||||
DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
PlatformDdlWriter writer = new PlatformDdlWriter(databasePlatform, serverConfig);
|
||||
writer.processMigration(dbMigration, write, writePath, nextMajorVersion);
|
||||
logger.info("generating migration:{}", fullVersion);
|
||||
if (!writeMigrationXml(dbMigration, modelDir, fullVersion)) {
|
||||
logger.warn("migration already exists, not generating DDL");
|
||||
|
||||
} else {
|
||||
if (databasePlatform != null) {
|
||||
// writer needs the current model to provide table/column details for
|
||||
// history ddl generation (triggers, history tables etc)
|
||||
DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
|
||||
writer.processMigration(dbMigration, write, migrationDir , fullVersion);
|
||||
}
|
||||
writeExtraPlatformDdl(fullVersion, currentModel, dbMigration, migrationDir);
|
||||
}
|
||||
|
||||
writeExtraPlatformDdl(nextMajorVersion, currentModel, dbMigration, writePath);
|
||||
|
||||
} finally {
|
||||
DbOffline.reset();
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full version for the migration being generated.
|
||||
*/
|
||||
private String getFullVersion(MigrationModel migrationModel) {
|
||||
|
||||
String version = migrationConfig.getVersion();
|
||||
if (version == null) {
|
||||
version = migrationModel.getNextVersion(initialVersion);
|
||||
}
|
||||
|
||||
String fullVersion = version;
|
||||
|
||||
String name = migrationConfig.getName();
|
||||
if (name != null) {
|
||||
fullVersion += "__" + toUnderScore(name);
|
||||
}
|
||||
return fullVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace spaces with underscores.
|
||||
*/
|
||||
private String toUnderScore(String name) {
|
||||
return name.replace(' ','_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write any extra platform ddl.
|
||||
*/
|
||||
protected void writeExtraPlatformDdl(int nextMajorVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
|
||||
PlatformDdlWriter platformWriter = new PlatformDdlWriter(pair.platform, serverConfig, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, writePath, nextMajorVersion);
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, writePath, fullVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(Pair pair) {
|
||||
return createDdlWriter(pair.platform, pair.prefix);
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform, String prefix) {
|
||||
return new PlatformDdlWriter(platform, serverConfig, prefix, migrationConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration xml.
|
||||
*/
|
||||
protected void writeMigrationXml(Migration dbMigration, File resourcePath, int migrationVersion) {
|
||||
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
|
||||
File file = new File(resourcePath, "v"+migrationVersion+".0.xml");
|
||||
String modelFile = fullVersion + migrationConfig.getModelSuffix();
|
||||
File file = new File(resourcePath, modelFile);
|
||||
if (file.exists()) {
|
||||
return false;
|
||||
}
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter();
|
||||
xmlWriter.write(dbMigration, file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,14 +327,13 @@ public class DbMigration {
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
protected File getWritePath() {
|
||||
protected File getMigrationDirectory() {
|
||||
|
||||
// path to src/main/resources in typical maven project
|
||||
File resourceRootDir = new File(pathToResources);
|
||||
String resourcePath = migrationConfig.getMigrationPath();
|
||||
|
||||
String resourcePath = migrationConfig.getResourcePath();
|
||||
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/myapp
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/model
|
||||
File path = new File(resourceRootDir, resourcePath);
|
||||
if (!path.exists()) {
|
||||
if (!path.mkdirs()) {
|
||||
@@ -277,6 +343,21 @@ public class DbMigration {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the model directory (relative to the migration directory).
|
||||
*/
|
||||
protected File getModelDirectory(File migrationDirectory) {
|
||||
String modelPath = migrationConfig.getModelPath();
|
||||
if (modelPath == null || modelPath.isEmpty()) {
|
||||
return migrationDirectory;
|
||||
}
|
||||
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
|
||||
if (!modelDir.exists() && !modelDir.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
|
||||
}
|
||||
return modelDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DatabasePlatform given the platform key.
|
||||
*/
|
||||
|
||||
@@ -1,47 +1,43 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.model.CurrentModel;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanPlugin;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.StringReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* Controls the generation of DDL and potentially runs the resulting scripts.
|
||||
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
|
||||
*
|
||||
* Typically the "Create All" DDL is executed for running tests etc and has nothing to do
|
||||
* with DB Migration (diff based) DDL.
|
||||
*/
|
||||
public class DdlGenerator implements SpiEbeanPlugin {
|
||||
public class DdlGenerator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DdlGenerator.class);
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private SpiEbeanServer server;
|
||||
|
||||
private boolean generateDdl;
|
||||
private boolean runDdl;
|
||||
private final boolean generateDdl;
|
||||
private final boolean runDdl;
|
||||
private final boolean createOnly;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropContent;
|
||||
private String createContent;
|
||||
|
||||
public void setup(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
public DdlGenerator(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
this.server = server;
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,9 +54,11 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
/**
|
||||
* Generate the DDL drop and create scripts if the properties have been set.
|
||||
*/
|
||||
public void generateDdl() {
|
||||
protected void generateDdl() {
|
||||
if (generateDdl) {
|
||||
writeDrop(getDropFileName());
|
||||
if (!createOnly) {
|
||||
writeDrop(getDropFileName());
|
||||
}
|
||||
writeCreate(getCreateFileName());
|
||||
}
|
||||
}
|
||||
@@ -68,18 +66,14 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
/**
|
||||
* Run the DDL drop and DDL create scripts if properties have been set.
|
||||
*/
|
||||
public void runDdl() {
|
||||
protected void runDdl() {
|
||||
|
||||
if (runDdl) {
|
||||
try {
|
||||
if (dropContent == null) {
|
||||
dropContent = readFile(getDropFileName());
|
||||
}
|
||||
if (createContent == null) {
|
||||
createContent = readFile(getCreateFileName());
|
||||
}
|
||||
runScript(true, dropContent);
|
||||
runScript(false, createContent);
|
||||
runInitSql();
|
||||
runDropSql();
|
||||
runCreateSql();
|
||||
runSeedSql();
|
||||
|
||||
} catch (IOException e) {
|
||||
String msg = "Error reading drop/create script from file system";
|
||||
@@ -88,6 +82,53 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
protected void runDropSql() throws IOException {
|
||||
if (!createOnly) {
|
||||
if (dropContent == null) {
|
||||
dropContent = readFile(getDropFileName());
|
||||
}
|
||||
runScript(true, dropContent, getDropFileName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void runCreateSql() throws IOException {
|
||||
if (createContent == null) {
|
||||
createContent = readFile(getCreateFileName());
|
||||
}
|
||||
runScript(false, createContent, getCreateFileName());
|
||||
}
|
||||
|
||||
protected void runInitSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlInitSql());
|
||||
}
|
||||
|
||||
protected void runSeedSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlSeedSql());
|
||||
}
|
||||
|
||||
protected void runResourceScript(String sqlScript) throws IOException {
|
||||
|
||||
if (sqlScript != null) {
|
||||
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
|
||||
if (is != null) {
|
||||
DdlRunner runner = new DdlRunner(false, sqlScript);
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runner.runAll(content, server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the classLoader to use to read sql scripts as resources.
|
||||
*/
|
||||
protected ClassLoader getClassLoader() {
|
||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||
if (cl == null) {
|
||||
cl = this.getClassLoader();
|
||||
}
|
||||
return cl;
|
||||
}
|
||||
|
||||
protected void writeDrop(String dropFile) {
|
||||
|
||||
try {
|
||||
@@ -108,7 +149,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public String generateDropDdl() {
|
||||
protected String generateDropDdl() {
|
||||
|
||||
try {
|
||||
dropContent = currentModel().getDropDdl();
|
||||
@@ -118,7 +159,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
public String generateCreateDdl() {
|
||||
protected String generateCreateDdl() {
|
||||
|
||||
try {
|
||||
createContent = currentModel().getCreateDdl();
|
||||
@@ -163,201 +204,33 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
return readContent(new FileReader(f));
|
||||
}
|
||||
|
||||
protected String readContent(Reader reader) throws IOException {
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
FileReader fr = new FileReader(f);
|
||||
LineNumberReader lr = new LineNumberReader(fr);
|
||||
LineNumberReader lineReader = new LineNumberReader(reader);
|
||||
try {
|
||||
String s;
|
||||
while ((s = lr.readLine()) != null) {
|
||||
while ((s = lineReader.readLine()) != null) {
|
||||
buf.append(s).append("\n");
|
||||
}
|
||||
} finally {
|
||||
lr.close();
|
||||
}
|
||||
return buf.toString();
|
||||
|
||||
return buf.toString();
|
||||
} finally {
|
||||
lineReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public void runScript(boolean expectErrors, String content) {
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
StringReader sr = new StringReader(content);
|
||||
List<String> statements = parseStatements(sr);
|
||||
|
||||
Transaction t = server.createTransaction();
|
||||
try {
|
||||
Connection connection = t.getConnection();
|
||||
|
||||
logger.info("Running DDL");
|
||||
|
||||
runStatements(expectErrors, statements, connection);
|
||||
|
||||
logger.info("Running DDL Complete");
|
||||
|
||||
t.commit();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error: " + e.getMessage(), e);
|
||||
} finally {
|
||||
t.end();
|
||||
}
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
return runner.runAll(content, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the list of statements.
|
||||
*/
|
||||
private void runStatements(boolean expectErrors, List<String> statements, Connection c) {
|
||||
List<String> noDuplicates = new ArrayList<String>();
|
||||
|
||||
for (String statement : statements) {
|
||||
if (!noDuplicates.contains(statement)) {
|
||||
noDuplicates.add(statement);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < noDuplicates.size(); i++) {
|
||||
String xOfy = (i + 1) + " of " + noDuplicates.size();
|
||||
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement.
|
||||
*/
|
||||
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
|
||||
|
||||
PreparedStatement pstmt = null;
|
||||
try {
|
||||
|
||||
// trim and remove trailing ; or /
|
||||
stmt = stmt.trim();
|
||||
if (stmt.endsWith(";")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
} else if (stmt.endsWith("/")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
}
|
||||
|
||||
logger.info("executing " + oneOf + " " + getSummary(stmt));
|
||||
|
||||
pstmt = c.prepareStatement(stmt);
|
||||
pstmt.execute();
|
||||
|
||||
} catch (Exception e) {
|
||||
if (expectErrors) {
|
||||
logger.info(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
|
||||
} else {
|
||||
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
} finally {
|
||||
if (pstmt != null) {
|
||||
try {
|
||||
pstmt.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing pstmt", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Local utility used to detect the end of statements / separate statements.
|
||||
* This is often just the semicolon character but for trigger/procedures this
|
||||
* detects the $$ demarcation used in the history DDL generation for MySql and
|
||||
* Postgres.
|
||||
*/
|
||||
static class StatementsSeparator {
|
||||
|
||||
ArrayList<String> statements = new ArrayList<String>();
|
||||
|
||||
boolean trimDelimiter;
|
||||
|
||||
boolean inDbProcedure;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
void lineContainsDollars(String line) {
|
||||
if (inDbProcedure) {
|
||||
if (trimDelimiter) {
|
||||
line = line.replace("$$","");
|
||||
}
|
||||
endOfStatement(line);
|
||||
} else {
|
||||
// MySql style delimiter needs to be trimmed/removed
|
||||
trimDelimiter = line.equals("delimiter $$");
|
||||
if (!trimDelimiter) {
|
||||
sb.append(line).append(" ");
|
||||
}
|
||||
}
|
||||
inDbProcedure = !inDbProcedure;
|
||||
}
|
||||
|
||||
void endOfStatement(String line) {
|
||||
// end of Db procedure
|
||||
sb.append(line);
|
||||
statements.add(sb.toString().trim());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
void nextLine(String line) {
|
||||
|
||||
if (line.contains("$$")) {
|
||||
lineContainsDollars(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (inDbProcedure) {
|
||||
sb.append(line).append(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
int semiPos = line.indexOf(';');
|
||||
if (semiPos == -1) {
|
||||
sb.append(line).append(" ");
|
||||
|
||||
} else if (semiPos == line.length() - 1) {
|
||||
// semicolon at end of line
|
||||
endOfStatement(line);
|
||||
|
||||
} else {
|
||||
// semicolon in middle of line
|
||||
String preSemi = line.substring(0, semiPos);
|
||||
endOfStatement(preSemi);
|
||||
sb.append(line.substring(semiPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Break up the sql in reader into a list of statements using the semi-colon
|
||||
* character;
|
||||
*/
|
||||
protected List<String> parseStatements(StringReader reader) {
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
StatementsSeparator statements = new StatementsSeparator();
|
||||
|
||||
String s;
|
||||
while ((s = br.readLine()) != null) {
|
||||
s = s.trim();
|
||||
statements.nextLine(s);
|
||||
}
|
||||
|
||||
return statements.statements;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSummary(String s) {
|
||||
if (s.length() > 80) {
|
||||
return s.substring(0, 80).trim() + "...";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Parses string content into separate SQL/DDL statements.
|
||||
*/
|
||||
public class DdlParser {
|
||||
|
||||
/**
|
||||
* Break up the sql in reader into a list of statements using the semi-colon and $$ delimiters;
|
||||
*/
|
||||
public List<String> parse(StringReader reader) {
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
StatementsSeparator statements = new StatementsSeparator();
|
||||
|
||||
String s;
|
||||
while ((s = br.readLine()) != null) {
|
||||
s = s.trim();
|
||||
statements.nextLine(s);
|
||||
}
|
||||
|
||||
return statements.statements;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Local utility used to detect the end of statements / separate statements.
|
||||
* This is often just the semicolon character but for trigger/procedures this
|
||||
* detects the $$ demarcation used in the history DDL generation for MySql and
|
||||
* Postgres.
|
||||
*/
|
||||
static class StatementsSeparator {
|
||||
|
||||
ArrayList<String> statements = new ArrayList<String>();
|
||||
|
||||
boolean trimDelimiter;
|
||||
|
||||
boolean inDbProcedure;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
void lineContainsDollars(String line) {
|
||||
if (inDbProcedure) {
|
||||
if (trimDelimiter) {
|
||||
line = line.replace("$$","");
|
||||
}
|
||||
endOfStatement(line);
|
||||
} else {
|
||||
// MySql style delimiter needs to be trimmed/removed
|
||||
trimDelimiter = line.equals("delimiter $$");
|
||||
if (!trimDelimiter) {
|
||||
sb.append(line).append(" ");
|
||||
}
|
||||
}
|
||||
inDbProcedure = !inDbProcedure;
|
||||
}
|
||||
|
||||
void endOfStatement(String line) {
|
||||
// end of Db procedure
|
||||
sb.append(line);
|
||||
statements.add(sb.toString().trim());
|
||||
sb = new StringBuilder();
|
||||
}
|
||||
|
||||
void nextLine(String line) {
|
||||
|
||||
if (line.contains("$$")) {
|
||||
lineContainsDollars(line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sb.length() == 0 && (line.isEmpty() || line.startsWith("--"))) {
|
||||
// ignore leading empty lines and sql comments
|
||||
return;
|
||||
}
|
||||
|
||||
if (inDbProcedure) {
|
||||
sb.append(line).append(" ");
|
||||
return;
|
||||
}
|
||||
|
||||
int semiPos = line.indexOf(';');
|
||||
if (semiPos == -1) {
|
||||
sb.append(line).append(" ");
|
||||
|
||||
} else if (semiPos == line.length() - 1) {
|
||||
// semicolon at end of line
|
||||
endOfStatement(line);
|
||||
|
||||
} else {
|
||||
// semicolon in middle of line
|
||||
String preSemi = line.substring(0, semiPos);
|
||||
endOfStatement(preSemi);
|
||||
sb.append(line.substring(semiPos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.StringReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Runs DDL scripts.
|
||||
*/
|
||||
public class DdlRunner {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
|
||||
|
||||
protected DdlParser ddlParser = new DdlParser();
|
||||
|
||||
protected final String scriptName;
|
||||
|
||||
protected final boolean expectErrors;
|
||||
|
||||
/**
|
||||
* Construct with a script name (for logging) and flag indicating if errors are expected.
|
||||
*/
|
||||
public DdlRunner(boolean expectErrors, String scriptName) {
|
||||
this.expectErrors = expectErrors;
|
||||
this.scriptName = scriptName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the content into sql statements and execute them in a transaction.
|
||||
*/
|
||||
public int runAll(String content, SpiEbeanServer server) {
|
||||
|
||||
List<String> statements = ddlParser.parse(new StringReader(content));
|
||||
return runStatements(statements, server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the statements in a single transaction.
|
||||
*/
|
||||
public int runStatements(List<String> statements, SpiEbeanServer server) {
|
||||
|
||||
Transaction t = server.createTransaction();
|
||||
try {
|
||||
int statementCount = runStatements(expectErrors, statements, t.getConnection());
|
||||
t.commit();
|
||||
|
||||
return statementCount;
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error: " + e.getMessage(), e);
|
||||
|
||||
} finally {
|
||||
t.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the list of statements.
|
||||
*/
|
||||
private int runStatements(boolean expectErrors, List<String> statements, Connection c) {
|
||||
|
||||
List<String> noDuplicates = new ArrayList<String>();
|
||||
|
||||
for (String statement : statements) {
|
||||
if (!noDuplicates.contains(statement)) {
|
||||
noDuplicates.add(statement);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Executing {} - {} statements", scriptName, noDuplicates.size());
|
||||
|
||||
for (int i = 0; i < noDuplicates.size(); i++) {
|
||||
String xOfy = (i + 1) + " of " + noDuplicates.size();
|
||||
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
|
||||
}
|
||||
|
||||
return noDuplicates.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement.
|
||||
*/
|
||||
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
|
||||
|
||||
PreparedStatement pstmt = null;
|
||||
try {
|
||||
|
||||
// trim and remove trailing ; or /
|
||||
stmt = stmt.trim();
|
||||
if (stmt.endsWith(";")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
} else if (stmt.endsWith("/")) {
|
||||
stmt = stmt.substring(0, stmt.length() - 1);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("executing " + oneOf + " " + getSummary(stmt));
|
||||
}
|
||||
|
||||
pstmt = c.prepareStatement(stmt);
|
||||
pstmt.execute();
|
||||
|
||||
} catch (Exception e) {
|
||||
if (expectErrors) {
|
||||
logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
|
||||
} else {
|
||||
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (pstmt != null) {
|
||||
try {
|
||||
pstmt.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error closing pstmt", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getSummary(String s) {
|
||||
if (s.length() > 80) {
|
||||
return s.substring(0, 80).trim() + "...";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
import com.avaje.ebean.dbmigration.migration.ForeignKey;
|
||||
import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
@@ -123,7 +124,11 @@ public class BaseTableDdl implements TableDdl {
|
||||
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
|
||||
}
|
||||
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
apply.newLine().append(")");
|
||||
addTableCommentInline(apply, createTable);
|
||||
apply.endOfStatement();
|
||||
|
||||
addComments(apply, createTable);
|
||||
|
||||
writeUniqueOneToOneConstraints(writer, createTable);
|
||||
|
||||
@@ -150,6 +155,37 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table and column comments (separate from the create table statement).
|
||||
*/
|
||||
private void addComments(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
if (!platformDdl.isInlineComments()) {
|
||||
String tableComment = createTable.getComment();
|
||||
if (!StringHelper.isNull(tableComment)) {
|
||||
platformDdl.addTableComment(apply, createTable.getName(), tableComment);
|
||||
}
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
if (!StringHelper.isNull(column.getComment())) {
|
||||
platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the table comment inline with the create table statement.
|
||||
*/
|
||||
private void addTableCommentInline(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
if (platformDdl.isInlineComments()) {
|
||||
String tableComment = createTable.getComment();
|
||||
if (!StringHelper.isNull(tableComment)) {
|
||||
platformDdl.inlineTableComment(apply, tableComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
|
||||
platformDdl.writeTableColumns(apply, columns, useIdentity);
|
||||
}
|
||||
|
||||
+4
-2
@@ -160,8 +160,10 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
Collection<MColumn> cols = table.getColumns().values();
|
||||
for (MColumn column : cols) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
if (!column.isDraftOnly()) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
}
|
||||
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
|
||||
apply.append(",").newLine();
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MS SQL Server platform specific DDL.
|
||||
*/
|
||||
@@ -15,6 +18,7 @@ public class MsSqlServerDdl extends PlatformDdl {
|
||||
this.foreignKeyRestrict = "";
|
||||
this.inlineUniqueOneToOne = false;
|
||||
this.columnSetDefault = "add default";
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,4 +87,20 @@ public class MsSqlServerDdl extends PlatformDdl {
|
||||
// can't alter itself - done in alterColumnBaseAttributes()
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.Column;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MySql specific DDL.
|
||||
@@ -14,6 +19,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
this.alterColumn = "modify";
|
||||
this.dropUniqueConstraint = "drop index";
|
||||
this.historyDdl = new MySqlHistoryDdl();
|
||||
this.inlineComments = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,4 +70,26 @@ public class MySqlDdl extends PlatformDdl {
|
||||
// use modify
|
||||
return "alter table " + tableName + " modify " + columnName + " " + type + notnullClause;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeColumnDefinition(DdlBuffer buffer, Column column, boolean useIdentity) throws IOException {
|
||||
super.writeColumnDefinition(buffer, column, useIdentity);
|
||||
String comment = column.getComment();
|
||||
if (!StringHelper.isNull(comment)) {
|
||||
// in mysql 5.5 column comment save in information_schema.COLUMNS.COLUMN_COMMENT(VARCHAR 1024)
|
||||
if (comment.length() > 500) {
|
||||
comment = comment.substring(0, 500);
|
||||
}
|
||||
buffer.append(String.format(" comment '%s'", comment));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void inlineTableComment(DdlBuffer apply, String tableComment) throws IOException {
|
||||
if (tableComment.length() > 1000) {
|
||||
tableComment = tableComment.substring(0, 1000);
|
||||
}
|
||||
apply.append(" comment='").append(tableComment).append("'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ public class PlatformDdl {
|
||||
*/
|
||||
private final DbIdentity dbIdentity;
|
||||
|
||||
/**
|
||||
* Set to true if table and column comments are included inline with the create statements.
|
||||
*/
|
||||
protected boolean inlineComments;
|
||||
|
||||
/**
|
||||
* Default assumes if exists is supported.
|
||||
*/
|
||||
@@ -53,6 +58,8 @@ public class PlatformDdl {
|
||||
|
||||
protected String identitySuffix = " auto_increment";
|
||||
|
||||
protected String alterTableIfExists = "";
|
||||
|
||||
protected String dropConstraintIfExists = "drop constraint if exists";
|
||||
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
@@ -114,6 +121,13 @@ public class PlatformDdl {
|
||||
return columnDefn + identitySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table and column comments are included inline.
|
||||
*/
|
||||
public boolean isInlineComments() {
|
||||
return inlineComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the table columns converting to platform types as necessary.
|
||||
*/
|
||||
@@ -150,7 +164,7 @@ public class PlatformDdl {
|
||||
* Return the drop foreign key clause.
|
||||
*/
|
||||
public String alterTableDropForeignKey(String tableName, String fkName) {
|
||||
return "alter table " + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,4 +398,27 @@ public class PlatformDdl {
|
||||
protected boolean isTrue(Boolean value) {
|
||||
return Boolean.TRUE.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an inline table comment to the create table statement.
|
||||
*/
|
||||
public void inlineTableComment(DdlBuffer apply, String tableComment) throws IOException {
|
||||
// do nothing by default (MySql only)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
apply.append(String.format("comment on table %s is '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public class PostgresDdl extends PlatformDdl {
|
||||
this.historyDdl = new PostgresHistoryDdl();
|
||||
this.dropTableCascade = " cascade";
|
||||
this.columnSetType = "type ";
|
||||
this.alterTableIfExists = "if exists ";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,9 @@ import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
@@ -15,19 +18,6 @@ public class MigrationXmlReader {
|
||||
|
||||
private static final MigrationXmlReader INSTANCE = new MigrationXmlReader();
|
||||
|
||||
/**
|
||||
* Read and return a Migration from an xml document at the given resource path.
|
||||
*/
|
||||
public static Migration readMaybe(String resourcePath) {
|
||||
|
||||
InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath);
|
||||
if (is == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return INSTANCE.read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Migration from an xml document at the given resource path.
|
||||
*/
|
||||
@@ -41,10 +31,27 @@ public class MigrationXmlReader {
|
||||
return INSTANCE.read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Migration from a migration xml file.
|
||||
*/
|
||||
public static Migration read(File migrationFile) {
|
||||
|
||||
try {
|
||||
FileInputStream is = new FileInputStream(migrationFile);
|
||||
try {
|
||||
return read(is);
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Migration from an xml document.
|
||||
*/
|
||||
public Migration read(InputStream is) {
|
||||
public static Migration read(InputStream is) {
|
||||
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(Migration.class);
|
||||
|
||||
@@ -16,6 +16,7 @@ public class MColumn {
|
||||
private String references;
|
||||
private String foreignKeyName;
|
||||
private String foreignKeyIndex;
|
||||
private String comment;
|
||||
|
||||
private boolean historyExclude;
|
||||
private boolean notnull;
|
||||
@@ -43,6 +44,7 @@ public class MColumn {
|
||||
this.checkConstraint = column.getCheckConstraint();
|
||||
this.checkConstraintName = column.getCheckConstraintName();
|
||||
this.defaultValue = column.getDefaultValue();
|
||||
this.comment = column.getComment();
|
||||
this.references = column.getReferences();
|
||||
this.foreignKeyName = column.getForeignKeyName();
|
||||
this.foreignKeyIndex = column.getForeignKeyIndex();
|
||||
@@ -76,6 +78,7 @@ public class MColumn {
|
||||
copy.checkConstraintName = checkConstraintName;
|
||||
copy.defaultValue = defaultValue;
|
||||
copy.references = references;
|
||||
copy.comment = comment;
|
||||
copy.foreignKeyName = foreignKeyName;
|
||||
copy.foreignKeyIndex = foreignKeyIndex;
|
||||
copy.historyExclude = historyExclude;
|
||||
@@ -198,6 +201,19 @@ public class MColumn {
|
||||
return uniqueOneToOne;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column comment.
|
||||
*/
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column comment.
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the draftOnly status for this column.
|
||||
@@ -237,6 +253,7 @@ public class MColumn {
|
||||
c.setForeignKeyName(foreignKeyName);
|
||||
c.setForeignKeyIndex(foreignKeyIndex);
|
||||
c.setDefaultValue(defaultValue);
|
||||
c.setComment(comment);
|
||||
c.setUnique(unique);
|
||||
c.setUniqueOneToOne(uniqueOneToOne);
|
||||
|
||||
|
||||
@@ -325,6 +325,10 @@ public class MTable {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getTablespace() {
|
||||
return tablespace;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Build the model from the series of migrations.
|
||||
@@ -17,26 +18,15 @@ public class MigrationModel {
|
||||
|
||||
private final ModelContainer model = new ModelContainer();
|
||||
|
||||
private final Set<String> readVersions = new LinkedHashSet<String>();
|
||||
private final File modelDirectory;
|
||||
|
||||
private final String resourcePath;
|
||||
private final String modelSuffix;
|
||||
|
||||
int nextMajorVersion;
|
||||
private MigrationVersion lastVersion;
|
||||
|
||||
public MigrationModel(String resourcePath) {
|
||||
this.resourcePath = normaliseResourcePath(resourcePath);
|
||||
}
|
||||
|
||||
private String normaliseResourcePath(String resourcePath) {
|
||||
if (resourcePath.endsWith("/")) {
|
||||
// trim trailing slash
|
||||
resourcePath = resourcePath.substring(0, resourcePath.length()-1);
|
||||
}
|
||||
if (resourcePath.startsWith("/")) {
|
||||
// trim leading slash
|
||||
resourcePath = resourcePath.substring(1);
|
||||
}
|
||||
return resourcePath;
|
||||
public MigrationModel(File modelDirectory, String modelSuffix) {
|
||||
this.modelDirectory = modelDirectory;
|
||||
this.modelSuffix = modelSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,57 +36,41 @@ public class MigrationModel {
|
||||
public ModelContainer read() {
|
||||
|
||||
readMigrations();
|
||||
logger.info("read versions {}", readVersions);
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of versions that were read.
|
||||
*/
|
||||
public Set<String> getReadVersions() {
|
||||
return readVersions;
|
||||
}
|
||||
|
||||
public int getNextMajorVersion() {
|
||||
return nextMajorVersion;
|
||||
}
|
||||
|
||||
private void readMigrations() {
|
||||
|
||||
for (int majorVersion = 1; majorVersion < 100; majorVersion++) {
|
||||
if (!readMinorVersions(majorVersion)){
|
||||
// no major.0 version so stopping
|
||||
nextMajorVersion = majorVersion;
|
||||
return;
|
||||
// find all the migration xml files
|
||||
File[] xmlFiles = modelDirectory.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.getName().toLowerCase().endsWith(modelSuffix);
|
||||
}
|
||||
});
|
||||
|
||||
List<MigrationResource> resources = new ArrayList<MigrationResource>();
|
||||
|
||||
for (File xmlFile: xmlFiles) {
|
||||
resources.add(new MigrationResource(xmlFile));
|
||||
}
|
||||
|
||||
// sort into version order before applying
|
||||
Collections.sort(resources);
|
||||
|
||||
for (MigrationResource migrationResource: resources) {
|
||||
logger.debug("read {}", migrationResource);
|
||||
model.apply(migrationResource.read());
|
||||
}
|
||||
|
||||
// remember the last version
|
||||
if (!resources.isEmpty()) {
|
||||
lastVersion = resources.get(resources.size() - 1).getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readMinorVersions(int majorVersion) {
|
||||
public String getNextVersion(String initialVersion) {
|
||||
|
||||
for (int minorVersion = 0; minorVersion < 100; minorVersion++) {
|
||||
if (!readMigration(majorVersion, minorVersion)) {
|
||||
// continue reading next major if minorVersion 0 was read
|
||||
return (minorVersion > 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return lastVersion == null ? initialVersion : lastVersion.nextVersion();
|
||||
}
|
||||
|
||||
private boolean readMigration(int majorVersion, int minorVersion) {
|
||||
|
||||
String version = majorVersion+"."+minorVersion;
|
||||
String path = "/"+resourcePath+"/v"+version+".xml";
|
||||
|
||||
Migration migration = MigrationXmlReader.readMaybe(path);
|
||||
if (migration == null) {
|
||||
logger.debug("... no migration at path:{}", path);
|
||||
return false;
|
||||
}
|
||||
readVersions.add(version);
|
||||
logger.trace("... read migration v{}", version);
|
||||
model.apply(migration);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Migration XML resource that holds the changes to be applied.
|
||||
*/
|
||||
public class MigrationResource implements Comparable<MigrationResource> {
|
||||
|
||||
private final File migrationFile;
|
||||
|
||||
private final MigrationVersion version;
|
||||
|
||||
/**
|
||||
* Construct with a migration xml file.
|
||||
*/
|
||||
public MigrationResource(File migrationFile) {
|
||||
this.migrationFile = migrationFile;
|
||||
this.version = MigrationVersion.parse(migrationFile.getName());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return migrationFile.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version associated with this resource.
|
||||
*/
|
||||
public MigrationVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the migration from the resource.
|
||||
*/
|
||||
public Migration read() {
|
||||
|
||||
return MigrationXmlReader.read(migrationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare by underlying version.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(MigrationResource other) {
|
||||
return version.compareTo(other.version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
/**
|
||||
* The version of a migration used so that migrations are processed in order.
|
||||
*/
|
||||
public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
|
||||
/**
|
||||
* The raw version text.
|
||||
*/
|
||||
private final String raw;
|
||||
|
||||
/**
|
||||
* The ordering parts.
|
||||
*/
|
||||
private final int[] ordering;
|
||||
|
||||
private MigrationVersion(String raw, int[] ordering) {
|
||||
this.raw = raw;
|
||||
this.ordering = ordering;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
|
||||
public String nextVersion() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < ordering.length; i++) {
|
||||
if (i < ordering.length -1 ) {
|
||||
sb.append(ordering[i]).append(".");
|
||||
} else {
|
||||
sb.append(ordering[i]+1);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MigrationVersion other) {
|
||||
|
||||
int otherLength = other.ordering.length;
|
||||
for (int i = 0; i < ordering.length; i++) {
|
||||
if (i >= otherLength) {
|
||||
// considered greater
|
||||
return 1;
|
||||
}
|
||||
if (ordering[i] != other.ordering[i]) {
|
||||
return (ordering[i] > other.ordering[i]) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
// considered the same
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the raw version string into a MigrationVersion.
|
||||
*/
|
||||
public static MigrationVersion parse(String raw) {
|
||||
|
||||
String value = raw.replace("__",".");
|
||||
value = value.replace('_','.');
|
||||
|
||||
String[] sections = value.split("\\.");
|
||||
|
||||
int[] ordering = new int[sections.length];
|
||||
|
||||
int stopIndex = 0;
|
||||
for (int i = 0; i < sections.length; i++) {
|
||||
try {
|
||||
ordering[i] = Integer.parseInt(sections[i]);
|
||||
stopIndex++;
|
||||
} catch (NumberFormatException e) {
|
||||
// stop parsing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int[] actualOrder = new int[stopIndex];
|
||||
System.arraycopy(ordering, 0, actualOrder, 0, stopIndex);
|
||||
|
||||
return new MigrationVersion(raw, actualOrder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import com.avaje.ebean.config.DbMigrationConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
@@ -24,20 +25,19 @@ public class PlatformDdlWriter {
|
||||
|
||||
private final String platformPrefix;
|
||||
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig) {
|
||||
this(platform, serverConfig, "");
|
||||
}
|
||||
private final DbMigrationConfig config;
|
||||
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix) {
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix, DbMigrationConfig config) {
|
||||
this.platform = platform;
|
||||
this.serverConfig = serverConfig;
|
||||
this.platformPrefix = platformPrefix;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration as platform specific ddl.
|
||||
*/
|
||||
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, int nextMajorVersion) throws IOException {
|
||||
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, String fullVersion) throws IOException {
|
||||
|
||||
DdlHandler handler = handler();
|
||||
|
||||
@@ -49,16 +49,16 @@ public class PlatformDdlWriter {
|
||||
}
|
||||
handler.generateExtra(write);
|
||||
|
||||
writePlatformDdl(write, writePath, nextMajorVersion);
|
||||
writePlatformDdl(write, writePath, fullVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the ddl files.
|
||||
*/
|
||||
protected void writePlatformDdl(DdlWrite write, File resourcePath, int migrationVersion) throws IOException {
|
||||
protected void writePlatformDdl(DdlWrite write, File resourcePath, String fullVersion) throws IOException {
|
||||
|
||||
if (!write.isApplyEmpty()) {
|
||||
FileWriter applyWriter = createWriter(resourcePath, migrationVersion, "apply.sql");
|
||||
FileWriter applyWriter = createWriter(resourcePath, fullVersion, "", config.getApplySuffix());
|
||||
try {
|
||||
writeApplyDdl(applyWriter, write);
|
||||
applyWriter.flush();
|
||||
@@ -66,8 +66,8 @@ public class PlatformDdlWriter {
|
||||
applyWriter.close();
|
||||
}
|
||||
|
||||
if (!write.isApplyRollbackEmpty()) {
|
||||
FileWriter applyRollbackWriter = createWriter(resourcePath, migrationVersion, "applyRollback.sql");
|
||||
if (!config.isSuppressRollback() && !write.isApplyRollbackEmpty()) {
|
||||
FileWriter applyRollbackWriter = createWriter(resourcePath, fullVersion, config.getRollbackPath(), config.getRollbackSuffix());
|
||||
try {
|
||||
writeApplyRollbackDdl(applyRollbackWriter, write);
|
||||
applyRollbackWriter.flush();
|
||||
@@ -78,7 +78,7 @@ public class PlatformDdlWriter {
|
||||
}
|
||||
|
||||
if (!write.isDropEmpty()) {
|
||||
FileWriter dropWriter = createWriter(resourcePath, migrationVersion, "drop.sql");
|
||||
FileWriter dropWriter = createWriter(resourcePath, fullVersion, config.getDropPath(), config.getDropSuffix());
|
||||
try {
|
||||
writeDropDdl(dropWriter, write);
|
||||
dropWriter.flush();
|
||||
@@ -88,12 +88,28 @@ public class PlatformDdlWriter {
|
||||
}
|
||||
}
|
||||
|
||||
protected FileWriter createWriter(File resourcePath, int migrationVersion, String suffix) throws IOException {
|
||||
protected FileWriter createWriter(File path, String fullVersion, String subPath, String suffix) throws IOException {
|
||||
|
||||
File applyFile = new File(resourcePath, "v" + migrationVersion + ".0-" + platformPrefix + suffix);
|
||||
String fileName = fullVersion;
|
||||
if (!platformPrefix.isEmpty()) {
|
||||
fileName += "-"+platformPrefix;
|
||||
}
|
||||
if (subPath != null && !subPath.isEmpty()) {
|
||||
path = subPath(path, subPath);
|
||||
}
|
||||
fileName += suffix;
|
||||
File applyFile = new File(path, fileName);
|
||||
return new FileWriter(applyFile);
|
||||
}
|
||||
|
||||
protected File subPath(File path, String suffix) {
|
||||
File subPath = new File(path, suffix);
|
||||
if (!subPath.exists()) {
|
||||
subPath.mkdirs();
|
||||
}
|
||||
return subPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the 'Apply' DDL buffers to the writer.
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
|
||||
}
|
||||
|
||||
MTable table = new MTable(descriptor.getBaseTable());
|
||||
table.setComment(descriptor.getDbComment());
|
||||
if (descriptor.isHistorySupport()) {
|
||||
table.setWithHistory(true);
|
||||
BeanProperty whenCreated = descriptor.findWhenCreatedProperty();
|
||||
|
||||
@@ -230,6 +230,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
}
|
||||
|
||||
MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
|
||||
col.setComment(p.getDbComment());
|
||||
col.setDraftOnly(p.isDraftOnly());
|
||||
|
||||
if (p.isId()) {
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ClassUtil {
|
||||
/**
|
||||
* Load a class taking into account a context class loader (if present).
|
||||
*/
|
||||
private static Class<?> forName(String name) throws ClassNotFoundException {
|
||||
public static Class<?> forName(String name) throws ClassNotFoundException {
|
||||
return new ClassLoadContext().forName(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,153 +1,153 @@
|
||||
package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
import com.avaje.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Holds the joins needs to support the many where predicates.
|
||||
* These joins are independent of any 'fetch' joins on the many.
|
||||
*/
|
||||
public class ManyWhereJoins implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6490181101871795417L;
|
||||
|
||||
private final TreeMap<String,PropertyJoin> joins = new TreeMap<String,PropertyJoin>();
|
||||
private static final long serialVersionUID = -6490181101871795417L;
|
||||
|
||||
private StringBuilder formulaProperties = new StringBuilder();
|
||||
|
||||
private boolean formulaWithJoin;
|
||||
private final TreeMap<String, PropertyJoin> joins = new TreeMap<String, PropertyJoin>();
|
||||
|
||||
private StringBuilder formulaProperties = new StringBuilder();
|
||||
|
||||
private boolean formulaWithJoin;
|
||||
|
||||
/**
|
||||
* 'Mode' indicating that joins added while this is true are required to be outer joins.
|
||||
*/
|
||||
private boolean requireOuterJoins;
|
||||
|
||||
/**
|
||||
* Return the current 'mode' indicating if outer joins are currently required or not.
|
||||
*/
|
||||
public boolean isRequireOuterJoins() {
|
||||
return requireOuterJoins;
|
||||
}
|
||||
private boolean requireOuterJoins;
|
||||
|
||||
/**
|
||||
* Set the 'mode' to be that joins added are required to be outer joins.
|
||||
* This is set during the evaluation of disjunction predicates.
|
||||
*/
|
||||
public void setRequireOuterJoins(boolean requireOuterJoins) {
|
||||
this.requireOuterJoins = requireOuterJoins;
|
||||
}
|
||||
/**
|
||||
* Return the current 'mode' indicating if outer joins are currently required or not.
|
||||
*/
|
||||
public boolean isRequireOuterJoins() {
|
||||
return requireOuterJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a many where join.
|
||||
*/
|
||||
public void add(ElPropertyDeploy elProp) {
|
||||
|
||||
String join = elProp.getElPrefix();
|
||||
BeanProperty p = elProp.getBeanProperty();
|
||||
if (p instanceof BeanPropertyAssocMany<?>){
|
||||
join = addManyToJoin(join, p.getName());
|
||||
}
|
||||
if (join != null){
|
||||
addJoin(join);
|
||||
if (p != null) {
|
||||
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
addJoin(join+"."+secondaryTableJoinPrefix);
|
||||
}
|
||||
}
|
||||
addParentJoins(join);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'many' properties we also need to add the name of the
|
||||
* many property to get the full logical name of the join.
|
||||
*/
|
||||
private String addManyToJoin(String join, String manyPropName){
|
||||
if (join == null){
|
||||
return manyPropName;
|
||||
} else {
|
||||
return join+"."+manyPropName;
|
||||
}
|
||||
}
|
||||
|
||||
private void addParentJoins(String join) {
|
||||
String[] split = SplitName.split(join);
|
||||
if (split[0] != null){
|
||||
addJoin(split[0]);
|
||||
addParentJoins(split[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the 'mode' to be that joins added are required to be outer joins.
|
||||
* This is set during the evaluation of disjunction predicates.
|
||||
*/
|
||||
public void setRequireOuterJoins(boolean requireOuterJoins) {
|
||||
this.requireOuterJoins = requireOuterJoins;
|
||||
}
|
||||
|
||||
private void addJoin(String property) {
|
||||
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER: SqlJoinType.INNER;
|
||||
joins.put(property, new PropertyJoin(property, joinType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no extra many where joins.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of many where joins.
|
||||
*/
|
||||
public Collection<PropertyJoin> getPropertyJoins() {
|
||||
return joins.values();
|
||||
}
|
||||
/**
|
||||
* Add a many where join.
|
||||
*/
|
||||
public void add(ElPropertyDeploy elProp) {
|
||||
|
||||
/**
|
||||
* Return the set of property names for the many where joins.
|
||||
*/
|
||||
public TreeSet<String> getPropertyNames() {
|
||||
|
||||
TreeSet<String> propertyNames = new TreeSet<String>();
|
||||
for (PropertyJoin join : joins.values()) {
|
||||
propertyNames.add(join.getProperty());
|
||||
String join = elProp.getElPrefix();
|
||||
BeanProperty p = elProp.getBeanProperty();
|
||||
if (p instanceof BeanPropertyAssocMany<?>) {
|
||||
join = addManyToJoin(join, p.getName());
|
||||
}
|
||||
if (join != null) {
|
||||
addJoin(join);
|
||||
if (p != null) {
|
||||
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
addJoin(join + "." + secondaryTableJoinPrefix);
|
||||
}
|
||||
}
|
||||
return propertyNames;
|
||||
addParentJoins(join);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In findRowCount query found a formula property with a join clause so building a select clause
|
||||
* specifically for the findRowCount query.
|
||||
*/
|
||||
public void addFormulaWithJoin(String propertyName) {
|
||||
if (formulaWithJoin) {
|
||||
formulaProperties.append(",");
|
||||
} else {
|
||||
formulaProperties = new StringBuilder();
|
||||
formulaWithJoin = true;
|
||||
}
|
||||
formulaProperties.append(propertyName);
|
||||
/**
|
||||
* For 'many' properties we also need to add the name of the
|
||||
* many property to get the full logical name of the join.
|
||||
*/
|
||||
private String addManyToJoin(String join, String manyPropName) {
|
||||
if (join == null) {
|
||||
return manyPropName;
|
||||
} else {
|
||||
return join + "." + manyPropName;
|
||||
}
|
||||
|
||||
public boolean isHasMany() {
|
||||
return formulaWithJoin || !joins.isEmpty();
|
||||
}
|
||||
|
||||
private void addParentJoins(String join) {
|
||||
String[] split = SplitName.split(join);
|
||||
if (split[0] != null) {
|
||||
addJoin(split[0]);
|
||||
addParentJoins(split[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the findRowCount query just needs the id property in the select clause.
|
||||
*/
|
||||
public boolean isSelectId() {
|
||||
return !formulaWithJoin;
|
||||
}
|
||||
|
||||
private void addJoin(String property) {
|
||||
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER : SqlJoinType.INNER;
|
||||
joins.put(property, new PropertyJoin(property, joinType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no extra many where joins.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of many where joins.
|
||||
*/
|
||||
public Collection<PropertyJoin> getPropertyJoins() {
|
||||
return joins.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of property names for the many where joins.
|
||||
*/
|
||||
public TreeSet<String> getPropertyNames() {
|
||||
|
||||
TreeSet<String> propertyNames = new TreeSet<String>();
|
||||
for (PropertyJoin join : joins.values()) {
|
||||
propertyNames.add(join.getProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formula properties to build the select clause for a findRowCount query.
|
||||
*/
|
||||
public String getFormulaProperties() {
|
||||
return formulaProperties.toString();
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* In findRowCount query found a formula property with a join clause so building a select clause
|
||||
* specifically for the findRowCount query.
|
||||
*/
|
||||
public void addFormulaWithJoin(String propertyName) {
|
||||
if (formulaWithJoin) {
|
||||
formulaProperties.append(",");
|
||||
} else {
|
||||
formulaProperties = new StringBuilder();
|
||||
formulaWithJoin = true;
|
||||
}
|
||||
formulaProperties.append(propertyName);
|
||||
}
|
||||
|
||||
public boolean isHasMany() {
|
||||
return formulaWithJoin || !joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the findRowCount query just needs the id property in the select clause.
|
||||
*/
|
||||
public boolean isSelectId() {
|
||||
return !formulaWithJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formula properties to build the select clause for a findRowCount query.
|
||||
*/
|
||||
public String getFormulaProperties() {
|
||||
return formulaProperties.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.avaje.ebean.config.ServerConfig;
|
||||
*
|
||||
* author: Richard Vowles - http://gplus.to/RichardVowles
|
||||
*/
|
||||
@Deprecated
|
||||
public interface SpiEbeanPlugin {
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,11 +66,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Return the DDL generator.
|
||||
*/
|
||||
DdlGenerator getDdlGenerator();
|
||||
|
||||
/**
|
||||
* Clear the query execution statistics.
|
||||
*/
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Event where all tuned query information is collected.
|
||||
* <p>
|
||||
* This is for writing the "all" file on shutdown when using runtime tuning.
|
||||
* </p>
|
||||
*/
|
||||
public class AutoTuneAllCollection {
|
||||
|
||||
final Autotune document = new Autotune();
|
||||
|
||||
final BaseQueryTuner queryTuner;
|
||||
|
||||
/**
|
||||
* Construct to collect/report all tuned queries.
|
||||
*/
|
||||
public AutoTuneAllCollection(BaseQueryTuner queryTuner) {
|
||||
this.queryTuner = queryTuner;
|
||||
loadAllTuned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of origin elements in the document.
|
||||
*/
|
||||
public int size() {
|
||||
return document.getOrigin().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Autotune document object.
|
||||
*/
|
||||
public Autotune getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the existing query tuning into the document.
|
||||
*/
|
||||
private void loadAllTuned() {
|
||||
|
||||
Collection<TunedQueryInfo> all = queryTuner.getAll();
|
||||
for (TunedQueryInfo tuned: all) {
|
||||
document.getOrigin().add(tuned.getOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebean.bean.ObjectGraphOrigin;
|
||||
import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
/**
|
||||
* Event where profiling information is collected and processed for differences
|
||||
* relative to the current query tuning.
|
||||
*/
|
||||
public class AutoTuneDiffCollection {
|
||||
|
||||
final Autotune document = new Autotune();
|
||||
|
||||
final AutoTuneCollection profiling;
|
||||
|
||||
final BaseQueryTuner queryTuner;
|
||||
|
||||
final boolean updateTuning;
|
||||
|
||||
int newCount;
|
||||
|
||||
int diffCount;
|
||||
|
||||
/**
|
||||
* Construct to collect/report the new/diff query tuning entries.
|
||||
*/
|
||||
public AutoTuneDiffCollection(AutoTuneCollection profiling, BaseQueryTuner queryTuner, boolean updateTuning) {
|
||||
this.profiling = profiling;
|
||||
this.queryTuner = queryTuner;
|
||||
this.updateTuning = updateTuning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no new or diff entries.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return newCount == 0 && diffCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying Autotune document object.
|
||||
*/
|
||||
public Autotune getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of diff entries.
|
||||
*/
|
||||
public int getDiffCount() {
|
||||
return diffCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of new entries.
|
||||
*/
|
||||
public int getNewCount() {
|
||||
return newCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total new and diff entries.
|
||||
*/
|
||||
public int getChangeCount() {
|
||||
return newCount + diffCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the underlying document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process checking profiling entries against existing query tuning.
|
||||
*/
|
||||
public boolean process() {
|
||||
|
||||
for (AutoTuneCollection.Entry entry : profiling.getEntries()) {
|
||||
addToDocument(entry);
|
||||
}
|
||||
|
||||
return isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entry is new or diff and add as necessary.
|
||||
*/
|
||||
private void addToDocument(AutoTuneCollection.Entry entry) {
|
||||
|
||||
ObjectGraphOrigin point = entry.getOrigin();
|
||||
OrmQueryDetail profileDetail = entry.getDetail();
|
||||
|
||||
// compare with the existing query tuning entry
|
||||
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
|
||||
if (tuneDetail == null) {
|
||||
addToDocumentNewEntry(entry, point);
|
||||
|
||||
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
|
||||
addToDocumentDiffEntry(entry, point, tuneDetail);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as a diff entry.
|
||||
*/
|
||||
private void addToDocumentDiffEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, OrmQueryDetail tuneDetail) {
|
||||
|
||||
diffCount++;
|
||||
|
||||
Origin origin = createOrigin(entry, point, tuneDetail.toString());
|
||||
ProfileDiff diff = document.getProfileDiff();
|
||||
if (diff == null) {
|
||||
diff = new ProfileDiff();
|
||||
document.setProfileDiff(diff);
|
||||
}
|
||||
diff.getOrigin().add(origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as a "new" entry.
|
||||
*/
|
||||
private void addToDocumentNewEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
|
||||
|
||||
newCount++;
|
||||
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew == null) {
|
||||
profileNew = new ProfileNew();
|
||||
document.setProfileNew(profileNew);
|
||||
}
|
||||
Origin origin = createOrigin(entry, point, entry.getOriginalQuery());
|
||||
profileNew.getOrigin().add(origin);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
|
||||
*/
|
||||
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, String query) {
|
||||
|
||||
Origin origin = new Origin();
|
||||
origin.setKey(point.getKey());
|
||||
origin.setBeanType(point.getBeanType());
|
||||
origin.setDetail(entry.getDetail().toString());
|
||||
origin.setCallStack(point.getCallStack().description("\n"));
|
||||
origin.setOriginal(query);
|
||||
|
||||
if (updateTuning) {
|
||||
queryTuner.put(origin);
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,12 +7,29 @@ import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Simple writer for output of the AutoTune Profiling as an XML document.
|
||||
*/
|
||||
public class AutoTuneXmlWriter {
|
||||
|
||||
/**
|
||||
* Write the document as xml file with the given prefix.
|
||||
*/
|
||||
public void write(Autotune document, String filePrefix) {
|
||||
|
||||
SortAutoTuneDocument.sort(document);
|
||||
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
String now = df.format(new Date());
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
File file = new File(filePrefix + "-" + now + ".xml");
|
||||
write(document, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Profiling to a file as xml.
|
||||
*/
|
||||
|
||||
@@ -7,9 +7,11 @@ import com.avaje.ebean.config.AutoTuneMode;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -48,6 +50,21 @@ public class BaseQueryTuner {
|
||||
this.skipAll = !queryTuning && !profiling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the current tuned query entries.
|
||||
*/
|
||||
public Collection<TunedQueryInfo> getAll() {
|
||||
return tunedQueryInfoMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query tuning entry.
|
||||
*/
|
||||
public void put(Origin origin) {
|
||||
|
||||
tunedQueryInfoMap.put(origin.getKey(), new TunedQueryInfo(origin));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the tuned query information.
|
||||
*/
|
||||
|
||||
+95
-150
@@ -1,6 +1,5 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebean.bean.ObjectGraphOrigin;
|
||||
import com.avaje.ebean.config.AutoTuneConfig;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
@@ -9,24 +8,11 @@ import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileEmpty;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetailParser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Implementation of the AutoTuneService which is comprised of profiling and query tuning.
|
||||
@@ -35,6 +21,8 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class);
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final long defaultGarbageCollectionWait;
|
||||
|
||||
private final boolean skipCollectionOnShutdown;
|
||||
@@ -53,14 +41,20 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
|
||||
private final String serverName;
|
||||
|
||||
private final int profilingUpdateFrequency;
|
||||
|
||||
private long runtimeChangeCount;
|
||||
|
||||
public DefaultAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
|
||||
AutoTuneConfig config = serverConfig.getAutoTuneConfig();
|
||||
|
||||
this.server = server;
|
||||
this.queryTuning = config.isQueryTuning();
|
||||
this.profiling = config.isProfiling();
|
||||
this.tuningFile = config.getQueryTuningFile();
|
||||
this.profilingFile = config.getProfilingFile();
|
||||
this.profilingUpdateFrequency = config.getProfilingUpdateFrequency();
|
||||
this.serverName = server.getName();
|
||||
this.profileManager = new ProfileManager(config, server);
|
||||
this.queryTuner = new BaseQueryTuner(config, server, profileManager);
|
||||
@@ -75,169 +69,120 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
public void startup() {
|
||||
|
||||
if (queryTuning) {
|
||||
File file = new File(tuningFile);
|
||||
if (!file.exists()) {
|
||||
logger.warn("AutoTune file {} not found - no automatic tuning will be applied", file.getAbsolutePath());
|
||||
|
||||
} else {
|
||||
AutoTuneXmlReader reader = new AutoTuneXmlReader();
|
||||
Autotune profiling = reader.read(file);
|
||||
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
|
||||
for (Origin origin : profiling.getOrigin()) {
|
||||
queryTuner.load(origin.getKey(), createTunedQueryInfo(origin));
|
||||
}
|
||||
loadTuningFile();
|
||||
if (isRuntimeTuningUpdates()) {
|
||||
// periodically gather and update query tuning
|
||||
server.getBackgroundExecutor().executePeriodically(new ProfilingUpdate(), profilingUpdateFrequency, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private TunedQueryInfo createTunedQueryInfo(Origin origin) {
|
||||
OrmQueryDetail detail = new OrmQueryDetailParser(origin.getDetail()).parse();
|
||||
return new TunedQueryInfo(detail);
|
||||
|
||||
/**
|
||||
* Return true if the tuning should update periodically at runtime.
|
||||
*/
|
||||
private boolean isRuntimeTuningUpdates() {
|
||||
return profilingUpdateFrequency > 0;
|
||||
}
|
||||
|
||||
private void saveProfiling(boolean reset) {
|
||||
private class ProfilingUpdate implements Runnable {
|
||||
|
||||
Autotune document = new Autotune();
|
||||
|
||||
AutoTuneCollection autoTuneCollection = profileManager.profilingCollection(reset);
|
||||
|
||||
List<AutoTuneCollection.Entry> entries = autoTuneCollection.getEntries();
|
||||
|
||||
// count "new" and "diff" profiling entries
|
||||
AtomicInteger newCounter = new AtomicInteger();
|
||||
AtomicInteger diffCounter = new AtomicInteger();
|
||||
|
||||
Set<String> profileKeys = new HashSet<String>();
|
||||
for (AutoTuneCollection.Entry entry : entries) {
|
||||
saveProfilingEntry(document, entry, newCounter, diffCounter);
|
||||
profileKeys.add(entry.getOrigin().getKey());
|
||||
@Override
|
||||
public void run() {
|
||||
runtimeTuningUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
// report the origin keys that we didn't collect any profiling on
|
||||
Set<String> tunerKeys = queryTuner.keySet();
|
||||
for (String tuneKey : tunerKeys) {
|
||||
if (!profileKeys.contains(tuneKey)) {
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty == null) {
|
||||
profileEmpty = new ProfileEmpty();
|
||||
document.setProfileEmpty(profileEmpty);
|
||||
}
|
||||
Origin emptyOrigin = new Origin();
|
||||
emptyOrigin.setKey(tuneKey);
|
||||
profileEmpty.getOrigin().add(emptyOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
int totalNew = newCounter.get();
|
||||
int totalDiff = diffCounter.get();
|
||||
if (totalNew == 0 && totalDiff == 0) {
|
||||
logger.info("No new or diff entries for profiling server:{}", serverName);
|
||||
/**
|
||||
* Load tuning information from an existing tuning file.
|
||||
*/
|
||||
private void loadTuningFile() {
|
||||
File file = new File(tuningFile);
|
||||
if (!file.exists()) {
|
||||
logger.warn("AutoTune file {} not found - no initial automatic query tuning", file.getAbsolutePath());
|
||||
|
||||
} else {
|
||||
sortDocument(document);
|
||||
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
String now = df.format(new Date());
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
File file = new File(profilingFile + "-" + serverName + "-" + now + ".xml");
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, file);
|
||||
|
||||
logger.info("writing new:{} diff:{} profiling entries for server:{}", totalNew, totalDiff, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the diff and new entries by bean type followed by key.
|
||||
*/
|
||||
private void sortDocument(Autotune document) {
|
||||
|
||||
ProfileDiff profileDiff = document.getProfileDiff();
|
||||
if (profileDiff != null) {
|
||||
Collections.sort(profileDiff.getOrigin(), new OriginNameKeySort());
|
||||
}
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew != null) {
|
||||
Collections.sort(profileNew.getOrigin(), new OriginNameKeySort());
|
||||
}
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty != null) {
|
||||
Collections.sort(profileEmpty.getOrigin(), new OriginKeySort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
class OriginNameKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
int comp = o1.getBeanType().compareTo(o2.getBeanType());
|
||||
if (comp == 0) {
|
||||
comp = o1.getKey().compareTo(o2.getKey());
|
||||
AutoTuneXmlReader reader = new AutoTuneXmlReader();
|
||||
Autotune profiling = reader.read(file);
|
||||
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
|
||||
for (Origin origin : profiling.getOrigin()) {
|
||||
queryTuner.put(origin);
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
* Collect profiling, check for new/diff to existing tuning and apply changes.
|
||||
*/
|
||||
class OriginKeySort implements Comparator<Origin> {
|
||||
private void runtimeTuningUpdate() {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
synchronized (this) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
AutoTuneCollection profiling = profileManager.profilingCollection(false);
|
||||
|
||||
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, true);
|
||||
if (event.process()) {
|
||||
long exeMillis = System.currentTimeMillis() - start;
|
||||
logger.debug("No query tuning updates for server:{} executionMillis:{}", serverName, exeMillis);
|
||||
|
||||
} else {
|
||||
// report the query tuning changes that have been made
|
||||
runtimeChangeCount += event.getChangeCount();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-update");
|
||||
long exeMillis = System.currentTimeMillis() - start;
|
||||
logger.info("query tuning updates - new:{} diff:{} for server:{} executionMillis:{}", event.getNewCount(), event.getDiffCount(), serverName, exeMillis);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error collecting or applying automatic query tuning", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveProfilingEntry(Autotune document, AutoTuneCollection.Entry entry, AtomicInteger newCount, AtomicInteger diffCount) {
|
||||
private void saveProfilingOnShutdown(boolean reset) {
|
||||
|
||||
ObjectGraphOrigin point = entry.getOrigin();
|
||||
OrmQueryDetail profileDetail = entry.getDetail();
|
||||
synchronized (this) {
|
||||
if (isRuntimeTuningUpdates()) {
|
||||
runtimeTuningUpdate();
|
||||
outputAllTuning();
|
||||
|
||||
// compare with the existing query tuning entry
|
||||
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
|
||||
if (tuneDetail == null) {
|
||||
// New entry
|
||||
newCount.incrementAndGet();
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew == null) {
|
||||
profileNew = new ProfileNew();
|
||||
document.setProfileNew(profileNew);
|
||||
} else {
|
||||
|
||||
AutoTuneCollection profiling = profileManager.profilingCollection(reset);
|
||||
|
||||
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, false);
|
||||
if (!event.process()) {
|
||||
logger.info("No new or diff entries for profiling server:{}", serverName);
|
||||
|
||||
} else {
|
||||
event.writeFile(profilingFile + "-" + serverName);
|
||||
logger.info("writing new:{} diff:{} profiling entries for server:{}", event.getNewCount(), event.getDiffCount(), serverName);
|
||||
}
|
||||
}
|
||||
Origin origin = createOrigin(entry, point);
|
||||
origin.setOriginal(entry.getOriginalQuery());
|
||||
profileNew.getOrigin().add(origin);
|
||||
|
||||
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
|
||||
// Diff entry
|
||||
diffCount.incrementAndGet();
|
||||
Origin origin = createOrigin(entry, point);
|
||||
origin.setOriginal(tuneDetail.toString());
|
||||
ProfileDiff diff = document.getProfileDiff();
|
||||
if (diff == null) {
|
||||
diff = new ProfileDiff();
|
||||
document.setProfileDiff(diff);
|
||||
}
|
||||
diff.getOrigin().add(origin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
|
||||
* Output all the query tuning (the "all" file).
|
||||
* <p>
|
||||
* This is the originally loaded tuning plus any tuning changes picked up and applied at runtime.
|
||||
* </p>
|
||||
* <p>
|
||||
* This "all" file can be used as the next "ebean-autotune.xml" file.
|
||||
* </p>
|
||||
*/
|
||||
@NotNull
|
||||
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
|
||||
Origin origin = new Origin();
|
||||
origin.setKey(point.getKey());
|
||||
origin.setBeanType(point.getBeanType());
|
||||
origin.setDetail(entry.getDetail().toString());
|
||||
origin.setCallStack(point.getCallStack().description("\n"));
|
||||
return origin;
|
||||
private void outputAllTuning() {
|
||||
|
||||
if (runtimeChangeCount == 0) {
|
||||
logger.info("no runtime query tuning changes for server:{}", serverName);
|
||||
|
||||
} else {
|
||||
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
|
||||
int size = event.size();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-all");
|
||||
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +197,7 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
public void shutdown() {
|
||||
if (profiling && !skipCollectionOnShutdown) {
|
||||
collectProfiling(-1);
|
||||
saveProfiling(false);
|
||||
saveProfilingOnShutdown(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ public class ProfileManager implements ProfilingListener {
|
||||
AutoTuneCollection req = new AutoTuneCollection();
|
||||
|
||||
for (ProfileOrigin origin : profileMap.values()) {
|
||||
|
||||
BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
|
||||
if (desc != null) {
|
||||
origin.profilingCollection(desc, req, reset);
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileEmpty;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sorts Autotune document by
|
||||
*/
|
||||
public class SortAutoTuneDocument {
|
||||
|
||||
|
||||
/**
|
||||
* Set the diff and new entries by bean type followed by key.
|
||||
*/
|
||||
public static void sort(Autotune document) {
|
||||
|
||||
ProfileDiff profileDiff = document.getProfileDiff();
|
||||
if (profileDiff != null) {
|
||||
Collections.sort(profileDiff.getOrigin(), NAME_KEY_SORT);
|
||||
}
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew != null) {
|
||||
Collections.sort(profileNew.getOrigin(), NAME_KEY_SORT);
|
||||
}
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty != null) {
|
||||
Collections.sort(profileEmpty.getOrigin(), KEY_SORT);
|
||||
}
|
||||
List<Origin> origins = document.getOrigin();
|
||||
if (!origins.isEmpty()) {
|
||||
Collections.sort(origins, NAME_KEY_SORT);
|
||||
}
|
||||
}
|
||||
|
||||
private static final OriginNameKeySort NAME_KEY_SORT = new OriginNameKeySort();
|
||||
|
||||
private static final OriginKeySort KEY_SORT = new OriginKeySort();
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
private static class OriginNameKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
int comp = o1.getBeanType().compareTo(o2.getBeanType());
|
||||
if (comp == 0) {
|
||||
comp = o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
private static class OriginKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.avaje.ebeaninternal.server.autotune.service;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetailParser;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -11,10 +13,20 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class TunedQueryInfo implements Serializable {
|
||||
|
||||
private final Origin origin;
|
||||
|
||||
private final OrmQueryDetail tunedDetail;
|
||||
|
||||
public TunedQueryInfo(OrmQueryDetail tunedDetail) {
|
||||
this.tunedDetail = tunedDetail;
|
||||
public TunedQueryInfo(Origin origin) {
|
||||
this.origin = origin;
|
||||
this.tunedDetail = new OrmQueryDetailParser(origin.getDetail()).parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the origin entry (includes call stack and bean type).
|
||||
*/
|
||||
public Origin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -401,8 +401,10 @@ public class DefaultBeanLoader {
|
||||
ebi.setPersistenceContext(pc);
|
||||
}
|
||||
|
||||
boolean draft = desc.isDraftInstance(bean);
|
||||
|
||||
if (embeddedOwnerIndex == -1) {
|
||||
if (SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
|
||||
if (!draft && SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
|
||||
// lazy loading and the bean cache is active
|
||||
if (desc.cacheBeanLoad(bean, ebi, id)) {
|
||||
return;
|
||||
@@ -415,10 +417,12 @@ public class DefaultBeanLoader {
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(desc.getBeanType());
|
||||
query.setLazyLoadProperty(ebi.getLazyLoadProperty());
|
||||
if (draft) {
|
||||
query.asDraft();
|
||||
}
|
||||
|
||||
if (embeddedOwnerIndex > -1) {
|
||||
String embeddedBeanPropertyName = ebi.getProperty(embeddedOwnerIndex);
|
||||
query.select("id," + embeddedBeanPropertyName);
|
||||
query.select("id," + ebi.getProperty(embeddedOwnerIndex));
|
||||
}
|
||||
|
||||
// don't collect AutoTune usage profiling information
|
||||
|
||||
@@ -47,8 +47,6 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
private final JndiDataSourceLookup jndiDataSourceFactory;
|
||||
|
||||
// private final AtomicInteger serverId = new AtomicInteger(1);
|
||||
|
||||
public DefaultContainer(ContainerConfig containerConfig) {
|
||||
|
||||
this.clusterManager = new ClusterManager(containerConfig);
|
||||
@@ -116,7 +114,6 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
ServerCacheManager cacheManager = getCacheManager(serverConfig);
|
||||
|
||||
// int uniqueServerId = serverId.incrementAndGet();
|
||||
SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig);
|
||||
|
||||
XmlConfigLoader xmlConfigLoader = new XmlConfigLoader(null);
|
||||
@@ -128,19 +125,6 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
cacheManager.init(server);
|
||||
|
||||
// if (serverConfig.isRegisterJmxMBeans()) {
|
||||
// MBeanServer mbeanServer;
|
||||
// ArrayList<?> list = MBeanServerFactory.findMBeanServer(null);
|
||||
// if (list.size() == 0) {
|
||||
// // probably not running in a server
|
||||
// mbeanServer = MBeanServerFactory.createMBeanServer();
|
||||
// } else {
|
||||
// // use the first MBeanServer
|
||||
// mbeanServer = (MBeanServer) list.get(0);
|
||||
// }
|
||||
// server.registerMBeans(mbeanServer, uniqueServerId);
|
||||
// }
|
||||
|
||||
// generate and run DDL if required
|
||||
// if there are any other tasks requiring action in their plugins, do them as well
|
||||
if (!DbOffline.isRunningMigration()) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.avaje.ebean.bean.ObjectGraphNode;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.bean.PersistenceContext.WithOption;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.DbMigrationConfig;
|
||||
import com.avaje.ebean.config.EncryptKeyManager;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
@@ -141,7 +142,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final List<SpiServerPlugin> serverPlugins;
|
||||
|
||||
private DdlGenerator ddlGenerator;
|
||||
private final DdlGenerator ddlGenerator;
|
||||
|
||||
private final ExpressionFactory expressionFactory;
|
||||
|
||||
@@ -234,6 +235,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.beanLoader = new DefaultBeanLoader(this);
|
||||
this.jsonContext = config.createJsonContext(this);
|
||||
this.serverPlugins = config.getPlugins();
|
||||
this.ddlGenerator = new DdlGenerator(this, serverConfig);
|
||||
|
||||
// load normal plugins late and call setup on all
|
||||
loadAndInitializePlugins(config.getServerConfig());
|
||||
@@ -260,18 +262,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
for (SpiEbeanPlugin plugin : ServiceLoader.load(SpiEbeanPlugin.class)) {
|
||||
spiPlugins.add(plugin);
|
||||
plugin.setup(this, config);
|
||||
|
||||
if (plugin instanceof DdlGenerator) {
|
||||
// backwards compatible
|
||||
ddlGenerator = (DdlGenerator) plugin;
|
||||
}
|
||||
}
|
||||
|
||||
if (ddlGenerator == null) {
|
||||
// ServiceLoader not finding ddlGenerator (typically OSGi)
|
||||
ddlGenerator = new DdlGenerator();
|
||||
spiPlugins.add(ddlGenerator);
|
||||
ddlGenerator.setup(this, config);
|
||||
}
|
||||
|
||||
ebeanPlugins = Collections.unmodifiableList(spiPlugins);
|
||||
@@ -288,6 +278,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
* Execute all the plugins with an online flag indicating the DB is up or not.
|
||||
*/
|
||||
public void executePlugins(boolean online) {
|
||||
|
||||
ddlGenerator.execute(online);
|
||||
|
||||
for (SpiEbeanPlugin plugin : ebeanPlugins) {
|
||||
plugin.execute(online);
|
||||
}
|
||||
@@ -336,10 +329,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return expressionFactory;
|
||||
}
|
||||
|
||||
public DdlGenerator getDdlGenerator() {
|
||||
return ddlGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutoTune getAutoTune() {
|
||||
return autoTuneService;
|
||||
@@ -373,6 +362,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
* Start any services after registering with the ClusterManager.
|
||||
*/
|
||||
public void start() {
|
||||
DbMigrationConfig migrationConfig = serverConfig.getMigrationConfig();
|
||||
if (migrationConfig != null) {
|
||||
migrationConfig.generateOnStart(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1384,6 +1377,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return new LimitOffsetPagedList<T>(this, (SpiQuery<T>)query, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
|
||||
|
||||
SpiQuery<T> spiQuery = (SpiQuery<T>)query;
|
||||
int maxRows = spiQuery.getMaxRows();
|
||||
if (maxRows == 0) {
|
||||
throw new PersistenceException("maxRows must be specified for findPagedList() query");
|
||||
}
|
||||
|
||||
return new LimitOffsetPagedList<T>(this, spiQuery);
|
||||
}
|
||||
|
||||
public <T> void findEach(Query<T> query, QueryEachConsumer<T> consumer, Transaction t) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
|
||||
|
||||
@@ -54,6 +54,7 @@ import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Used to extend the ServerConfig with additional objects used to configure and
|
||||
@@ -165,6 +166,14 @@ public class InternalConfiguration {
|
||||
* Return the list of plugins we collected during construction.
|
||||
*/
|
||||
public List<SpiServerPlugin> getPlugins() {
|
||||
|
||||
// find additional plugins via ServiceLoader ...
|
||||
for (SpiServerPlugin plugin : ServiceLoader.load(SpiServerPlugin.class)) {
|
||||
if (!plugins.contains(plugin)) {
|
||||
plugins.add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
|
||||
@@ -461,7 +461,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
public boolean isHardDeleteDraft() {
|
||||
if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) {
|
||||
// deleting a top level draftable bean
|
||||
if (!beanDescriptor.isDraftInstance(entityBean)) {
|
||||
if (beanDescriptor.isLiveInstance(entityBean)) {
|
||||
throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans");
|
||||
}
|
||||
return true;
|
||||
@@ -474,7 +474,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
* Save or Update is not allowed to execute using 'live' beans - must use publish().
|
||||
*/
|
||||
public void checkDraft() {
|
||||
if (beanDescriptor.isDraftable() && !beanDescriptor.isDraftInstance(entityBean)) {
|
||||
if (beanDescriptor.isDraftable() && beanDescriptor.isLiveInstance(entityBean)) {
|
||||
throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans");
|
||||
}
|
||||
}
|
||||
@@ -754,6 +754,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
intercept.setLoadedProperty(i);
|
||||
}
|
||||
beanDescriptor.setEmbeddedOwner(entityBean);
|
||||
if (!publish) {
|
||||
beanDescriptor.setDraft(entityBean);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.config.EncryptKey;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.IdGenerator;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebean.event.BeanFindController;
|
||||
@@ -154,6 +155,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
|
||||
private final String draftTable;
|
||||
|
||||
/**
|
||||
* DB table comment.
|
||||
*/
|
||||
private final String dbComment;
|
||||
|
||||
/**
|
||||
* Set to true if read auditing is on for this bean type.
|
||||
*/
|
||||
@@ -396,6 +402,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
this.baseTable = InternString.intern(deploy.getBaseTable());
|
||||
this.baseTableAsOf = deploy.getBaseTableAsOf();
|
||||
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
|
||||
this.dbComment = deploy.getDbComment();
|
||||
this.autoTunable = EntityType.ORM.equals(entityType) && (beanFinder == null);
|
||||
|
||||
// helper object used to derive lists of properties
|
||||
@@ -509,6 +516,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ServerConfig.
|
||||
*/
|
||||
public ServerConfig getServerConfig() {
|
||||
return owner.getServerConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the server. Primarily so that the Many's can lazy load.
|
||||
*/
|
||||
@@ -1928,6 +1942,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return EntityType.SQL.equals(entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB comment for the base table.
|
||||
*/
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table. Only properties mapped to the base table are by
|
||||
* default persisted.
|
||||
@@ -2003,14 +2024,25 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean is considered a 'draft' instance.
|
||||
* Return true if the bean is considered a 'draft' instance (not 'live').
|
||||
*/
|
||||
public boolean isDraftInstance(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
return Boolean.TRUE == draft.getValue(entityBean);
|
||||
}
|
||||
// no draft property - so just ignore the check / return true
|
||||
return true;
|
||||
// no draft property - so return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean is draftable and considered a 'live' instance.
|
||||
*/
|
||||
public boolean isLiveInstance(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
return Boolean.FALSE == draft.getValue(entityBean);
|
||||
}
|
||||
// no draft property - so return false
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2284,7 +2316,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
*/
|
||||
public void appendOrderById(SpiQuery<T> query) {
|
||||
|
||||
if (idProperty != null) {
|
||||
if (idProperty != null && !idProperty.isEmbedded()) {
|
||||
OrderBy<T> orderBy = query.getOrderBy();
|
||||
if (orderBy == null || orderBy.isEmpty()) {
|
||||
query.order().asc(idProperty.getName());
|
||||
|
||||
@@ -125,6 +125,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final String serverName;
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private Map<Class<?>, DeployBeanInfo<?>> deplyInfoMap = new HashMap<Class<?>, DeployBeanInfo<?>>();
|
||||
|
||||
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<Class<?>, BeanTable>();
|
||||
@@ -178,8 +180,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
*/
|
||||
public BeanDescriptorManager(InternalConfiguration config) {
|
||||
|
||||
ServerConfig serverConfig = config.getServerConfig();
|
||||
|
||||
this.serverConfig = config.getServerConfig();
|
||||
this.serverName = InternString.intern(serverConfig.getName());
|
||||
this.cacheManager = config.getCacheManager();
|
||||
this.xmlConfig = config.getXmlConfig();
|
||||
@@ -240,6 +241,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerConfig getServerConfig() {
|
||||
return serverConfig;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
|
||||
return (BeanDescriptor<T>) descMap.get(entityType.getName());
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.EncryptKey;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
|
||||
/**
|
||||
@@ -12,26 +13,31 @@ import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
*/
|
||||
public interface BeanDescriptorMap {
|
||||
|
||||
/**
|
||||
* Return the name of the server/database.
|
||||
*/
|
||||
String getServerName();
|
||||
/**
|
||||
* Return the name of the server/database.
|
||||
*/
|
||||
String getServerName();
|
||||
|
||||
/**
|
||||
* Return the Cache Manager.
|
||||
*/
|
||||
ServerCacheManager getCacheManager();
|
||||
/**
|
||||
* Return the ServerConfig.
|
||||
*/
|
||||
ServerConfig getServerConfig();
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor for a given class.
|
||||
*/
|
||||
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
|
||||
/**
|
||||
* Return the Cache Manager.
|
||||
*/
|
||||
ServerCacheManager getCacheManager();
|
||||
|
||||
/**
|
||||
* Return the Encrypt key given the table and column name.
|
||||
*/
|
||||
EncryptKey getEncryptKey(String tableName, String columnName);
|
||||
|
||||
IdBinder createIdBinder(BeanProperty id);
|
||||
/**
|
||||
* Return the BeanDescriptor for a given class.
|
||||
*/
|
||||
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType);
|
||||
|
||||
/**
|
||||
* Return the Encrypt key given the table and column name.
|
||||
*/
|
||||
EncryptKey getEncryptKey(String tableName, String columnName);
|
||||
|
||||
IdBinder createIdBinder(BeanProperty id);
|
||||
|
||||
}
|
||||
|
||||
@@ -211,6 +211,11 @@ public class BeanProperty implements ElPropertyValue {
|
||||
*/
|
||||
final String dbColumnDefn;
|
||||
|
||||
/**
|
||||
* Database DDL column comment.
|
||||
*/
|
||||
final String dbComment;
|
||||
|
||||
/**
|
||||
* DB Constraint (typically check constraint on enum)
|
||||
*/
|
||||
@@ -298,6 +303,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.setter = deploy.getSetter();
|
||||
|
||||
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
|
||||
this.dbComment = deploy.getDbComment();
|
||||
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
|
||||
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
|
||||
this.formula = sqlFormulaSelect != null;
|
||||
@@ -315,7 +321,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
if (softDelete) {
|
||||
ScalarTypeBoolean.BooleanBase boolType = (ScalarTypeBoolean.BooleanBase)scalarType;
|
||||
this.softDeleteDbSet = dbColumn+"="+boolType.getDbTrueLiteral();
|
||||
this.softDeleteDbPredicate = dbColumn+"="+boolType.getDbFalseLiteral();
|
||||
this.softDeleteDbPredicate = "."+dbColumn+","+boolType.getDbFalseLiteral()+")="+boolType.getDbFalseLiteral();
|
||||
} else {
|
||||
this.softDeleteDbSet = null;
|
||||
this.softDeleteDbPredicate = null;
|
||||
@@ -377,6 +383,7 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.secondaryTableJoin = source.secondaryTableJoin;
|
||||
this.secondaryTableJoinPrefix = source.secondaryTableJoinPrefix;
|
||||
|
||||
this.dbComment = source.dbComment;
|
||||
this.dbBind = source.getDbBind();
|
||||
this.dbEncrypted = source.isDbEncrypted();
|
||||
this.dbEncryptedType = source.getDbEncryptedType();
|
||||
@@ -650,7 +657,8 @@ public class BeanProperty implements ElPropertyValue {
|
||||
* Return the DB literal predicate used to filter out soft deleted rows from a query.
|
||||
*/
|
||||
public String getSoftDeleteDbPredicate(String tableAlias) {
|
||||
return tableAlias+"."+softDeleteDbPredicate;
|
||||
// use coalesce to handle null values from optional relationships
|
||||
return "coalesce(" + tableAlias + softDeleteDbPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -998,6 +1006,13 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the comment for the associated DB column.
|
||||
*/
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database jdbc data type this is mapped to.
|
||||
*/
|
||||
|
||||
@@ -85,8 +85,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
public void initialise() {
|
||||
// this *MUST* execute after the BeanDescriptor is
|
||||
// put into the map to stop infinite recursion
|
||||
if (!isTransient){
|
||||
targetDescriptor = descriptor.getBeanDescriptor(targetType);
|
||||
targetDescriptor = descriptor.getBeanDescriptor(targetType);
|
||||
if (!isTransient){
|
||||
targetIdBinder = targetDescriptor.getIdBinder();
|
||||
targetInheritInfo = targetDescriptor.getInheritInfo();
|
||||
saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable();
|
||||
|
||||
@@ -887,7 +887,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
if (help != null) {
|
||||
help.jsonWrite(ctx, name, value, include != null);
|
||||
} else {
|
||||
if (isTransient) {
|
||||
if (isTransient && targetDescriptor == null) {
|
||||
ctx.writeValueUsingObjectMapper(name, value);
|
||||
} else {
|
||||
Collection<?> collection = (Collection<?>)value;
|
||||
|
||||
+5
-1
@@ -31,7 +31,8 @@ public class BeanPropertyAssocManyJsonHelp {
|
||||
*/
|
||||
public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
|
||||
this.many = many;
|
||||
this.jsonTransient = new BeanPropertyAssocManyJsonTransient();
|
||||
boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent();
|
||||
this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +82,9 @@ public class BeanPropertyAssocManyJsonHelp {
|
||||
*/
|
||||
private void jsonReadTransientUsingObjectMapper(ReadJson readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
if (jsonTransient == null) {
|
||||
throw new IllegalStateException("Jackson ObjectMapper is required to read this Transient property "+many.getFullBeanName());
|
||||
}
|
||||
jsonTransient.jsonReadUsingObjectMapper(many, readJson, parentBean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,9 @@ public class DeployBeanDescriptor<T> {
|
||||
|
||||
private ChangeLogFilter changeLogFilter;
|
||||
|
||||
private String dbComment;
|
||||
|
||||
|
||||
/**
|
||||
* Construct the BeanDescriptor.
|
||||
*/
|
||||
@@ -207,6 +210,14 @@ public class DeployBeanDescriptor<T> {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
public void setDbComment(String dbComment) {
|
||||
this.dbComment = dbComment;
|
||||
}
|
||||
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
|
||||
public void setDraftable() {
|
||||
draftable = true;
|
||||
}
|
||||
|
||||
@@ -193,6 +193,8 @@ public class DeployBeanProperty {
|
||||
|
||||
private boolean softDelete;
|
||||
|
||||
private String dbComment;
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
@@ -875,6 +877,7 @@ public class DeployBeanProperty {
|
||||
public void setDraftDirty() {
|
||||
this.draftOnly = true;
|
||||
this.draftDirty = true;
|
||||
this.nullable = false;
|
||||
}
|
||||
|
||||
public boolean isDraftDirty() {
|
||||
@@ -891,10 +894,18 @@ public class DeployBeanProperty {
|
||||
|
||||
public void setSoftDelete() {
|
||||
this.softDelete = true;
|
||||
this.nullable = false;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
public void setDbComment(String dbComment) {
|
||||
this.dbComment = dbComment;
|
||||
}
|
||||
|
||||
public String getDbComment() {
|
||||
return dbComment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.NamedQueries;
|
||||
@@ -9,6 +11,7 @@ import javax.persistence.UniqueConstraint;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
import com.avaje.ebean.annotation.DbComment;
|
||||
import com.avaje.ebean.annotation.Draftable;
|
||||
import com.avaje.ebean.annotation.DraftableElement;
|
||||
import com.avaje.ebean.annotation.EntityConcurrencyMode;
|
||||
@@ -25,22 +28,60 @@ import com.avaje.ebeaninternal.server.deploy.CompoundUniqueConstraint;
|
||||
import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Read the class level deployment annotations.
|
||||
*/
|
||||
public class AnnotationClass extends AnnotationParser {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnnotationClass.class);
|
||||
|
||||
private final String asOfViewSuffix;
|
||||
|
||||
private final String versionsBetweenSuffix;
|
||||
|
||||
/**
|
||||
* Create for normal early parse of class level annotations.
|
||||
*/
|
||||
public AnnotationClass(DeployBeanInfo<?> info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix) {
|
||||
super(info, validationAnnotations);
|
||||
this.asOfViewSuffix = asOfViewSuffix;
|
||||
this.versionsBetweenSuffix = versionsBetweenSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create to parse AttributeOverride annotations which is run last
|
||||
* after all the properties/fields have been parsed fully.
|
||||
*/
|
||||
public AnnotationClass(DeployBeanInfo<?> info) {
|
||||
super(info, false);
|
||||
this.asOfViewSuffix = null;
|
||||
this.versionsBetweenSuffix = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse any AttributeOverride set on the class.
|
||||
*/
|
||||
public void parseAttributeOverride() {
|
||||
|
||||
Class<?> cls = descriptor.getBeanType();
|
||||
AttributeOverride override = cls.getAnnotation(AttributeOverride.class);
|
||||
if (override != null) {
|
||||
String propertyName = override.name();
|
||||
Column column = override.column();
|
||||
if (column != null) {
|
||||
DeployBeanProperty beanProperty = descriptor.getBeanProperty(propertyName);
|
||||
if (beanProperty == null) {
|
||||
logger.error("AttributeOverride property [" + propertyName + "] not found on " + descriptor.getFullName());
|
||||
} else {
|
||||
readColumn(column, beanProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the class level deployment annotations.
|
||||
*/
|
||||
@@ -120,6 +161,11 @@ public class AnnotationClass extends AnnotationParser {
|
||||
descriptor.setHistorySupport();
|
||||
}
|
||||
|
||||
DbComment comment = cls.getAnnotation(DbComment.class);
|
||||
if (comment != null) {
|
||||
descriptor.setDbComment(comment.value());
|
||||
}
|
||||
|
||||
UpdateMode updateMode = cls.getAnnotation(UpdateMode.class);
|
||||
if (updateMode != null) {
|
||||
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
|
||||
|
||||
@@ -166,6 +166,10 @@ public class AnnotationFields extends AnnotationParser {
|
||||
prop.setSoftDelete();
|
||||
}
|
||||
|
||||
DbComment comment = get(prop, DbComment.class);
|
||||
if (comment != null) {
|
||||
prop.setDbComment(comment.value());
|
||||
}
|
||||
DbJson dbJson = get(prop, DbJson.class);
|
||||
if (dbJson != null) {
|
||||
util.setDbJsonType(prop, dbJson);
|
||||
@@ -456,32 +460,5 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void readColumn(Column columnAnn, DeployBeanProperty prop) {
|
||||
|
||||
if (!isEmpty(columnAnn.name())) {
|
||||
String dbColumn = databasePlatform.convertQuotedIdentifiers(columnAnn.name());
|
||||
prop.setDbColumn(dbColumn);
|
||||
}
|
||||
|
||||
prop.setDbInsertable(columnAnn.insertable());
|
||||
prop.setDbUpdateable(columnAnn.updatable());
|
||||
prop.setNullable(columnAnn.nullable());
|
||||
prop.setUnique(columnAnn.unique());
|
||||
if (columnAnn.precision() > 0) {
|
||||
prop.setDbLength(columnAnn.precision());
|
||||
} else if (columnAnn.length() != 255) {
|
||||
// set default 255 on DbTypeMap
|
||||
prop.setDbLength(columnAnn.length());
|
||||
}
|
||||
prop.setDbScale(columnAnn.scale());
|
||||
prop.setDbColumnDefn(columnAnn.columnDefinition());
|
||||
|
||||
String baseTable = descriptor.getBaseTable();
|
||||
String tableName = columnAnn.table();
|
||||
if (!"".equals(tableName) && !tableName.equalsIgnoreCase(baseTable)) {
|
||||
// its on a secondary table...
|
||||
prop.setSecondaryTable(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import java.util.HashMap;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanCascadeInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
|
||||
/**
|
||||
@@ -66,4 +68,31 @@ public abstract class AnnotationParser extends AnnotationBase {
|
||||
|
||||
}
|
||||
|
||||
protected void readColumn(Column columnAnn, DeployBeanProperty prop) {
|
||||
|
||||
if (!isEmpty(columnAnn.name())) {
|
||||
String dbColumn = databasePlatform.convertQuotedIdentifiers(columnAnn.name());
|
||||
prop.setDbColumn(dbColumn);
|
||||
}
|
||||
|
||||
prop.setDbInsertable(columnAnn.insertable());
|
||||
prop.setDbUpdateable(columnAnn.updatable());
|
||||
prop.setNullable(columnAnn.nullable());
|
||||
prop.setUnique(columnAnn.unique());
|
||||
if (columnAnn.precision() > 0) {
|
||||
prop.setDbLength(columnAnn.precision());
|
||||
} else if (columnAnn.length() != 255) {
|
||||
// set default 255 on DbTypeMap
|
||||
prop.setDbLength(columnAnn.length());
|
||||
}
|
||||
prop.setDbScale(columnAnn.scale());
|
||||
prop.setDbColumnDefn(columnAnn.columnDefinition());
|
||||
|
||||
String baseTable = descriptor.getBaseTable();
|
||||
String tableName = columnAnn.table();
|
||||
if (!"".equals(tableName) && !tableName.equalsIgnoreCase(baseTable)) {
|
||||
// its on a secondary table...
|
||||
prop.setSecondaryTable(tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ public class ReadAnnotations {
|
||||
// dependent on field level annotations
|
||||
new AnnotationSql(info, javaxValidationAnnotations).parse();
|
||||
|
||||
new AnnotationClass(info).parseAttributeOverride();
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
throw new RuntimeException("Error reading annotations for " + info, e);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
class InExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = 3150665801693551260L;
|
||||
@@ -54,10 +54,8 @@ class InExpression extends AbstractExpression {
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
|
||||
if (values.length == 0) {
|
||||
if (!not) {
|
||||
// 'no match' for in empty collection
|
||||
request.append("1=0");
|
||||
}
|
||||
String expr = not ? "1=1" : "1=0";
|
||||
request.append(expr);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -314,6 +314,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
return exprList.findPagedList(pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList() {
|
||||
return exprList.findPagedList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findRowCount() {
|
||||
return exprList.findRowCount();
|
||||
@@ -567,7 +572,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> raw(String raw, Object[] values) {
|
||||
public ExpressionList<T> raw(String raw, Object... values) {
|
||||
return exprList.raw(raw, values);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package com.avaje.ebeaninternal.server.lib.sql;
|
||||
|
||||
import com.avaje.ebean.config.DataSourceConfig;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
@@ -12,15 +19,6 @@ import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.DataSourceConfig;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
|
||||
/**
|
||||
* A robust DataSource.
|
||||
* <p>
|
||||
@@ -244,6 +242,13 @@ public class DataSourcePool implements DataSource {
|
||||
|
||||
private void initialise() throws SQLException {
|
||||
|
||||
// Ensure database driver is loaded
|
||||
try {
|
||||
ClassUtil.forName(this.databaseDriver);
|
||||
} catch (Throwable e) {
|
||||
throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation);
|
||||
//noinspection StringBufferReplaceableByString
|
||||
StringBuilder sb = new StringBuilder(70);
|
||||
|
||||
@@ -21,7 +21,9 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
|
||||
private final SpiQuery<T> query;
|
||||
|
||||
private final int pageSize;
|
||||
private final int firstRow;
|
||||
|
||||
private final int maxRows;
|
||||
|
||||
private final int pageIndex;
|
||||
|
||||
@@ -33,11 +35,29 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
|
||||
private List<T> list;
|
||||
|
||||
/**
|
||||
* Construct with pageIndex/pageSize.
|
||||
*/
|
||||
public LimitOffsetPagedList(EbeanServer server, SpiQuery<T> query, int pageIndex, int pageSize) {
|
||||
this.server = server;
|
||||
this.query = query;
|
||||
this.pageSize = pageSize;
|
||||
this.maxRows = pageSize;
|
||||
this.firstRow = pageIndex * pageSize;
|
||||
this.pageIndex = pageIndex;
|
||||
|
||||
query.setFirstRow(firstRow);
|
||||
query.setMaxRows(pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with firstRow/maxRows.
|
||||
*/
|
||||
public LimitOffsetPagedList(EbeanServer server, SpiQuery<T> query) {
|
||||
this.server = server;
|
||||
this.query = query;
|
||||
this.maxRows = query.getMaxRows();
|
||||
this.firstRow = query.getFirstRow();
|
||||
this.pageIndex = 0;
|
||||
}
|
||||
|
||||
public void loadRowCount() {
|
||||
@@ -56,8 +76,6 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
public List<T> getList() {
|
||||
synchronized (monitor) {
|
||||
if (list == null) {
|
||||
query.setFirstRow(pageIndex * pageSize);
|
||||
query.setMaxRows(pageSize);
|
||||
list = server.findList(query, null);
|
||||
}
|
||||
return list;
|
||||
@@ -70,7 +88,7 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
if (rowCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ((rowCount - 1) / pageSize) + 1;
|
||||
return ((rowCount - 1) / maxRows) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +112,11 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return pageIndex < (getTotalPageCount() - 1);
|
||||
return (firstRow + maxRows) < getTotalRowCount();
|
||||
}
|
||||
|
||||
public boolean hasPrev() {
|
||||
return pageIndex > 0;
|
||||
return firstRow > 0;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
@@ -106,13 +124,13 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
return maxRows;
|
||||
}
|
||||
|
||||
public String getDisplayXtoYofZ(String to, String of) {
|
||||
|
||||
int first = pageIndex * pageSize + 1;
|
||||
int last = first + getList().size() - 1;
|
||||
int first = firstRow + 1;
|
||||
int last = firstRow + getList().size();
|
||||
int total = getTotalRowCount();
|
||||
|
||||
return first + to + last + of + total;
|
||||
|
||||
@@ -603,7 +603,7 @@ public class SqlTreeBuilder {
|
||||
// no extra join required for embedded beans
|
||||
return null;
|
||||
}
|
||||
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp);
|
||||
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, assocProp, elGetValue.containsMany());
|
||||
joinRegister.put(propertyName, extraJoin);
|
||||
return extraJoin;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
|
||||
private final boolean manyJoin;
|
||||
|
||||
private final boolean pathContainsMany;
|
||||
|
||||
private List<SqlTreeNodeExtraJoin> children;
|
||||
|
||||
public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty) {
|
||||
public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc<?> assocBeanProperty, boolean pathContainsMany) {
|
||||
this.prefix = prefix;
|
||||
this.assocBeanProperty = assocBeanProperty;
|
||||
this.pathContainsMany = pathContainsMany;
|
||||
this.manyJoin = assocBeanProperty instanceof BeanPropertyAssocMany<?>;
|
||||
}
|
||||
|
||||
@@ -95,13 +98,16 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
if (!manyToMany) {
|
||||
if (pathContainsMany) {
|
||||
// "promote" to left outer as the path contains a many
|
||||
assocBeanProperty.addJoin(SqlJoinType.OUTER, prefix, ctx);
|
||||
} else if (!manyToMany) {
|
||||
assocBeanProperty.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
|
||||
if (manyJoin) {
|
||||
if (manyJoin || pathContainsMany) {
|
||||
// if AUTO then make all descendants use OUTER JOIN
|
||||
joinType = joinType.autoToOuter();
|
||||
}
|
||||
|
||||
@@ -529,6 +529,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
if (detail != null) {
|
||||
copy.detail = detail.copy();
|
||||
}
|
||||
if (temporalMode == TemporalMode.DRAFT) {
|
||||
copy.temporalMode = TemporalMode.DRAFT;
|
||||
}
|
||||
|
||||
copy.firstRow = firstRow;
|
||||
copy.maxRows = maxRows;
|
||||
@@ -1094,6 +1097,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return server.findPagedList(this, null, pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList() {
|
||||
return server.findPagedList(this, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an ordered bind parameter according to its position. Note that the position starts at 1 to
|
||||
* be consistent with JDBC PreparedStatement. You need to set a parameter value for each ? you
|
||||
|
||||
+21
@@ -2,11 +2,13 @@ package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
@@ -29,4 +31,23 @@ public class ExplicitTransactionManager extends TransactionManager {
|
||||
return new ExplicitJdbcTransaction(prefix + id, explicit, c, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the initialise of OnQueryOnly with the intention not to use CLOSE with ExplicitJdbcTransaction.
|
||||
*/
|
||||
@Override
|
||||
protected DatabasePlatform.OnQueryOnly initOnQueryOnly(DatabasePlatform.OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
|
||||
// first check for a system property 'override'
|
||||
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
|
||||
if (systemPropertyValue != null) {
|
||||
return DatabasePlatform.OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase());
|
||||
}
|
||||
|
||||
if (DatabasePlatform.OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) {
|
||||
// Not using OnQueryOnly.CLOSE with ExplicitJdbcTransaction
|
||||
return DatabasePlatform.OnQueryOnly.COMMIT;
|
||||
}
|
||||
// default to commit if not defined on the platform
|
||||
return dbPlatformOnQueryOnly == null ? DatabasePlatform.OnQueryOnly.COMMIT : dbPlatformOnQueryOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class TransactionManager {
|
||||
* just for queries do need to be committed or rollback after the query.
|
||||
* </p>
|
||||
*/
|
||||
private OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
protected OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
|
||||
|
||||
// first check for a system property 'override'
|
||||
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
|
||||
@@ -190,7 +190,7 @@ public class TransactionManager {
|
||||
/**
|
||||
* Return true if the isolation level is read committed.
|
||||
*/
|
||||
private boolean isReadCommittedIsolation(DataSource ds) {
|
||||
protected boolean isReadCommittedIsolation(DataSource ds) {
|
||||
|
||||
if (DbOffline.isSet()) {
|
||||
return true;
|
||||
|
||||
@@ -553,7 +553,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
maxValueLen = Math.max(maxValueLen, value.length());
|
||||
|
||||
Object enumValue = Enum.valueOf(enumType, name.trim());
|
||||
beanDbMap.add(enumValue, value.trim());
|
||||
beanDbMap.add(enumValue, value);
|
||||
}
|
||||
|
||||
if (dbColumnLength == 0 && !integerType) {
|
||||
|
||||
@@ -466,7 +466,7 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
}
|
||||
|
||||
/**
|
||||
* If a jarfile with a manifest claspath return that.
|
||||
* If a jarfile with a manifest classpath return that.
|
||||
*/
|
||||
private static List<URI> getClassPathFromManifest(File jarFile, Manifest manifest) {
|
||||
|
||||
|
||||
@@ -201,6 +201,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return query.findPagedList(pageIndex, pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<T> findPagedList() {
|
||||
return query.findPagedList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findRowCount() {
|
||||
return query.findRowCount();
|
||||
@@ -691,7 +696,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> raw(String raw, Object[] values) {
|
||||
public ExpressionList<T> raw(String raw, Object... values) {
|
||||
add(expr.raw(raw, values));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
com.avaje.ebean.dbmigration.DdlGenerator
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.RawSql.Sql;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class TestRawSqlBuilder extends BaseTestCase {
|
||||
|
||||
@@ -17,8 +19,27 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id", sql.getPreFrom());
|
||||
assertEquals("from t_cust", sql.getPreWhere());
|
||||
assertEquals("", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNewLineCharacters() {
|
||||
|
||||
RawSqlBuilder r = RawSqlBuilder.parse("select\n id from\n o_customer");
|
||||
Sql sql = r.getSql();
|
||||
|
||||
assertEquals("id", sql.getPreFrom());
|
||||
assertEquals("from o_customer", sql.getPreWhere());
|
||||
assertEquals("", sql.getPreHaving());
|
||||
assertNull(sql.getOrderBy());
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql = r.create();
|
||||
|
||||
Ebean.find(Customer.class)
|
||||
.setRawSql(rawSql)
|
||||
.findList();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -29,7 +50,7 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id", sql.getPreFrom());
|
||||
assertEquals("from t_cust where id > ?", sql.getPreWhere());
|
||||
assertEquals("", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,7 +108,7 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id, sum(x)", sql.getPreFrom());
|
||||
assertEquals("from t_cust", sql.getPreWhere());
|
||||
assertEquals("group by id having sum(x) > ?", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
assertEquals("order by", sql.getOrderByPrefix());
|
||||
|
||||
// no order by
|
||||
@@ -97,7 +118,7 @@ public class TestRawSqlBuilder extends BaseTestCase {
|
||||
assertEquals("id, sum(x)", sql.getPreFrom());
|
||||
assertEquals("from t_cust where id > ?", sql.getPreWhere());
|
||||
assertEquals("group by id having sum(x) > ?", sql.getPreHaving());
|
||||
Assert.assertNull(sql.getOrderBy());
|
||||
assertNull(sql.getOrderBy());
|
||||
assertEquals("order by", sql.getOrderByPrefix());
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,42 @@ public class TestRawSqlColumnParsing extends TestCase {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void test_withDatabaseFunction() {
|
||||
|
||||
ColumnMapping columnMapping = DRawSqlColumnsParser.parse("a a0,b b1, MONTH(MAKEDATE(2015, 241)) m2 , d d3 , e e4 ");
|
||||
Map<String, Column> mapping = columnMapping.mapping();
|
||||
|
||||
assertEquals(5, mapping.size());
|
||||
|
||||
Column c = mapping.get("a");
|
||||
|
||||
assertEquals("a",c.getDbColumn());
|
||||
assertEquals(0, c.getIndexPos());
|
||||
assertEquals("a0",c.getPropertyName());
|
||||
|
||||
c = mapping.get("b");
|
||||
assertEquals("b",c.getDbColumn());
|
||||
assertEquals(1, c.getIndexPos());
|
||||
assertEquals("b1",c.getPropertyName());
|
||||
|
||||
c = mapping.get("MONTH(MAKEDATE(2015, 241))");
|
||||
assertEquals("MONTH(MAKEDATE(2015, 241))",c.getDbColumn());
|
||||
assertEquals(2, c.getIndexPos());
|
||||
assertEquals("m2",c.getPropertyName());
|
||||
|
||||
c = mapping.get("d");
|
||||
assertEquals("d",c.getDbColumn());
|
||||
assertEquals(3, c.getIndexPos());
|
||||
assertEquals("d3",c.getPropertyName());
|
||||
|
||||
|
||||
c = mapping.get("e");
|
||||
assertEquals("e",c.getDbColumn());
|
||||
assertEquals(4, c.getIndexPos());
|
||||
assertEquals("e4",c.getPropertyName());
|
||||
|
||||
}
|
||||
|
||||
public void test_withAsAlias() {
|
||||
|
||||
ColumnMapping columnMapping = DRawSqlColumnsParser.parse("a as a0,'b' b1, \"c(blah)\" as c2 , d as d3 , e as e4 ");
|
||||
|
||||
@@ -17,6 +17,6 @@ public class DbMigrationConfigTest {
|
||||
|
||||
DbMigrationConfig migrationConfig = config.getMigrationConfig();
|
||||
|
||||
assertThat(migrationConfig.getResourcePath()).isEqualTo("dbmigration/myapp");
|
||||
assertThat(migrationConfig.getMigrationPath()).isEqualTo("dbmigration/myapp");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbPlatformName;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
@@ -17,6 +18,18 @@ public class PropertiesWrapperTest {
|
||||
assertEquals("myserver", pw.getServerName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEnum() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("platform","postgres");
|
||||
|
||||
PropertiesWrapper pw = new PropertiesWrapper("pref", "myserver", properties);
|
||||
assertEquals(DbPlatformName.POSTGRES, pw.getEnum(DbPlatformName.class, "platform", DbPlatformName.H2));
|
||||
assertEquals(DbPlatformName.H2, pw.getEnum(DbPlatformName.class, "junk", DbPlatformName.H2));
|
||||
assertNull(pw.getEnum(DbPlatformName.class, "junk", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrimPropertyValues() {
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.avaje.ebean.dbmigration;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
public class DdlParserTest {
|
||||
|
||||
DdlParser parser = new DdlParser();
|
||||
|
||||
@Test
|
||||
public void parse_ignoresEmptyLines() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("\n\none;\n\ntwo;\n\n"));
|
||||
|
||||
assertThat(stmts).hasSize(2);
|
||||
assertThat(stmts).contains("one;","two;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_ignoresComments_whenFirst() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("-- comment\ntwo;"));
|
||||
|
||||
assertThat(stmts).hasSize(1);
|
||||
assertThat(stmts).contains("two;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_ignoresEmptyLines_whenFirst() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("\n\n-- comment\ntwo;\n\n"));
|
||||
assertThat(stmts).hasSize(1);
|
||||
assertThat(stmts).contains("two;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse_inlineEmptyLines_replacedWithSpace() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("\n\n-- comment\none\ntwo;\n\n"));
|
||||
assertThat(stmts).hasSize(1);
|
||||
assertThat(stmts).contains("one two;");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void parse_ignoresComments() throws Exception {
|
||||
|
||||
List<String> stmts = parser.parse(new StringReader("one;\n-- comment\ntwo;"));
|
||||
|
||||
assertThat(stmts).hasSize(2);
|
||||
assertThat(stmts).contains("one;","two;");
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
public class MigrationModelTest {
|
||||
|
||||
@Test
|
||||
public void testRead() throws Exception {
|
||||
|
||||
MigrationModel migrationModel = new MigrationModel("dbmigration/app1");
|
||||
ModelContainer model = migrationModel.read();
|
||||
|
||||
assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0");
|
||||
assertThat(model.getTable("v10_table")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead_leadingSlash() throws Exception {
|
||||
|
||||
MigrationModel migrationModel = new MigrationModel("/dbmigration/app1");
|
||||
ModelContainer model = migrationModel.read();
|
||||
|
||||
assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0");
|
||||
assertThat(model.getTable("v10_table")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead_trailingSlash() throws Exception {
|
||||
|
||||
MigrationModel migrationModel = new MigrationModel("/dbmigration/app1/");
|
||||
ModelContainer model = migrationModel.read();
|
||||
|
||||
assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0");
|
||||
assertThat(model.getTable("v10_table")).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
|
||||
public class MigrationVersionTest {
|
||||
|
||||
@Test
|
||||
public void testParse() throws Exception {
|
||||
|
||||
MigrationVersion v0 = MigrationVersion.parse("1.1.1_2__Foo");
|
||||
MigrationVersion v1 = MigrationVersion.parse("1.1.1.2_junk");
|
||||
MigrationVersion v2 = MigrationVersion.parse("1.1_1.2_foo");
|
||||
|
||||
assertThat(v0.compareTo(v1)).isEqualTo(0);
|
||||
assertThat(v1.compareTo(v0)).isEqualTo(0);
|
||||
assertThat(v1.compareTo(v2)).isEqualTo(0);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextVersion() {
|
||||
|
||||
assertThat(MigrationVersion.parse("2").nextVersion()).isEqualTo("3");
|
||||
assertThat(MigrationVersion.parse("1.0").nextVersion()).isEqualTo("1.1");
|
||||
assertThat(MigrationVersion.parse("2.0.b34").nextVersion()).isEqualTo("2.1");
|
||||
assertThat(MigrationVersion.parse("1.1.1_2__Foo").nextVersion()).isEqualTo("1.1.1.3");
|
||||
assertThat(MigrationVersion.parse("1.1.1.2_junk").nextVersion()).isEqualTo("1.1.1.3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareTo() throws Exception {
|
||||
|
||||
MigrationVersion v1 = MigrationVersion.parse("1.1.1.2_junk");
|
||||
MigrationVersion v2 = MigrationVersion.parse("2.1_1.2_junk");
|
||||
MigrationVersion v3 = MigrationVersion.parse("1.2_1.2_junk");
|
||||
MigrationVersion v4 = MigrationVersion.parse("1.1_1.3_junk");
|
||||
MigrationVersion v5 = MigrationVersion.parse("1.1.1.1_junk");
|
||||
|
||||
assertThat(v1.compareTo(v2)).isEqualTo(-1);
|
||||
assertThat(v1.compareTo(v3)).isEqualTo(-1);
|
||||
assertThat(v1.compareTo(v4)).isEqualTo(-1);
|
||||
|
||||
assertThat(v1.compareTo(v5)).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebean.server.type;
|
||||
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
|
||||
/**
|
||||
* Enum test when DB CHAR column used with spaces.
|
||||
*/
|
||||
public enum MyDayOfWeek {
|
||||
|
||||
@EnumValue("MONDAY ") MONDAY,
|
||||
@EnumValue("TUESDAY ") TUESDAY,
|
||||
@EnumValue("WEDNESDAY") WEDNESDAY,
|
||||
@EnumValue("THURSDAY ") THURSDAY,
|
||||
@EnumValue("FRIDAY ") FRIDAY,
|
||||
@EnumValue("SATURDAY ") SATURDAY,
|
||||
@EnumValue("SUNDAY ") SUNDAY
|
||||
}
|
||||
@@ -1,34 +1,55 @@
|
||||
package com.avaje.ebean.server.type;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.H2Platform;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.type.CtCompoundType;
|
||||
import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
|
||||
import com.avaje.ebeaninternal.server.type.RsetDataReader;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarDataReader;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
|
||||
import com.avaje.tests.model.ivo.CMoney;
|
||||
import com.avaje.tests.model.ivo.ExhangeCMoneyRate;
|
||||
import com.avaje.tests.model.ivo.Money;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestTypeManager extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testEnumWithChar() throws SQLException {
|
||||
|
||||
DefaultTypeManager typeManager = createTypeManager();
|
||||
|
||||
ScalarType<?> dayOfWeekType = typeManager.createEnumScalarType(MyDayOfWeek.class);
|
||||
|
||||
Object val = dayOfWeekType.read(new DummyDataReader("MONDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.MONDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("TUESDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.TUESDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("WEDNESDAY"));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.WEDNESDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("THURSDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.THURSDAY);
|
||||
|
||||
val = dayOfWeekType.read(new DummyDataReader("FRIDAY "));
|
||||
assertThat(val).isEqualTo(MyDayOfWeek.FRIDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ServerConfig serverConfig = new ServerConfig();
|
||||
serverConfig.setDatabasePlatform(new H2Platform());
|
||||
|
||||
BootupClasses bootupClasses = new BootupClasses();
|
||||
|
||||
DefaultTypeManager typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
|
||||
DefaultTypeManager typeManager = createTypeManager();
|
||||
|
||||
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(Money.class);
|
||||
Assert.assertTrue(checkImmutable.isImmutable());
|
||||
@@ -50,4 +71,31 @@ public class TestTypeManager extends BaseTestCase {
|
||||
|
||||
}
|
||||
|
||||
private DefaultTypeManager createTypeManager() {
|
||||
|
||||
ServerConfig serverConfig = new ServerConfig();
|
||||
serverConfig.setDatabasePlatform(new H2Platform());
|
||||
|
||||
BootupClasses bootupClasses = new BootupClasses();
|
||||
|
||||
return new DefaultTypeManager(serverConfig, bootupClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test double DataReader implementation.
|
||||
*/
|
||||
private static class DummyDataReader extends RsetDataReader {
|
||||
|
||||
String val;
|
||||
|
||||
public DummyDataReader(String val) {
|
||||
super(null);
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString() throws SQLException {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,11 +82,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlGenerator getDdlGenerator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadAuditLogger getReadAuditLogger() {
|
||||
return null;
|
||||
@@ -512,6 +507,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Set<T> findSet(Query<T> query, Transaction transaction) {
|
||||
return null;
|
||||
|
||||
@@ -3,7 +3,9 @@ package com.avaje.tests.autofetch;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -33,34 +35,6 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
serverCacheManager.setCaching(Order.class, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSelectNull() {
|
||||
|
||||
init();
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select(null);
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
tunedInfo.tuneQuery((SpiQuery<?>) query);
|
||||
|
||||
Order order = query.findUnique();
|
||||
EntityBean eb = (EntityBean)order;
|
||||
EntityBeanIntercept ebi = eb._ebean_getIntercept();
|
||||
|
||||
Assert.assertTrue(ebi.isFullyLoadedBean());
|
||||
|
||||
Set<String> loadedPropertyNames = ebi.getLoadedPropertyNames();
|
||||
Assert.assertNull(loadedPropertyNames);
|
||||
|
||||
// invoke lazy loading
|
||||
order.getCustomer();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void withSelectEmpty() {
|
||||
|
||||
@@ -68,9 +42,9 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
|
||||
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
tunedInfo.tuneQuery((SpiQuery<?>) query);
|
||||
@@ -95,9 +69,9 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("somethingThatDoesNotExist");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
|
||||
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
tunedInfo.tuneQuery((SpiQuery<?>) query);
|
||||
@@ -123,7 +97,14 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
Assert.assertTrue(loggedSql.get(0).contains("select t0.id c0, t0.id c1 from o_order t0 where t0.id = ?"));
|
||||
Assert.assertTrue(loggedSql.get(1).contains("select t0.id c0, t0.status c1,"));
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) {
|
||||
Origin origin = new Origin();
|
||||
origin.setDetail(tunedDetail.toString());
|
||||
return new TunedQueryInfo(origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSelectSomeIncludeLazyLoaded() {
|
||||
|
||||
@@ -132,7 +113,7 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("status, customer");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
@@ -167,7 +148,7 @@ public class TunedQueryInfoTest extends BaseTestCase {
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("status");
|
||||
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<Order> query = server.find(Order.class).setId(1);
|
||||
|
||||
|
||||
@@ -2,23 +2,37 @@ package com.avaje.tests.basic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.avaje.ebean.Query;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestInEmpty extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
public void test_in_empty() {
|
||||
|
||||
List<Order> list = Ebean.find(Order.class).where().gt("id", 0).in("id", new Object[0])
|
||||
.findList();
|
||||
Query<Order> query = Ebean.find(Order.class).where().in("id", new Object[0]).gt("id", 0)
|
||||
.query();
|
||||
|
||||
Assert.assertEquals(0, list.size());
|
||||
List<Order> list = query.findList();
|
||||
assertThat(query.getGeneratedSql()).contains("1=0");
|
||||
assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_notIn_empty() {
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class).where().notIn("id", new Object[0]).gt("id", 0)
|
||||
.query();
|
||||
|
||||
query.findList();
|
||||
assertThat(query.getGeneratedSql()).contains("1=1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.tests.cache;
|
||||
|
||||
import com.avaje.ebeaninternal.server.autotune.model.Origin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -14,6 +16,13 @@ import com.avaje.tests.model.basic.FeatureDescription;
|
||||
|
||||
public class TestL2CacheWithSharedBean extends BaseTestCase {
|
||||
|
||||
@NotNull
|
||||
private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) {
|
||||
Origin origin = new Origin();
|
||||
origin.setDetail(tunedDetail.toString());
|
||||
return new TunedQueryInfo(origin);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
@@ -28,7 +37,7 @@ public class TestL2CacheWithSharedBean extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail tunedDetail = new OrmQueryDetail();
|
||||
tunedDetail.select("name");
|
||||
TunedQueryInfo tunedInfo = new TunedQueryInfo(tunedDetail);
|
||||
TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
|
||||
|
||||
Query<FeatureDescription> query = Ebean.find(FeatureDescription.class).setId(f1.getId());
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.avaje.tests.compositekeys;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.PagedList;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -51,6 +52,8 @@ public class TestCKeyLazyLoad extends BaseTestCase {
|
||||
|
||||
Ebean.save(p2);
|
||||
|
||||
exerciseMaxRowsQuery_with_embeddedId();
|
||||
|
||||
CKeyParentId searchId = new CKeyParentId(1, "one");
|
||||
|
||||
CKeyParent found = Ebean.find(CKeyParent.class).where().idEq(searchId).findUnique();
|
||||
@@ -79,4 +82,15 @@ public class TestCKeyLazyLoad extends BaseTestCase {
|
||||
Assert.assertTrue(idInTestList.size() == 2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Exercise paging/maxRows type query with EmbeddedId.
|
||||
*/
|
||||
private void exerciseMaxRowsQuery_with_embeddedId() {
|
||||
|
||||
PagedList<CKeyParent> siteUserPage = Ebean.find(CKeyParent.class).where()
|
||||
.orderBy("name asc")
|
||||
.findPagedList(0, 10);
|
||||
siteUserPage.getList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
package com.avaje.tests.draftable;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.draftable.Doc;
|
||||
import com.avaje.tests.model.draftable.Link;
|
||||
import org.assertj.core.api.StrictAssertions;
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DocLinkTest extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testLazyLoadOnDraftProperty() {
|
||||
|
||||
Link link1 = new Link("something");
|
||||
link1.save();
|
||||
|
||||
Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
Link link = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.select("name")
|
||||
.findUnique();
|
||||
|
||||
assertThat(link).isNotNull();
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
// no lazy loading is invoked as draft property considered @Transient
|
||||
assertThat(link.isDraft()).isFalse();
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).isEmpty();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate_whenNotPublished() {
|
||||
|
||||
Link link1 = new Link("update");
|
||||
StrictAssertions.assertThat(link1.isDraft()).isFalse();
|
||||
|
||||
link1.save();
|
||||
StrictAssertions.assertThat(link1.isDraft()).isTrue();
|
||||
|
||||
// perform stateless update
|
||||
Link linkUpdate = new Link();
|
||||
linkUpdate.setId(link1.getId());
|
||||
linkUpdate.setComment("stateless update");
|
||||
linkUpdate.setDraft(true);
|
||||
linkUpdate.update();
|
||||
|
||||
// invoke lazy loading on the updated bean
|
||||
// automatically set asDraft() on lazy loading query
|
||||
linkUpdate.getLocation();
|
||||
|
||||
Ebean.deletePermanent(linkUpdate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete_whenNotPublished() {
|
||||
|
||||
Link link1 = new Link("Ld1");
|
||||
StrictAssertions.assertThat(link1.isDraft()).isFalse();
|
||||
|
||||
link1.save();
|
||||
StrictAssertions.assertThat(link1.isDraft()).isTrue();
|
||||
|
||||
link1.setComment("some change");
|
||||
link1.save();
|
||||
|
||||
Ebean.delete(link1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeletePermanent_whenPublished2() {
|
||||
|
||||
Link link1 = new Link("Ld2");
|
||||
link1.save();
|
||||
Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
Link link = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
Ebean.deletePermanent(link);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteLivePermanent_throwsException() {
|
||||
|
||||
Link link1 = new Link("Ld2");
|
||||
link1.save();
|
||||
|
||||
Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
try {
|
||||
Ebean.deletePermanent(live);
|
||||
assertTrue("never get here",false);
|
||||
|
||||
} catch (PersistenceException e) {
|
||||
// assert nice message when trying to delete live bean
|
||||
assertThat(e.getMessage().contains("Explicit Delete is not allowed on a 'live' bean - only draft beans"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete_whenPublished() {
|
||||
|
||||
Link link1 = new Link("Ld2");
|
||||
link1.save();
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
server.publish(Link.class, link1.getId());
|
||||
|
||||
link1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
StrictAssertions.assertThat(link1.isDraft()).isTrue();
|
||||
|
||||
// this is a soft delete (no automatic publish here, only updates draft)
|
||||
link1.delete();
|
||||
|
||||
Link live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
|
||||
assertThat(live).isNotNull();
|
||||
StrictAssertions.assertThat(live.isDraft()).isFalse();
|
||||
StrictAssertions.assertThat(live.isDeleted()).isFalse(); // soft delete state not published yet
|
||||
|
||||
// this is a permanent delete (effectively has automatic publish)
|
||||
server.deletePermanent(link1);
|
||||
|
||||
live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
|
||||
assertThat(live).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateLive_throwsException() {
|
||||
|
||||
Link link1 = new Link("forUpdateLive");
|
||||
link1.save();
|
||||
|
||||
Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());
|
||||
|
||||
live.setComment("foo");
|
||||
// Expect a nice
|
||||
try {
|
||||
live.save();
|
||||
assertTrue("Never get here",false);
|
||||
|
||||
} catch (PersistenceException e) {
|
||||
// we want to assert the message is nice and meaningful (and not a optimistic locking exception etc)
|
||||
assertThat(e.getMessage()).contains("Save or update is not allowed on a 'live' bean - only draft beans");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirtyState() {
|
||||
|
||||
Timestamp when = new Timestamp(System.currentTimeMillis());
|
||||
String comment = "Really interesting";
|
||||
|
||||
Link link1 = new Link("Ls1");
|
||||
link1.setComment(comment);
|
||||
link1.setWhenPublish(when);
|
||||
link1.save();
|
||||
|
||||
Link draft1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
StrictAssertions.assertThat(draft1.isDirty()).isTrue();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
Link linkLive = server.publish(Link.class, link1.getId(), null);
|
||||
StrictAssertions.assertThat(linkLive.getComment()).isEqualTo(comment);
|
||||
StrictAssertions.assertThat(linkLive.getWhenPublish()).isEqualTo(when);
|
||||
|
||||
Link draft1b = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
|
||||
StrictAssertions.assertThat(draft1b.isDirty()).isFalse();
|
||||
StrictAssertions.assertThat(draft1b.getComment()).isNull();
|
||||
StrictAssertions.assertThat(draft1b.getWhenPublish()).isNull();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSave() {
|
||||
|
||||
Link link1 = new Link("LinkOne");
|
||||
link1.save();
|
||||
|
||||
Link link2 = new Link("LinkTwo");
|
||||
link2.save();
|
||||
|
||||
Link link3 = new Link("LinkThree");
|
||||
link3.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
server.publish(Link.class, link1.getId(), null);
|
||||
server.publish(Link.class, link2.getId(), null);
|
||||
server.publish(Link.class, link3.getId(), null);
|
||||
|
||||
Doc doc1 = new Doc("DocOne");
|
||||
doc1.getLinks().add(link1);
|
||||
doc1.getLinks().add(link2);
|
||||
doc1.save();
|
||||
|
||||
Doc draftDoc1 = server.find(Doc.class)
|
||||
.setId(doc1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
assertThat(draftDoc1.getLinks()).hasSize(2);
|
||||
|
||||
Doc liveDoc1 = server.publish(Doc.class, doc1.getId(), null);
|
||||
|
||||
assertThat(liveDoc1.getLinks()).hasSize(2);
|
||||
assertThat(liveDoc1.getLinks()).extracting("id").contains(link1.getId(), link2.getId());
|
||||
|
||||
|
||||
draftDoc1.getLinks().remove(0);
|
||||
draftDoc1.getLinks().add(link3);
|
||||
|
||||
draftDoc1.save();
|
||||
|
||||
// publish with insert and delete of Links M2M relationship
|
||||
Doc liveDoc2 = server.publish(Doc.class, doc1.getId(), null);
|
||||
assertThat(liveDoc2.getLinks()).hasSize(2);
|
||||
assertThat(liveDoc2.getLinks()).extracting("id").contains(link2.getId(), link3.getId());
|
||||
|
||||
// delete the draft and live beans (with associated children)
|
||||
draftDoc1.delete();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDraftRestore() {
|
||||
|
||||
Link link1 = new Link("Ldr1");
|
||||
link1.setLocation("firstLocation");
|
||||
link1.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
Link live = server.publish(Link.class, link1.getId(), null);
|
||||
StrictAssertions.assertThat(live.isDraft()).isFalse();
|
||||
|
||||
Link draftLink = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
draftLink.setLocation("secondLocation");
|
||||
draftLink.save();
|
||||
|
||||
server.draftRestore(Link.class, link1.getId(), null);
|
||||
|
||||
draftLink = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
StrictAssertions.assertThat(draftLink.getLocation()).isEqualTo("firstLocation");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDraftRestoreViaQuery() {
|
||||
|
||||
Link link1 = new Link("Ldr1");
|
||||
link1.setLocation("firstLocation");
|
||||
link1.setComment("Banana");
|
||||
link1.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
server.publish(Link.class, link1.getId(), null);
|
||||
|
||||
Link draftLink = Ebean.find(Link.class)
|
||||
.setId(link1.getId())
|
||||
.asDraft()
|
||||
.findUnique();
|
||||
|
||||
draftLink.setLocation("secondLocation");
|
||||
draftLink.setComment("A good change");
|
||||
draftLink.save();
|
||||
|
||||
Query<Link> query = server.find(Link.class).where().eq("id", link1.getId()).query();
|
||||
List<Link> links = server.draftRestore(query);
|
||||
|
||||
assertThat(links).hasSize(1);
|
||||
StrictAssertions.assertThat(links.get(0).getLocation()).isEqualTo("firstLocation");
|
||||
StrictAssertions.assertThat(links.get(0).isDirty()).isEqualTo(false);
|
||||
StrictAssertions.assertThat(links.get(0).getComment()).isNull();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.avaje.tests.draftable;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.draftable.Link;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class LinkQueryPublishTest {
|
||||
|
||||
@Test
|
||||
public void testPublishViaQuery() {
|
||||
|
||||
Link link1 = new Link("L1");
|
||||
link1.save();
|
||||
|
||||
Link link2 = new Link("L2");
|
||||
link2.save();
|
||||
|
||||
Link link3 = new Link("L3");
|
||||
link3.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
List<Object> ids = new ArrayList<Object>();
|
||||
ids.add(link1.getId());
|
||||
ids.add(link2.getId());
|
||||
ids.add(link3.getId());
|
||||
|
||||
PagedList<Link> pagedList =
|
||||
server.find(Link.class).asDraft()
|
||||
.where().idIn(ids)
|
||||
.setMaxRows(10)
|
||||
.findPagedList();
|
||||
|
||||
assertThat(pagedList.getTotalRowCount()).isEqualTo(3);
|
||||
|
||||
|
||||
Query<Link> pubQuery = server.find(Link.class)
|
||||
.where().idIn(ids)
|
||||
.order().asc("id");
|
||||
|
||||
|
||||
List<Link> pubList = server.publish(pubQuery);
|
||||
|
||||
assertThat(pubList).hasSize(3);
|
||||
assertThat(pubList).extracting("id").contains(link1.getId(), link2.getId(), link3.getId());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.avaje.tests.draftable;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.tests.model.draftable.Document;
|
||||
import com.avaje.tests.model.draftable.DocumentMedia;
|
||||
import com.avaje.tests.model.draftable.Organisation;
|
||||
import org.assertj.core.api.StrictAssertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class OrganisationTest {
|
||||
|
||||
@Test
|
||||
public void testSave() {
|
||||
|
||||
Organisation org = new Organisation("OrgOne");
|
||||
org.save();
|
||||
|
||||
assertNotNull(org.getId());
|
||||
|
||||
Document doc = new Document();
|
||||
doc.setTitle("NewTitle");
|
||||
doc.setOrganisation(org);
|
||||
doc.setBody("Hello");
|
||||
|
||||
doc.save();
|
||||
|
||||
doc.setBody("Change content");
|
||||
doc.save();
|
||||
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
Document draftDoc = server.find(Document.class)
|
||||
.asDraft()
|
||||
.setId(doc.getId())
|
||||
.findUnique();
|
||||
|
||||
assertNotNull(draftDoc);
|
||||
|
||||
Document liveDoc = server.find(Document.class)
|
||||
.setId(doc.getId())
|
||||
.findUnique();
|
||||
assertNull(liveDoc);
|
||||
|
||||
|
||||
server.publish(Document.class, doc.getId(), null);
|
||||
|
||||
doc.setTitle("Mod1");
|
||||
doc.save();
|
||||
|
||||
server.publish(Document.class, doc.getId(), null);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testSaveWithCascade() {
|
||||
|
||||
Organisation org = new Organisation("Org2");
|
||||
org.save();
|
||||
|
||||
Document doc = new Document();
|
||||
doc.setTitle("Title1");
|
||||
doc.setOrganisation(org);
|
||||
doc.setBody("Body1");
|
||||
|
||||
doc.getMedia().add(createMedia("media1"));
|
||||
doc.getMedia().add(createMedia("media2"));
|
||||
doc.save();
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
|
||||
|
||||
server.publish(Document.class, doc.getId(), null);
|
||||
|
||||
Document fetchDoc = Ebean.find(Document.class).setId(doc.getId()).asDraft().findUnique();
|
||||
List<DocumentMedia> media = fetchDoc.getMedia();
|
||||
|
||||
assertThat(media.size()).isEqualTo(2);
|
||||
|
||||
// // delete one of the 'child' @DraftElement rows ...
|
||||
// SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from document_media_draft where id = ?");
|
||||
// sqlUpdate.setParameter(1, doc.getMedia().get(0).getId());
|
||||
// sqlUpdate.execute();
|
||||
|
||||
doc.getMedia().get(1).setDescription("mod");
|
||||
doc.getMedia().add(createMedia("media3"));
|
||||
doc.getMedia().remove(0);
|
||||
doc.setBody("Body2");
|
||||
doc.save();
|
||||
|
||||
// publish will perform an insert, update and delete on child DocumentMedia
|
||||
// during the publish below with media1 being deleted
|
||||
Document liveBean = server.publish(Document.class, doc.getId(), null);
|
||||
StrictAssertions.assertThat(liveBean.getBody()).isEqualTo("Body2");
|
||||
StrictAssertions.assertThat(liveBean.getMedia().size()).isEqualTo(2);
|
||||
assertThat(liveBean.getMedia()).extracting("name").contains("media2","media3");
|
||||
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DocumentMedia createMedia(String name) {
|
||||
DocumentMedia media = new DocumentMedia();
|
||||
media.setName(name);
|
||||
return media;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package com.avaje.tests.inheritance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import com.avaje.tests.model.basic.CarAccessory;
|
||||
import com.avaje.tests.model.basic.CarFuse;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
@@ -13,6 +14,11 @@ import com.avaje.tests.model.basic.Truck;
|
||||
import com.avaje.tests.model.basic.Vehicle;
|
||||
import com.avaje.tests.model.basic.VehicleDriver;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestInheritInsert extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
@@ -25,11 +31,11 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
Vehicle v = Ebean.find(Vehicle.class, t.getId());
|
||||
if (v instanceof Truck) {
|
||||
Truck t0 = (Truck) v;
|
||||
Assert.assertEquals(Double.valueOf(10d), t0.getCapacity());
|
||||
Assert.assertEquals(Double.valueOf(10d), ((Truck) v).getCapacity());
|
||||
Assert.assertNotNull(t0.getId());
|
||||
assertEquals(Double.valueOf(10d), t0.getCapacity());
|
||||
assertEquals(Double.valueOf(10d), ((Truck) v).getCapacity());
|
||||
assertNotNull(t0.getId());
|
||||
} else {
|
||||
Assert.assertTrue("v not a Truck?", false);
|
||||
assertTrue("v not a Truck?", false);
|
||||
}
|
||||
|
||||
VehicleDriver driver = new VehicleDriver();
|
||||
@@ -42,17 +48,17 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
v = d1.getVehicle();
|
||||
if (v instanceof Truck) {
|
||||
Double capacity = ((Truck) v).getCapacity();
|
||||
Assert.assertEquals(Double.valueOf(10d), capacity);
|
||||
Assert.assertNotNull(v.getId());
|
||||
assertEquals(Double.valueOf(10d), capacity);
|
||||
assertNotNull(v.getId());
|
||||
} else {
|
||||
Assert.assertTrue("v not a Truck?", false);
|
||||
assertTrue("v not a Truck?", false);
|
||||
}
|
||||
|
||||
List<VehicleDriver> list = Ebean.find(VehicleDriver.class).findList();
|
||||
for (VehicleDriver vehicleDriver : list) {
|
||||
if (vehicleDriver.getVehicle() instanceof Truck) {
|
||||
Double capacity = ((Truck) vehicleDriver.getVehicle()).getCapacity();
|
||||
Assert.assertEquals(Double.valueOf(10d), capacity);
|
||||
assertEquals(Double.valueOf(10d), capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,12 +79,12 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
query.where().eq("vehicle.licenseNumber", "MARIOS_CAR_LICENSE");
|
||||
List<VehicleDriver> drivers = query.findList();
|
||||
|
||||
Assert.assertNotNull(drivers);
|
||||
Assert.assertEquals(1, drivers.size());
|
||||
Assert.assertNotNull(drivers.get(0));
|
||||
assertNotNull(drivers);
|
||||
assertEquals(1, drivers.size());
|
||||
assertNotNull(drivers.get(0));
|
||||
|
||||
Assert.assertEquals("Mario", drivers.get(0).getName());
|
||||
Assert.assertEquals("MARIOS_CAR_LICENSE", drivers.get(0).getVehicle().getLicenseNumber());
|
||||
assertEquals("Mario", drivers.get(0).getName());
|
||||
assertEquals("MARIOS_CAR_LICENSE", drivers.get(0).getVehicle().getLicenseNumber());
|
||||
|
||||
Vehicle car2 = Ebean.find(Vehicle.class, car.getId());
|
||||
|
||||
@@ -86,4 +92,34 @@ public class TestInheritInsert extends BaseTestCase {
|
||||
Ebean.save(car);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_AtOrderBy_on_ChildOfChild() {
|
||||
|
||||
Car car = new Car();
|
||||
car.setLicenseNumber("ABC");
|
||||
Ebean.save(car);
|
||||
|
||||
CarFuse fuse = new CarFuse();
|
||||
fuse.setLocationCode("xdfg");
|
||||
Ebean.save(fuse);
|
||||
|
||||
CarAccessory accessory = new CarAccessory(car, fuse);
|
||||
Ebean.save(accessory);
|
||||
|
||||
|
||||
Query<Car> query = Ebean.find(Car.class)
|
||||
.fetch("accessories")
|
||||
.where()
|
||||
.eq("id", car.getId())
|
||||
.query();
|
||||
|
||||
Car result = query.findUnique();
|
||||
|
||||
assertThat(query.getGeneratedSql()).contains("order by t0.id, t2.location_code");
|
||||
assertThat(query.getGeneratedSql()).contains("left outer join car_fuse t2 on t2.id = t1.fuse_id");
|
||||
|
||||
assertNotNull(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.avaje.tests.json.include;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.config.JsonConfig;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebean.text.json.JsonWriteOptions;
|
||||
import com.avaje.tests.json.transientproperties.EJsonTransientEntityList;
|
||||
import com.avaje.tests.json.transientproperties.EJsonTransientList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestJsonExcludeTransientEmptyList {
|
||||
@@ -45,4 +48,18 @@ public class TestJsonExcludeTransientEmptyList {
|
||||
|
||||
assertEquals(expectedJson, asJson);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToJson_with_transientExcludeFromPathProperties() throws Exception {
|
||||
|
||||
EJsonTransientEntityList bean = new EJsonTransientEntityList();
|
||||
bean.setId(99L);
|
||||
bean.setName("John");
|
||||
|
||||
PathProperties pathProps = PathProperties.parse("id,name");
|
||||
|
||||
String asJson = Ebean.json().toJson(bean, pathProps);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"id\":99,\"name\":\"John\"}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
import com.avaje.ebean.annotation.Sql;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.List;
|
||||
|
||||
@Sql
|
||||
@Entity
|
||||
public class EJsonTransientEntityList {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@Transient
|
||||
private Boolean basic;
|
||||
|
||||
@Transient
|
||||
private List<Order> orders;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean getBasic() {
|
||||
return basic;
|
||||
}
|
||||
|
||||
public void setBasic(Boolean basic) {
|
||||
this.basic = basic;
|
||||
}
|
||||
|
||||
public List<Order> getOrders() {
|
||||
return orders;
|
||||
}
|
||||
|
||||
public void setOrders(List<Order> orders) {
|
||||
this.orders = orders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
import com.avaje.ebean.annotation.Sql;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Transient;
|
||||
import java.util.List;
|
||||
|
||||
@Sql
|
||||
@Entity
|
||||
public class ModelA {
|
||||
|
||||
@Id
|
||||
int id;
|
||||
|
||||
String a;
|
||||
|
||||
// transient mapping to an entity bean
|
||||
@Transient
|
||||
List<ModelB> list;
|
||||
|
||||
public String getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
public void setA(String a) {
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public List<ModelB> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
public void setList(List<ModelB> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
|
||||
import com.avaje.ebean.annotation.Sql;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Sql
|
||||
@Entity
|
||||
public class ModelB {
|
||||
|
||||
Integer oneField;
|
||||
|
||||
Integer twoField;
|
||||
|
||||
public Integer getOneField() {
|
||||
return oneField;
|
||||
}
|
||||
|
||||
public void setOneField(Integer oneField) {
|
||||
this.oneField = oneField;
|
||||
}
|
||||
|
||||
public Integer getTwoField() {
|
||||
return twoField;
|
||||
}
|
||||
|
||||
public void setTwoField(Integer twoField) {
|
||||
this.twoField = twoField;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.avaje.tests.json.transientproperties;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
|
||||
public class TestModelAJson {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ModelA a = new ModelA();
|
||||
a.setId(1);
|
||||
a.setA("a");
|
||||
|
||||
ModelB b = new ModelB();
|
||||
b.setOneField(1);
|
||||
b.setTwoField(1);
|
||||
|
||||
a.setList(new ArrayList<ModelB>());
|
||||
a.getList().add(b);
|
||||
|
||||
PathProperties pathProperties = PathProperties.parse("(a,list(oneField))");
|
||||
|
||||
String json = Ebean.json().toJson(a, pathProperties);
|
||||
|
||||
assertThat(json).isEqualTo("{\"a\":\"a\",\"list\":[{\"oneField\":1}]}");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,51 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OrderBy;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Inheritance
|
||||
@DiscriminatorValue("C")
|
||||
public class Car extends Vehicle {
|
||||
|
||||
private static final long serialVersionUID = 4716705779684333446L;
|
||||
private static final long serialVersionUID = 4716705779684333446L;
|
||||
|
||||
private String driver;
|
||||
private String driver;
|
||||
|
||||
|
||||
@ManyToOne
|
||||
TruckRef carRef;
|
||||
|
||||
@OneToMany(mappedBy="car")
|
||||
private Set<CarAccessory> accessories = new HashSet<CarAccessory>();
|
||||
|
||||
public String getDriver() {
|
||||
return driver;
|
||||
}
|
||||
@ManyToOne
|
||||
TruckRef carRef;
|
||||
|
||||
public void setDriver(String driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
@OneToMany(mappedBy = "car")
|
||||
@OrderBy("fuse.locationCode")
|
||||
private Set<CarAccessory> accessories = new HashSet<CarAccessory>();
|
||||
|
||||
public TruckRef getCarRef() {
|
||||
return carRef;
|
||||
}
|
||||
public String getDriver() {
|
||||
return driver;
|
||||
}
|
||||
|
||||
public void setCarRef(TruckRef carRef) {
|
||||
this.carRef = carRef;
|
||||
}
|
||||
public void setDriver(String driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
public Set<CarAccessory> getAccessories() {
|
||||
return accessories;
|
||||
}
|
||||
public TruckRef getCarRef() {
|
||||
return carRef;
|
||||
}
|
||||
|
||||
public void setAccessories(Set<CarAccessory> accessories) {
|
||||
this.accessories = accessories;
|
||||
}
|
||||
public void setCarRef(TruckRef carRef) {
|
||||
this.carRef = carRef;
|
||||
}
|
||||
|
||||
public Set<CarAccessory> getAccessories() {
|
||||
return accessories;
|
||||
}
|
||||
|
||||
public void setAccessories(Set<CarAccessory> accessories) {
|
||||
this.accessories = accessories;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user