Compare commits

..
Author SHA1 Message Date
Robin Bygrave 3ef77ea410 [maven-release-plugin] prepare release avaje-ebeanorm-6.17.3 2016-02-15 23:49:08 +13:00
Robin Bygrave 0d456b2ad6 Update TestOrderByFetch for running in Postgres ... by adding a 'name' property to the beans so that there is at least one property in the inserts 2016-02-13 13:09:41 +13:00
Robin Bygrave 39e913cbe2 #565 - Query plan hash collision on firstRow/maxRows combinations - change prime 2016-02-13 12:30:31 +13:00
Robin Bygrave ae38672e02 #565 - Query plan hash collision on firstRow/maxRows combinations - tests 2016-02-13 12:30:00 +13:00
Robin Bygrave 8a8b36ec1e #462 - When fetching, collections can get populated with too many entities - Fix 2016-02-13 00:43:47 +13:00
Robin Bygrave 62aca1e77b #462 - When fetching, collections can get populated with too many entities - Fix 2016-02-12 23:33:17 +13:00
Robin Bygrave f0b80068b6 #462 - No effective change, reformat code ... When fetching, collections can get populated with too many entities - 2016-02-12 23:32:43 +13:00
Rob Bygrave 5721fe3807 Merge pull request #560 from nedap/master
Add test for broken fetching issue #462
2016-02-12 21:58:35 +13:00
Robin Bygrave afffb88a29 [maven-release-plugin] prepare for next development iteration 2016-02-12 21:51:59 +13:00
Robin Bygrave 4c3a125192 [maven-release-plugin] prepare release avaje-ebeanorm-6.17.2 2016-02-12 21:51:37 +13:00
Robin Bygrave f8804f8d18 #564 - DDL DB Migration - migration generation on history table, pending drop columns missing in the history view and tigger 2016-02-12 21:28:19 +13:00
Robin Bygrave 4848c24a0a #563 - ENH: DDL DB Migration - add a suppressDropsForever="true" attribute such that drops we never want to run are not warned on and can't be selected for a migration. 2016-02-12 14:33:19 +13:00
Robin Bygrave 4c3d1d9edd #561 - DDL DB Migration - change "ddl.migration.pendingDrop" to "ddl.migration.pendingDropsFor" 2016-02-12 11:04:25 +13:00
Pieter Bos 42b112c3d7 Add test for broken fetching issue #462 2016-02-11 17:00:39 +01:00
Robin Bygrave 59996da07a [maven-release-plugin] prepare for next development iteration 2016-02-11 22:43:53 +13:00
Robin Bygrave 32d4b83a86 [maven-release-plugin] prepare release avaje-ebeanorm-6.17.1 2016-02-11 22:43:22 +13:00
Robin Bygrave ca824c5e0d Refactor / fix .gitignore of test resources 2016-02-11 22:33:25 +13:00
Robin Bygrave b2f7664316 Bump pom to 6.17.1-SNAPSHOT 2016-02-11 22:20:17 +13:00
Robin Bygrave 062f7a281f #559 - DDL - DB Migration refactor - remove rollback.ddl generation (ultimately not used by FlywayDb etc) 2016-02-11 22:17:35 +13:00
Robin Bygrave 019ce4e614 #558 - Internal refactor - MTable pending dropped columns 2016-02-11 20:43:45 +13:00
Robin Bygrave 6b490ed8b1 #557 - DDL - DB Migration refactor - remove drop.ddl and use pendingDrops. 2016-02-11 20:26:53 +13:00
Robin Bygrave 1bee6fcae5 #557 - DDL - DB Migration refactor - remove drop.ddl and use pendingDrops. 2016-02-11 20:15:49 +13:00
Robin Bygrave 097552b8d3 [maven-release-plugin] prepare for next development iteration 2016-02-10 20:53:52 +13:00
Robin Bygrave 9fbfca5f73 [maven-release-plugin] prepare release avaje-ebeanorm-6.16.5 2016-02-10 20:53:29 +13:00
Robin Bygrave 9e70ba915f #556 - DDL - DB Migration for History - change treatment of dropped columns such that they are still included in apply script 2016-02-10 16:05:59 +13:00
Robin Bygrave a1a5dcd035 #556 - DDL - DB Migration for History - change treatment of dropped columns such that they are still included in apply script 2016-02-10 16:04:46 +13:00
Robin Bygrave d2b048b0b4 No effective change - reformat code 2016-02-09 14:51:26 +13:00
Robin Bygrave 4272c17689 #555 - Use a common timestamp value for @WhenCreated and @WhenModified 2016-02-09 14:48:41 +13:00
Robin Bygrave 11723e3a32 #554 - DDL - DB Migration with @History and Postgres - views need to be dropped early to support migration changes 2016-02-05 11:54:52 +13:00
Robin Bygrave 237cdcdd3d #554 - DDL - DB Migration with @History and Postgres - views need to be dropped early to support migration changes 2016-02-05 11:52:06 +13:00
Robin Bygrave e96bee74ae [maven-release-plugin] prepare for next development iteration 2016-02-05 00:15:30 +13:00
Robin Bygrave c8b57c3302 [maven-release-plugin] prepare release avaje-ebeanorm-6.16.4 2016-02-05 00:15:04 +13:00
Robin Bygrave f65ea96b89 #553 - DDL - DB Migration with @History and Postgres needs to re-create the "with_history" view when columns dropped 2016-02-05 00:11:25 +13:00
Robin Bygrave a1bd0b469a #551 - DDL - DB Migration with @History and @Draftable - @DraftOnly columns added when not part of change 2016-02-04 22:31:07 +13:00
Robin Bygrave fd5563e3da No effective change - javadoc update for RawSql tableAliasMapping 2016-02-02 15:35:31 +13:00
Robin Bygrave f9156ede55 No effective change - add tests for RawSql tableAlias mapping of root type to null path and not mapping it 2016-02-02 14:58:59 +13:00
Robin Bygrave 10d2d591db [maven-release-plugin] prepare for next development iteration 2016-02-02 13:38:09 +13:00
Robin Bygrave 998d8ed6f2 [maven-release-plugin] prepare release avaje-ebeanorm-6.16.3 2016-02-02 13:37:46 +13:00
Robin Bygrave a8da472872 #550 - ENH: Add generated file warning to DB migration XML files 2016-02-02 13:33:46 +13:00
Robin Bygrave a9cdf9736a Bump maven plugin version to 4.9.1 2016-02-02 13:23:46 +13:00
Robin Bygrave c930a1dd6b #549 - Regression: ArrayIndexOutOfBoundsException when lazy loading on bean with inheritance - tidy up 2016-02-02 11:29:38 +13:00
Robin Bygrave 67ce6ef58e #549 - Regression: ArrayIndexOutOfBoundsException when lazy loading on bean with inheritance 2016-02-02 11:24:46 +13:00
Robin Bygrave c4f28efc45 Testing CI - .travis - git depth + skip mvn install 2016-01-28 10:54:16 +13:00
Robin Bygrave afa4bc06d9 Testing CI - .travis - try setup_database 2016-01-28 10:44:14 +13:00
Robin Bygrave 828c9ce128 Testing CI - .travis - try sudo required 2016-01-28 09:31:18 +13:00
Robin Bygrave 6f37a0bd72 Testing CI - .travis - print surefire reports + setup_database script 2016-01-28 09:20:27 +13:00
Robin Bygrave 036086af02 Testing CI - .travis - print surefire reports on failure 2016-01-28 09:00:44 +13:00
Robin Bygrave f34b5e52e0 [maven-release-plugin] prepare for next development iteration 2016-01-28 08:36:40 +13:00
Robin Bygrave a70e289b00 [maven-release-plugin] prepare release avaje-ebeanorm-6.16.2 2016-01-28 08:36:18 +13:00
Robin Bygrave 4ad0e06aab Testing CI - TestEncrypt change to only run against H2 for now 2016-01-27 22:53:06 +13:00
Robin Bygrave 86de7e845e Testing CI - add sql logging for TestEncrypt run on CI server 2016-01-27 22:39:03 +13:00
Robin Bygrave 38501ca372 Modify .travis.yml - add postgres pgcrypto extension 2016-01-27 22:15:39 +13:00
Robin Bygrave 4790c83fa1 Modify .travis.yml - cache .m2 directory 2016-01-27 21:56:00 +13:00
Robin Bygrave b410c83492 Modify .travis.yml for postgres - add hstore and postgis extensions 2016-01-27 21:50:43 +13:00
Robin Bygrave b19145f16f Modify .travis.yml for postgres - add hstore and postgis extensions 2016-01-27 21:46:30 +13:00
Robin Bygrave c28cff2f40 Modify .travis.yml for h2 and postgres runs using EBEAN_DB environment variable 2016-01-27 21:37:57 +13:00
Robin Bygrave 0c8cf78ecc Modify .travis.yml for h2 and postgres runs using EBEAN_DB environment variable 2016-01-27 21:21:01 +13:00
Robin Bygrave df112815ff #545 - ENH: Read environment variable "EBEAN_DB" to set default datasource - typically for CI builds 2016-01-27 21:20:13 +13:00
Robin Bygrave 85522ed8bc Modify .travis.yml for h2 and postgres runs 2016-01-27 20:39:53 +13:00
Robin Bygrave cf88f2375b #544 - Postgres @History DB trigger using current_timestamp (transaction) and should instead use statement_timestamp() 2016-01-27 20:34:51 +13:00
Robin Bygrave 24151f11fd #543 - Tidy up test logging - adjust test LoggedSqlCollector - remove logging noise when running tests 2016-01-27 20:12:36 +13:00
Robin Bygrave 424a83fa0a #543 - Tidy up test logging - adjust test LoggedSqlCollector - remove logging noise when running tests 2016-01-27 19:10:52 +13:00
Robin Bygrave 53dd91416c #543 - Tidy up test logging - adjust test LoggedSqlCollector - remove logging noise when running tests 2016-01-27 19:05:19 +13:00
Robin Bygrave 5a463c5071 #543 - Tidy up test logging - adjust test LoggedSqlCollector - remove trace level logging when running tests 2016-01-27 18:25:54 +13:00
Robin Bygrave bc3cf1348a #543 - Tidy up test logging - adjust test LoggedSqlCollector 2016-01-27 18:25:16 +13:00
Robin Bygrave f3b1024283 Fix for test asserts to run against Postgres with escape clause on LIKE expression 2016-01-27 17:22:55 +13:00
Robin Bygrave 7ba35d7a62 Update .travis - postgres service 2016-01-27 16:17:45 +13:00
Robin Bygrave 990ca2980b Add initial .travis.yml 2016-01-27 16:05:33 +13:00
Robin Bygrave 38dd3d82fc #536 - @ManyToOne with @JoinColumn throw error - Update tests 2016-01-26 16:41:15 +13:00
Robin Bygrave 6be26aaff2 #537 - Change PersistenceException: Mapping limitation on @JoinColumn such that it includes the bean type 2016-01-26 15:30:57 +13:00
Robin Bygrave d1462b032a #535 - Bean lazy loading can execute an extra query when one of the lazy loaded beans has been deleted 2016-01-21 20:51:45 +13:00
Robin Bygrave 6ab974a330 #535 - Bean lazy loading can execute an extra query when one of the lazy loaded beans has been deleted 2016-01-21 20:51:32 +13:00
Robin Bygrave 64618da6a7 #534 - REFACTOR: Internals for lazy loading specifically improving errors/logging in cases of lazy load errors due to deletes - Update debug logging for LoadManyRequest when empty collection found (expected frequently) 2016-01-21 17:52:23 +13:00
Robin Bygrave d242ff7351 #534 - REFACTOR: Internals for lazy loading specifically improving errors/logging in cases of lazy load errors due to deletes - Initial refactor for LoadManyRequest 2016-01-21 17:30:49 +13:00
Robin Bygrave 7d54aaa9a7 #534 - REFACTOR: Internals for lazy loading specifically improving errors/logging in cases of lazy load errors due to deletes - Initial refactor 2016-01-21 16:50:51 +13:00
Robin Bygrave 0e46184656 No effective change - Rename test class 2016-01-21 16:03:51 +13:00
Robin Bygrave c9e1a6edbd Bump agent to 4.9.1 2016-01-20 15:13:11 +13:00
Robin Bygrave 3c0cffd146 [maven-release-plugin] prepare for next development iteration 2016-01-19 19:49:31 +13:00
Robin Bygrave d31547fb69 [maven-release-plugin] prepare release avaje-ebeanorm-6.16.1 2016-01-19 19:49:08 +13:00
Robin Bygrave 93adaa12bc Bump pom to 6.16.1-SNAPSHOT 2016-01-19 19:48:22 +13:00
Robin Bygrave d1cdbe94de #529 - @OneToOne with @JoinColumn to non primary key throws Data conversion error 2016-01-19 19:46:41 +13:00
Robin Bygrave e7c6223799 #530 - Bad join with raw() expression on a @ManyToMany path. 2016-01-19 13:18:28 +13:00
Robin Bygrave d33afd85de #533 - DDL - Invalid foreign key constraint name for @ManyToMany when using explicit schema in @Table - Temp testing removed 2016-01-19 10:01:04 +13:00
Robin Bygrave 77bcb4aef9 #533 - DDL - Invalid foreign key constraint name for @ManyToMany when using explicit schema in @Table - Temp testing 2016-01-19 09:57:49 +13:00
Robin Bygrave a70fe184c2 #533 - DDL - Invalid foreign key constraint name for @ManyToMany when using explicit schema in @Table - Fix 2016-01-19 09:56:27 +13:00
Robin Bygrave 7238f39be5 #531 - AutoTune - On shutdown provide ability to saving profiling without garbage collection 2016-01-19 08:16:17 +13:00
Robin Bygrave ae44b59c5f #532 - Change example expression such that it supports nested beans 2016-01-18 23:52:28 +13:00
Robin Bygrave ced4e71307 [maven-release-plugin] prepare for next development iteration 2016-01-13 16:13:59 +13:00
Robin Bygrave 9cb43bd5dd [maven-release-plugin] prepare release avaje-ebeanorm-6.15.2 2016-01-13 16:13:30 +13:00
Robin Bygrave 9665de2dbe #502 - jsonContext.toJson(o, generator, path) can not apply path on @Transient field that is an entity bean 2016-01-13 13:29:06 +13:00
Robin Bygrave e2666f0c4c No effective change - turn off autotune profiling/tuning flags for test running 2016-01-13 12:38:04 +13:00
Robin Bygrave 89c83c90d1 No effective change - add test to show findRowCount with joins 2016-01-13 12:35:52 +13:00
Robin Bygrave cdd25351a6 No effective change - test code static imports 2016-01-13 11:40:23 +13:00
Robin Bygrave 17e0413837 No effective change - code format 2016-01-13 11:38:53 +13:00
Robin Bygrave b04419d9c7 #527 - @OrderBy on child-of-child property 2016-01-13 11:37:38 +13:00
Robin Bygrave 0453892ebd #526 - findPagedList with @EmbeddedId broken - automatically adds orderby t0.null 2016-01-13 10:43:56 +13:00
Robin Bygrave 55493c5b7d #524 - ExplicitJdbcTransaction leaves dangling “begin” - comment update 2016-01-13 00:17:22 +13:00
Robin Bygrave eb2f7cd852 #525 - Automatic autotune data 2016-01-13 00:08:00 +13:00
Robin Bygrave f44eb234c8 #525 - Automatic autotune data - initial wip 2016-01-12 11:45:24 +13:00
Robin Bygrave 404b57ab21 #524 - ExplicitJdbcTransaction leaves dangling “begin” - expected fix 2016-01-12 08:37:12 +13:00
Robin Bygrave a54f09ca4b #524 - ExplicitJdbcTransaction leaves dangling “begin” - expected fix 2016-01-11 14:04:14 +13:00
Robin Bygrave a3bb909308 [maven-release-plugin] prepare for next development iteration 2016-01-08 00:22:06 +13:00
Robin Bygrave 7eb443859e [maven-release-plugin] prepare release avaje-ebeanorm-6.15.1 2016-01-08 00:21:44 +13:00
Robin Bygrave 8b39788ae3 Bump pom to 6.15.1-SNAPSHOT 2016-01-08 00:20:27 +13:00
Robin Bygrave 1ec132b7dd #508 - Upgrading to 6.13.5 causes "No suitable driver found for jdbc:mysql" error 2016-01-08 00:18:20 +13:00
Robin Bygrave 343a975073 #520 - SPI - Deprecate SpiEbeanPlugin in favor of SpiServerPlugin. - Deprecate SpiEbeanPlugin 2016-01-07 23:24:57 +13:00
Robin Bygrave 944f7bd086 #520 - SPI - Deprecate SpiEbeanPlugin in favor of SpiServerPlugin. - Add ServiceLoading for SpiServerPlugin 2016-01-07 16:45:24 +13:00
Robin Bygrave 3639c2a540 #518 - Refactor: Remove getDdlGenerator() from SpiEbeanServer. 2016-01-07 14:27:27 +13:00
Robin Bygrave a9cc8ed702 #517 - Refactor DdlGenerator such that it is not an SpiEbeanPlugin 2016-01-07 14:22:37 +13:00
Robin Bygrave 0351a15067 #516 - DdlParser for seed and init sql is not ignoring sql comment lines (starting with --) 2016-01-07 14:03:17 +13:00
Robin Bygrave c7300a1707 #246 - ENH: Add ebean.ddl.initsql=.... to support executing a sql script prior to running the create all DDL - was - When the scripts generates the evolution, it should generate the create schema too. 2016-01-07 13:12:43 +13:00
Robin Bygrave c48008c4b2 #515 - ENH: Add ebean.ddl.seedSql=xxx.sql ... such that a sql script will execute to insert seed data typically for testing 2016-01-07 12:48:55 +13:00
Robin Bygrave e5d8804591 #514 - ENH: Add ebean.ddl.createOnly=true ... such that all the drop table statements are skipped (for running tests against H2 usually) 2016-01-07 10:06:35 +13:00
Robin Bygrave f6dc294bc2 No effective change - update public project url in pom 2016-01-05 21:38:25 +13:00
Robin Bygrave 692baf8bb6 [maven-release-plugin] prepare for next development iteration 2016-01-05 21:28:03 +13:00
248 changed files with 5432 additions and 2462 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
*.autofetch
*.sql
*create-all.sql
*drop-all.sql
*.orig
.classpath
.project
+32
View File
@@ -0,0 +1,32 @@
sudo: required
language: java
jdk:
- oraclejdk8
git:
depth: 3
addons:
postgresql: "9.4"
services:
- postgresql
before_script:
- ./.travis/setup_database
env:
- EBEAN_DB=h2
- EBEAN_DB=pg
install: true
script:
- mvn clean test
after_failure:
- ./.travis/print_surefire_reports
cache:
directories:
- $HOME/.m2
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
echo "\n=== SUREFIRE REPORTS ===\n"
for file in target/surefire-reports/*.txt
do
echo ${file}
cat ${file}
echo
done
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
echo "\n=== SETUP DATABASE ===\n"
#set -e
#set -x
# Setup postgres' users and databases
sudo -u postgres psql -c "CREATE USER unit WITH PASSWORD 'unit';"
sudo -u postgres psql -c 'CREATE DATABASE unit WITH OWNER unit;'
sudo -u postgres psql unit -c 'CREATE EXTENSION hstore;'
sudo -u postgres psql unit -c 'CREATE EXTENSION pgcrypto;'
#sudo -u postgres psql test2 -c 'CREATE EXTENSION postgis;'
+4 -4
View File
@@ -9,11 +9,11 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.14.1</version>
<version>6.17.3</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.8.1</version>
<version>4.9.1</version>
<scope>test</scope>
</dependency>
@@ -219,7 +219,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.8.1</version>
<version>4.9.1</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -11,4 +11,14 @@ public interface AutoTune {
*/
void collectProfiling();
/**
* Output the profiling.
* <p>
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
* to the existing tuning.
* </p>
*/
void reportProfiling();
}
@@ -58,15 +58,18 @@ class PrimaryServer {
*/
private static String determineDefaultServerName() {
String defaultServerName = System.getProperty("datasource.default");
String defaultServerName = System.getenv("EBEAN_DB");
if (isEmpty(defaultServerName)) {
defaultServerName = System.getProperty("ebean.default.datasource");
}
if (isEmpty(defaultServerName)) {
defaultServerName = globalProperties.getProperty("datasource.default");
}
if (isEmpty(defaultServerName)) {
defaultServerName = globalProperties.getProperty("ebean.default.datasource");
defaultServerName = System.getProperty("datasource.default");
if (isEmpty(defaultServerName)) {
defaultServerName = System.getProperty("ebean.default.datasource");
if (isEmpty(defaultServerName)) {
defaultServerName = globalProperties.getProperty("datasource.default");
if (isEmpty(defaultServerName)) {
defaultServerName = globalProperties.getProperty("ebean.default.datasource");
}
}
}
}
return defaultServerName;
}
@@ -585,6 +585,9 @@ public final class RawSql implements Serializable {
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
* <p>
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
public void tableAliasMapping(String tableAlias, String path) {
@@ -106,6 +106,9 @@ public class RawSqlBuilder {
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
* <p>
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
columnMapping.tableAliasMapping(tableAlias, path);
@@ -125,6 +125,11 @@ public interface BeanCollection<E> extends Serializable {
*/
void internalAdd(Object bean);
/**
* Add the bean with a check to see if it is already contained.
*/
void internalAddWithCheck(Object bean);
/**
* Return the number of elements in the List Set or Map.
*/
@@ -1,5 +1,10 @@
package com.avaje.ebean.bean;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ValuePair;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
@@ -11,12 +16,6 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.ValuePair;
/**
* This is the object added to every entity bean using byte code enhancement.
* <p>
@@ -92,6 +91,8 @@ public final class EntityBeanIntercept implements Serializable {
private int lazyLoadProperty = -1;
private Object ownerId;
/**
* Create a intercept with a given entity.
* <p>
@@ -342,18 +343,11 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Check if the lazy load succeeded. If not then mark this bean as having
* failed lazy loading due to the underlying row being deleted.
* <p>
* We mark the bean this way rather than immediately fail as we might be batch
* lazy loading and this bean might not be used by the client code at all.
* Instead we will fail as soon as the client code tries to use this bean.
* </p>
* Set lazy load failure flag.
*/
public void checkLazyLoadFailure() {
if (lazyLoadProperty != -1) {
this.lazyLoadFailure = true;
}
public void setLazyLoadFailure(Object ownerId) {
this.lazyLoadFailure = true;
this.ownerId = ownerId;
}
/**
@@ -748,7 +742,7 @@ public final class EntityBeanIntercept implements Serializable {
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("Bean has been deleted - lazy loading failed");
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
}
if (lazyLoadProperty == -1) {
@@ -763,7 +757,7 @@ public final class EntityBeanIntercept implements Serializable {
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Bean has been deleted - lazy loading failed");
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
}
// bean should be loaded and intercepting now. setLoaded() has
@@ -1,5 +1,9 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@@ -8,17 +12,13 @@ import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
/**
* List capable of lazy loading.
*/
public final class BeanList<E> extends AbstractBeanCollection<E> implements List<E>, BeanCollectionAdd {
private static final long serialVersionUID = 1L;
/**
* The underlying List implementation.
*/
@@ -74,6 +74,13 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
@Override
public void internalAddWithCheck(Object bean) {
if (list == null || !list.contains(bean)) {
internalAdd(bean);
}
}
public boolean checkEmptyLazyLoad() {
if (list == null) {
list = new ArrayList<E>();
@@ -99,11 +106,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
private void initAsUntouched() {
init(false);
}
private void init() {
init(true);
}
private void init(boolean setTouched) {
synchronized (this) {
if (list == null) {
@@ -134,7 +141,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
public Collection<E> getActualDetails() {
return list;
}
@Override
public Collection<?> getActualEntries() {
return list;
@@ -66,7 +66,18 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
map.put((K) key, (E) bean);
}
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@Override
public void internalAddWithCheck(Object bean) {
throw new RuntimeException("Not allowed for map");
}
public void internalAdd(Object bean) {
throw new RuntimeException("Not allowed for map");
}
@@ -1,15 +1,15 @@
package com.avaje.ebean.common;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.EntityBean;
/**
* Set capable of lazy loading.
*/
@@ -57,6 +57,13 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
set.add((E) bean);
}
@Override
public void internalAddWithCheck(Object bean) {
if (set == null || !set.contains(bean)) {
internalAdd(bean);
}
}
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (set == null) {
@@ -107,11 +114,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
private void initAsUntouched() {
init(false);
}
private void init() {
init(true);
}
private void init(boolean setTouched) {
synchronized (this) {
if (set == null) {
@@ -21,9 +21,13 @@ public class AutoTuneConfig {
private double profilingRate = 0.01;
private int profilingUpdateFrequency;
private int garbageCollectionWait = 100;
private boolean skipCollectionOnShutdown;
private boolean skipGarbageCollectionOnShutdown;
private boolean skipProfileReportingOnShutdown;
public AutoTuneConfig() {
}
@@ -56,6 +60,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.
@@ -184,17 +202,33 @@ public class AutoTuneConfig {
}
/**
* Return true if profiling collection should be skipped on shutdown.
* Return true if triggering garbage collection should be skipped on shutdown.
* You might set this when System.GC() slows a application shutdown too much.
*/
public boolean isSkipCollectionOnShutdown() {
return skipCollectionOnShutdown;
public boolean isSkipGarbageCollectionOnShutdown() {
return skipGarbageCollectionOnShutdown;
}
/**
* Set to true if profiling collection should be skipped on shutdown.
* Set to true if triggering garbage collection should be skipped on shutdown.
* You might set this when System.GC() slows a application shutdown too much.
*/
public void setSkipCollectionOnShutdown(boolean skipCollectionOnShutdown) {
this.skipCollectionOnShutdown = skipCollectionOnShutdown;
public void setSkipGarbageCollectionOnShutdown(boolean skipGarbageCollectionOnShutdown) {
this.skipGarbageCollectionOnShutdown = skipGarbageCollectionOnShutdown;
}
/**
* Return true if profile reporting should be skipped on shutdown.
*/
public boolean isSkipProfileReportingOnShutdown() {
return skipProfileReportingOnShutdown;
}
/**
* Set to true if profile reporting should be skipped on shutdown.
*/
public void setSkipProfileReportingOnShutdown(boolean skipProfileReportingOnShutdown) {
this.skipProfileReportingOnShutdown = skipProfileReportingOnShutdown;
}
/**
@@ -206,13 +240,15 @@ public class AutoTuneConfig {
queryTuningAddVersion = p.getBoolean("autoTune.queryTuningAddVersion", queryTuningAddVersion);
queryTuningFile = p.get("autoTune.queryTuningFile", queryTuningFile);
skipCollectionOnShutdown = p.getBoolean("autoTune.skipCollectionOnShutdown", skipCollectionOnShutdown);
skipGarbageCollectionOnShutdown = p.getBoolean("autoTune.skipGarbageCollectionOnShutdown", skipGarbageCollectionOnShutdown);
skipProfileReportingOnShutdown = p.getBoolean("autoTune.skipProfileReportingOnShutdown", skipProfileReportingOnShutdown);
mode = p.getEnum(AutoTuneMode.class, "autoTune.mode", mode);
profiling = p.getBoolean("autoTune.profiling", profiling);
profilingBase = p.getInt("autoTune.profilingBase", profilingBase);
profilingRate = p.getDouble("autoTune.profilingRate", profilingRate);
profilingFile = p.get("autoTune.profilingFile", profilingFile);
profilingUpdateFrequency = p.getInt("autoTune.profilingUpdateFrequency", profilingUpdateFrequency);
}
}
@@ -23,11 +23,6 @@ public class DbMigrationConfig {
*/
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>
@@ -61,33 +56,17 @@ public class DbMigrationConfig {
*/
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";
protected boolean includeGeneratedFileComment;
/**
* The version of a pending drop that should be generated as the next migration.
*/
protected String generatePendingDrop;
/**
* Return the DB platform to generate migration DDL for.
*
@@ -138,34 +117,6 @@ public class DbMigrationConfig {
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)
*/
@@ -180,20 +131,6 @@ public class DbMigrationConfig {
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).
*/
@@ -209,31 +146,31 @@ public class DbMigrationConfig {
}
/**
* Return the drop script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
* Return true if the generated file comment should be included.
*/
public String getDropSuffix() {
return dropSuffix;
public boolean isIncludeGeneratedFileComment() {
return includeGeneratedFileComment;
}
/**
* Set the drop script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
* Set to true if the generated file comment should be included.
*/
public void setDropSuffix(String dropSuffix) {
this.dropSuffix = dropSuffix;
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
/**
* Return the rollback script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
* Return the migration version (or "next") to generate pending drops for.
*/
public String getRollbackSuffix() {
return rollbackSuffix;
public String getGeneratePendingDrop() {
return generatePendingDrop;
}
/**
* Set the rollback script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
* Set the migration version (or "next") to generate pending drops for.
*/
public void setRollbackSuffix(String rollbackSuffix) {
this.rollbackSuffix = rollbackSuffix;
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
/**
@@ -259,8 +196,6 @@ public class DbMigrationConfig {
* into a single directory.
*/
public void singleDirectory() {
this.dropPath = "";
this.rollbackPath = "";
this.modelPath = "";
}
@@ -274,16 +209,13 @@ public class DbMigrationConfig {
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);
includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop);
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);
@@ -307,7 +239,6 @@ public class DbMigrationConfig {
return generate;
}
/**
* Called by EbeanServer on start.
*
@@ -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);
}
@@ -20,6 +20,7 @@ import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlWriter;
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.MigrationVersion;
import com.avaje.ebean.dbmigration.model.ModelContainer;
import com.avaje.ebean.dbmigration.model.ModelDiff;
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
@@ -59,6 +60,8 @@ public class DbMigration {
private static final String initialVersion = "1.0";
private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY";
/**
* Set to true if DbMigration run with online EbeanServer instance.
*/
@@ -199,46 +202,15 @@ public class DbMigration {
if (!online) {
DbOffline.setRunningMigration();
}
setDefaults();
try {
Request request = createRequest();
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();
ModelDiff diff = new ModelDiff(migrated);
diff.compareTo(current);
if (diff.isEmpty()) {
logger.info("no changes detected - no migration written");
return;
}
// there were actually changes to write
Migration dbMigration = diff.getMigration();
String fullVersion = getFullVersion(migrationModel);
logger.info("generating migration:{}", fullVersion);
if (!writeMigrationXml(dbMigration, modelDir, fullVersion)) {
logger.warn("migration already exists, not generating DDL");
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
} 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);
generateDiff(request);
}
} finally {
@@ -249,9 +221,128 @@ public class DbMigration {
}
/**
* Return the full version for the migration being generated.
* Generate the diff migration.
*/
private String getFullVersion(MigrationModel migrationModel) {
private void generateDiff(Request request) throws IOException {
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("Pending un-applied drops in versions {}", pendingDrops);
}
Migration migration = request.createDiffMigration();
if (migration == null) {
logger.info("no changes detected - no migration written");
} else {
// there were actually changes to write
generateMigration(request, migration, null);
}
}
/**
* Generate the migration based on the pendingDrops from a prior version.
*/
private void generatePendingDrop(Request request, String pendingVersion) throws IOException {
Migration migration = request.migrationForPendingDrop(pendingVersion);
generateMigration(request, migration, pendingVersion);
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
}
}
private Request createRequest() {
return new Request();
}
private class Request {
final File migrationDir;
final File modelDir;
final MigrationModel migrationModel;
final CurrentModel currentModel;
final ModelContainer migrated;
final ModelContainer current;
private Request() {
this.migrationDir = getMigrationDirectory();
this.modelDir = getModelDirectory(migrationDir);
this.migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
this.migrated = migrationModel.read();
this.currentModel = new CurrentModel(server, constraintNaming);
this.current = currentModel.read();
}
/**
* Return the migration for the pending drops for a given version.
*/
public Migration migrationForPendingDrop(String pendingVersion) {
Migration migration = migrated.migrationForPendingDrop(pendingVersion);
// register any remaining pending drops
migrated.registerPendingHistoryDropColumns(current);
return migration;
}
/**
* Return the list of versions that have pending un-applied drops.
*/
public List<String> getPendingDrops() {
return migrated.getPendingDrops();
}
/**
* Create and return the diff of the current model to the migration model.
*/
public Migration createDiffMigration() {
ModelDiff diff = new ModelDiff(migrated);
diff.compareTo(current);
return diff.isEmpty() ? null : diff.getMigration();
}
}
private void generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
logger.info("generating migration:{}", fullVersion);
if (!writeMigrationXml(dbMigration, request.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(), request.current);
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
writer.processMigration(dbMigration, write, request.migrationDir , fullVersion);
}
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
}
}
/**
* Return true if the next pending drop changeSet should be generated as the next migration.
*/
private String generatePendingDrop() {
String nextDrop = System.getProperty("ddl.migration.pendingDropsFor");
if (nextDrop != null) {
return nextDrop;
}
return migrationConfig.getGeneratePendingDrop();
}
/**
* Return the full version for the migration being generated.
*
* The full version can contain a comment suffix after a "__" double underscore.
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
String version = migrationConfig.getVersion();
if (version == null) {
@@ -259,10 +350,14 @@ public class DbMigration {
}
String fullVersion = version;
if (migrationConfig.getName() != null) {
fullVersion += "__" + toUnderScore(migrationConfig.getName());
String name = migrationConfig.getName();
if (name != null) {
fullVersion += "__" + toUnderScore(name);
} else if (dropsFor != null) {
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
} else if (version.equals(initialVersion)) {
fullVersion += "__initial";
}
return fullVersion;
}
@@ -304,7 +399,8 @@ public class DbMigration {
if (file.exists()) {
return false;
}
MigrationXmlWriter xmlWriter = new MigrationXmlWriter();
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
xmlWriter.write(dbMigration, file);
return true;
}
@@ -1,47 +1,42 @@
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;
private String dropAllContent;
private String createAllContent;
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 +53,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 +65,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,11 +81,57 @@ public class DdlGenerator implements SpiEbeanPlugin {
}
}
protected void runDropSql() throws IOException {
if (!createOnly) {
if (dropAllContent == null) {
dropAllContent = readFile(getDropFileName());
}
runScript(true, dropAllContent, getDropFileName());
}
}
protected void runCreateSql() throws IOException {
if (createAllContent == null) {
createAllContent = readFile(getCreateFileName());
}
runScript(false, createAllContent, 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 {
String c = generateDropDdl();
writeFile(dropFile, c);
writeFile(dropFile, generateDropAllDdl());
} catch (IOException e) {
throw new PersistenceException("Error generating Drop DDL", e);
}
@@ -101,28 +140,27 @@ public class DdlGenerator implements SpiEbeanPlugin {
protected void writeCreate(String createFile) {
try {
String c = generateCreateDdl();
writeFile(createFile, c);
writeFile(createFile, generateCreateAllDdl());
} catch (IOException e) {
throw new PersistenceException("Error generating Create DDL", e);
}
}
public String generateDropDdl() {
protected String generateDropAllDdl() {
try {
dropContent = currentModel().getDropDdl();
return dropContent;
dropAllContent = currentModel().getDropAllDdl();
return dropAllContent;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String generateCreateDdl() {
protected String generateCreateAllDdl() {
try {
createContent = currentModel().getCreateDdl();
return createContent;
createAllContent = currentModel().getCreateDdl();
return createAllContent;
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -163,201 +201,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;
}
}
@@ -12,32 +12,17 @@ public class DdlWrite {
private final ModelContainer currentModel;
private final DdlBuffer applyDropDependencies;
private final DdlBuffer apply;
private final DdlBuffer applyForeignKeys;
private final DdlBuffer applyHistory;
private final DdlBuffer rollbackForeignKeys;
private final DdlBuffer dropAllForeignKeys;
private final DdlBuffer rollback;
/**
* For DDL that drops tables and columns etc.
*
* This DDL typically can not run automatically in production as there is most commonly
* existing servers running the application using these tables and columns. Typically
* these drop statements may be executed AFTER all the servers in the application have
* migrated onto new code.
*/
private final DdlBuffer drop;
/**
* For use when History is turned off for a base table or history is no longer
* desired on specific columns. This DDL should typically execute manually after review
* by DBA's.
*/
private final DdlBuffer dropHistory;
private final DdlBuffer dropAll;
/**
* Create without any configuration or current model (no history support).
@@ -51,13 +36,12 @@ public class DdlWrite {
*/
public DdlWrite(MConfiguration configuration, ModelContainer currentModel) {
this.currentModel = currentModel;
this.applyDropDependencies = new BaseDdlBuffer(configuration);
this.apply = new BaseDdlBuffer(configuration);
this.applyForeignKeys = new BaseDdlBuffer(configuration);
this.applyHistory = new BaseDdlBuffer(configuration);
this.rollbackForeignKeys = new BaseDdlBuffer(configuration);
this.rollback = new BaseDdlBuffer(configuration);
this.drop = new BaseDdlBuffer(configuration);
this.dropHistory = new BaseDdlBuffer(configuration);
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
this.dropAll = new BaseDdlBuffer(configuration);
}
/**
@@ -77,22 +61,8 @@ public class DdlWrite {
public boolean isApplyEmpty() {
return apply.getBuffer().isEmpty()
&& applyForeignKeys.getBuffer().isEmpty()
&& applyHistory.getBuffer().isEmpty();
}
/**
* Return true if the apply rollback buffers are all empty.
*/
public boolean isApplyRollbackEmpty() {
return rollback.getBuffer().isEmpty()
&& rollbackForeignKeys.getBuffer().isEmpty();
}
/**
* Return true the drop buffers are empty.
*/
public boolean isDropEmpty() {
return drop.getBuffer().isEmpty() && dropHistory.getBuffer().isEmpty();
&& applyHistory.getBuffer().isEmpty()
&& applyDropDependencies.getBuffer().isEmpty();
}
/**
@@ -102,6 +72,13 @@ public class DdlWrite {
return apply;
}
/**
* Return the buffer that executes early to drop dependencies like views etc.
*/
public DdlBuffer applyDropDependencies() {
return applyDropDependencies;
}
/**
* Return the buffer that APPLY DDL is written to for foreign keys and their associated indexes.
* <p>
@@ -120,41 +97,17 @@ public class DdlWrite {
}
/**
* Return the buffer that ROLLBACK DDL is written to for foreign keys and associated indexes.
* Return the buffer used for the 'drop all DDL' for dropping foreign keys and associated indexes.
*/
public DdlBuffer rollbackForeignKeys() {
return rollbackForeignKeys;
public DdlBuffer dropAllForeignKeys() {
return dropAllForeignKeys;
}
/**
* Return the buffer that ROLLBACK DDL is written to which is considered safe to run when
* apply changes fail to execute. This will reverse the apply changes typically dropping
* newly created tables, foreign keys etc.
* <p>
* When apply changes are made against DB's that support transactional DDL you could argue
* that these rollback statements are not necessary.
* <p>
* Note that statements added to this rollback buffer are executed after foreign key rollback
* has been executed.
* Return the buffer used for the 'drop all DDL' to drop tables, views and history triggers etc.
*/
public DdlBuffer rollback() {
return rollback;
public DdlBuffer dropAll() {
return dropAll;
}
/**
* Return the buffer that destructive changes are written to. This is typically drop table and
* drop column.
*/
public DdlBuffer drop() {
return drop;
}
/**
* Return the buffer that is used when history is no longer required on a table or specific columns.
*/
public DdlBuffer dropHistory() {
return dropHistory;
}
}
@@ -140,7 +140,7 @@ public class BaseTableDdl implements TableDdl {
// add drop table to the rollback buffer - do this before
// we drop the related sequence (if sequences are used)
dropTable(writer.rollback(), tableName);
dropTable(writer.dropAll(), tableName);
if (useSequence) {
String pkCol = pk.get(0).getName();
@@ -149,7 +149,7 @@ public class BaseTableDdl implements TableDdl {
// add blank line for a bit of whitespace between tables
apply.end();
writer.rollback().end();
writer.dropAll().end();
writeAddForeignKeys(writer, createTable);
@@ -204,7 +204,7 @@ public class BaseTableDdl implements TableDdl {
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames))
.endOfStatement();
write.rollbackForeignKeys()
write.dropAllForeignKeys()
.append(platformDdl.dropIndex(uqName, tableName))
.endOfStatement();
}
@@ -225,7 +225,7 @@ public class BaseTableDdl implements TableDdl {
String createSeq = platformDdl.createSequence(seqName, initial, allocate);
if (createSeq != null) {
writer.apply().append(createSeq).newLine();
writer.rollback().append(platformDdl.dropSequence(seqName)).endOfStatement();
writer.dropAll().append(platformDdl.dropSequence(seqName)).endOfStatement();
}
}
@@ -295,15 +295,15 @@ public class BaseTableDdl implements TableDdl {
fkeyBuffer.end();
write.rollbackForeignKeys()
write.dropAllForeignKeys()
.append(platformDdl.alterTableDropForeignKey(tableName, fkName)).endOfStatement();
if (indexName != null) {
write.rollbackForeignKeys()
write.dropAllForeignKeys()
.append(platformDdl.dropIndex(indexName, tableName)).endOfStatement();
}
write.rollbackForeignKeys().end();
write.dropAllForeignKeys().end();
}
@@ -471,7 +471,7 @@ public class BaseTableDdl implements TableDdl {
.append(platformDdl.createIndex(createIndex.getIndexName(), createIndex.getTableName(), cols))
.endOfStatement();
writer.rollback()
writer.dropAll()
.append(platformDdl.dropIndex(createIndex.getIndexName(), createIndex.getTableName()))
.endOfStatement();
}
@@ -520,7 +520,6 @@ public class BaseTableDdl implements TableDdl {
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), tableName, column, false);
alterTableDropColumn(writer.rollback(), tableName, column.getName());
}
if (isTrue(addColumn.isWithHistory())) {
@@ -529,13 +528,11 @@ public class BaseTableDdl implements TableDdl {
for (Column column : columns) {
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
alterTableAddColumn(writer.apply(), historyTable, column, true);
alterTableDropColumn(writer.rollback(), historyTable, column.getName());
}
}
// add a bit of whitespace
writer.apply().end();
writer.rollback().end();
}
/**
@@ -544,7 +541,7 @@ public class BaseTableDdl implements TableDdl {
@Override
public void generate(DdlWrite writer, DropTable dropTable) throws IOException {
dropTable(writer.drop(), dropTable.getName());
dropTable(writer.apply(), dropTable.getName());
}
/**
@@ -555,14 +552,14 @@ public class BaseTableDdl implements TableDdl {
String tableName = dropColumn.getTableName();
alterTableDropColumn(writer.drop(), tableName, dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
if (isTrue(dropColumn.isWithHistory())) {
// also drop from the history table
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
alterTableDropColumn(writer.drop(), historyTable(tableName), dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
}
writer.drop().end();
writer.apply().end();
}
/**
@@ -642,21 +639,6 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
// reverse and generate the rollback statement
String currentType = alter.getCurrentType();
String type = alter.getType();
Boolean currentNotnull = alter.isCurrentNotnull();
Boolean notnull = alter.isNotnull();
alter.setCurrentType(type);
alter.setType(currentType);
alter.setNotnull(currentNotnull);
alter.setCurrentNotnull(notnull);
// write the rollback
ddl = platformDdl.alterColumnBaseAttributes(alter);
writer.rollback().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && alter.getType() != null) {
// mysql and sql server column type change allowing nulls in the history table column
AlterColumn alterHistoryColumn = new AlterColumn();
@@ -667,11 +649,6 @@ public class BaseTableDdl implements TableDdl {
// write the apply to history table
writer.apply().append(histColumnDdl).endOfStatement();
// write the rollback from history table
alterHistoryColumn.setType(currentType);
histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
writer.rollback().append(histColumnDdl).endOfStatement();
}
}
}
@@ -689,8 +666,6 @@ public class BaseTableDdl implements TableDdl {
String ddl = platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isNotnull());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
ddl = platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isCurrentNotnull());
writer.rollback().append(ddl).endOfStatement();
}
}
@@ -699,14 +674,10 @@ public class BaseTableDdl implements TableDdl {
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getCurrentType());
writer.rollback().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory())) {
// apply same type change to matching column in the history table
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
writer.apply().append(ddl).endOfStatement();
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getCurrentType());
writer.rollback().append(ddl).endOfStatement();
}
}
}
@@ -762,7 +733,7 @@ public class BaseTableDdl implements TableDdl {
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols))
.endOfStatement();
writer.rollbackForeignKeys()
writer.dropAllForeignKeys()
.append(platformDdl.dropIndex(uqName, alter.getTableName()))
.endOfStatement();
}
@@ -10,7 +10,6 @@ import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -45,21 +44,43 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
this.historySuffix = serverConfig.getHistoryTableSuffix();
this.constraintNaming = serverConfig.getConstraintNaming();
this.sysPeriodStart = sysPeriod+"_start";
this.sysPeriodEnd = sysPeriod+"_end";
this.sysPeriodStart = sysPeriod + "_start";
this.sysPeriodEnd = sysPeriod + "_end";
}
@Override
public void regenerateHistoryTriggers(DdlWrite writer, HistoryTableUpdate update) throws IOException {
public void updateTriggers(DdlWrite writer, HistoryTableUpdate update) throws IOException {
MTable table = writer.getTable(update.getBaseTable());
if (table == null) {
throw new IllegalStateException("MTable "+update.getBaseTable()+" not found in writer? (required for history DDL)");
throw new IllegalStateException("MTable " + update.getBaseTable() + " not found in writer? (required for history DDL)");
}
regenerateHistoryTriggers(writer, table, update);
updateTriggers(writer, table, update);
}
protected abstract void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException;
/**
* Replace the existing triggers/stored procedures/views for history table support given the included columns.
*/
protected abstract void updateHistoryTriggers(DbTriggerUpdate triggerUpdate) throws IOException;
/**
* Process the HistoryTableUpdate which can result in changes to the apply, rollback
* and drop scripts.
*/
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
}
protected DbTriggerUpdate createDbTriggerUpdate(DdlWrite writer, MTable table) {
List<String> columns = columnNamesForApply(table);
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
return new DbTriggerUpdate(baseTableName, historyTableName, writer, columns);
}
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
@@ -67,18 +88,17 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String baseTable = dropHistoryTable.getBaseTable();
// drop in appropriate order
dropTriggers(writer.dropHistory(), baseTable);
dropHistoryTableEtc(writer.dropHistory(), baseTable);
dropTriggers(writer.applyDropDependencies(), baseTable);
dropHistoryTableEtc(writer.applyDropDependencies(), baseTable);
}
@Override
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
String baseTable = addHistoryTable.getBaseTable();
MTable table = writer.getTable(baseTable);
if (table == null) {
throw new IllegalStateException("MTable "+baseTable+" not found in writer? (required for history DDL)");
throw new IllegalStateException("MTable " + baseTable + " not found in writer? (required for history DDL)");
}
createWithHistory(writer, table);
@@ -90,12 +110,11 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String baseTable = table.getName();
String whenCreatedColumn = table.getWhenCreatedColumn();
// rollback changes in appropriate order
dropTriggers(writer.rollback(), baseTable);
dropHistoryTableEtc(writer.rollback(), baseTable);
dropTriggers(writer.dropAll(), baseTable);
dropHistoryTableEtc(writer.dropAll(), baseTable);
addHistoryTable(writer, table, whenCreatedColumn);
addStoredFunction(writer, table, null);
createStoredFunction(writer, table);
createTriggers(writer, table);
}
@@ -103,7 +122,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
protected abstract void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException;
protected void addStoredFunction(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
protected void createStoredFunction(DdlWrite writer, MTable table) throws IOException {
// do nothing
}
@@ -158,7 +177,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();
Collection<MColumn> cols = table.getColumns().values();
Collection<MColumn> cols = table.allColumns();
for (MColumn column : cols) {
if (!column.isDraftOnly()) {
writeColumnDefinition(apply, column.getName(), column.getType());
@@ -191,6 +210,26 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
.endOfStatement().end();
}
/**
* Create or replace the with_history view with explicit columns.
*/
protected void createWithHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
apply.append("create or replace view ").append(update.getBaseTable()).append(viewSuffix).append(" as select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getBaseTable()).append(" union all select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getHistoryTable()).endOfStatement().end();
}
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
appendColumnName(apply, prefix, sysPeriodStart);
appendColumnName(apply, prefix, sysPeriodEnd);
}
protected void dropHistoryTableEtc(DdlBuffer buffer, String baseTableName) throws IOException {
buffer.append("drop view ").append(baseTableName).append(viewSuffix).endOfStatement();
@@ -203,8 +242,6 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
}
//protected abstract void addFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException;
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriodStart).append(",").append(sysPeriodEnd).append(",");
@@ -216,7 +253,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
protected void appendColumnNames(DdlBuffer buffer, List<String> columns, String columnPrefix) throws IOException {
for (int i=0; i< columns.size(); i++) {
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
buffer.append(", ");
}
@@ -226,18 +263,26 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
}
/**
* Return the list of included columns in order.
* Append a single column to the buffer if it is not null.
*/
protected List<String> includedColumnNames(MTable table) throws IOException {
protected void appendColumnName(DdlBuffer buffer, String prefix, String columnName) throws IOException {
Collection<MColumn> columns = table.getColumns().values();
List<String> includedColumns = new ArrayList<String>(columns.size());
for (MColumn column : columns) {
if (column.isIncludeInHistory()) {
includedColumns.add(column.getName());
}
if (columnName != null) {
buffer.append(prefix).append(columnName);
}
return includedColumns;
}
/**
* Return the column names included in history for the apply script.
* <p>
* Note that dropped columns are actually still included at this point as they are going
* to be removed from the history handling when the drop script runs that also deletes
* the column.
* </p>
*/
protected List<String> columnNamesForApply(MTable table) {
return table.allHistoryColumns(true);
}
}
@@ -0,0 +1,63 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
import java.util.List;
/**
* DB trigger update when a change occurs on a table with history.
*/
public class DbTriggerUpdate {
private final String baseTableName;
private final String historyTableName;
private final DdlWrite writer;
private final List<String> columns;
public DbTriggerUpdate(String baseTableName, String historyTableName, DdlWrite writer, List<String> columns) {
this.baseTableName = baseTableName;
this.historyTableName = historyTableName;
this.writer = writer;
this.columns = columns;
}
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyBuffer() {
return writer.applyHistory();
}
/**
* Return the appropriate drop dependency buffer for the current mode.
*/
public DdlBuffer dropDependencyBuffer() {
return writer.applyDropDependencies();
}
/**
* Return the base table name.
*/
public String getBaseTable() {
return baseTableName;
}
/**
* Return the history table name.
*/
public String getHistoryTable() {
return historyTableName;
}
/**
* Return the included columns.
*/
public List<String> getColumns() {
return columns;
}
}
@@ -6,7 +6,6 @@ import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
import com.avaje.ebean.dbmigration.model.MTable;
import java.io.IOException;
import java.util.List;
/**
* H2 history support using DB triggers to maintain a history table.
@@ -28,40 +27,20 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
List<String> includedColumns = includedColumnNames(table);
DdlBuffer apply = writer.applyHistory();
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName);
}
@Override
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
List<String> includedColumns = includedColumnNames(table);
DdlBuffer apply = writer.applyHistory();
apply.append("-- Regenerated ").newLine();
apply.append("-- changes: ").append(update.description()).newLine();
dropTriggers(apply, baseTableName);
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
// put a reverted version into the rollback buffer
update.toRevertedColumns(includedColumns);
DdlBuffer rollback = writer.rollback();
rollback.append("-- Revert regenerated ").newLine();
rollback.append("-- revert changes: ").append(update.description()).newLine();
dropTriggers(rollback, baseTableName);
addCreateTrigger(rollback, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
DdlBuffer buffer = update.historyBuffer();
dropTriggers(buffer, update.getBaseTable());
addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable());
}
private void addCreateTrigger(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
private void addCreateTrigger(DdlBuffer apply, String triggerName, String baseTable) throws IOException {
// Note that this does not take into account the historyTable name (excepts _history suffix) and
// does not take into account excluded columns (all columns included in history)
@@ -10,7 +10,6 @@ import java.util.List;
*/
public class HistoryTableUpdate {
/**
* Column change type.
*/
@@ -21,35 +20,25 @@ public class HistoryTableUpdate {
EXCLUDE
}
public static class Column {
private static class Column {
final Change change;
final String column;
public final Change change;
public final String column;
public Column(Change change, String column) {
this.change = change;
this.column = column;
}
public String toString() {
return description();
}
public String description() {
return change.name().toLowerCase()+" "+column;
}
public void apply(List<String> includedColumns) {
switch (change) {
case ADD:
case INCLUDE: {
includedColumns.remove(column);
break;
}
case DROP:
case EXCLUDE: {
includedColumns.add(column);
break;
}
default:
throw new IllegalStateException("Unexpected change "+change);
}
}
}
private final String baseTable;
@@ -65,27 +54,12 @@ public class HistoryTableUpdate {
/**
* Return a description of the changes that cause the history trigger/function
* to be regenerated (added, dropped, included or excluded columns).
* to be regenerated (added, included, excluded and dropped columns).
*/
public String description() {
StringBuilder sb = new StringBuilder(90);
for (int i = 0; i < columnChanges.size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(columnChanges.get(i).description());
}
return sb.toString();
return columnChanges.toString();
}
public void toRevertedColumns(List<String> includedColumns) {
for (Column columnChange : columnChanges) {
columnChange.apply(includedColumns);
}
}
/**
* Add a comment for column added, dropped, included or excluded.
*/
@@ -100,10 +74,4 @@ public class HistoryTableUpdate {
return baseTable;
}
/**
* Return the comments.
*/
public List<Column> getColumnChanges() {
return columnChanges;
}
}
@@ -5,7 +5,6 @@ import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
import com.avaje.ebean.dbmigration.model.MTable;
import java.io.IOException;
import java.util.List;
/**
* MySql history support using DB triggers to maintain a history table.
@@ -26,67 +25,46 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
List<String> includedColumns = includedColumnNames(table);
DbTriggerUpdate update = createDbTriggerUpdate(writer, table);
DdlBuffer apply = writer.applyHistory();
addBeforeUpdate(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
addBeforeDelete(apply, deleteTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
addBeforeUpdate(updateTriggerName(update.getBaseTable()), update);
addBeforeDelete(deleteTriggerName(update.getBaseTable()), update);
}
@Override
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
List<String> includedColumns = includedColumnNames(table);
DdlBuffer buffer = update.historyBuffer();
String baseTable = update.getBaseTable();
DdlBuffer apply = writer.applyHistory();
apply.append("-- Regenerated ").newLine();
apply.append("-- changes: ").append(update.description()).newLine();
// lock the base table while we drop and recreate the triggers
apply.append("lock tables ").append(baseTableName).append(" write").endOfStatement();
dropTriggers(apply, baseTableName);
addBeforeUpdate(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
addBeforeDelete(apply, deleteTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
apply.append("unlock tables").endOfStatement();
// put a reverted version into the rollback buffer
update.toRevertedColumns(includedColumns);
DdlBuffer rollback = writer.rollback();
rollback.append("-- Revert regenerated ").newLine();
rollback.append("-- revert changes: ").append(update.description()).newLine();
// lock the base table while we drop and recreate the triggers
rollback.append("lock tables ").append(baseTableName).append(" write").endOfStatement();
dropTriggers(rollback, baseTableName);
addBeforeUpdate(rollback, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
addBeforeDelete(rollback, deleteTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
rollback.append("unlock tables").endOfStatement();
buffer.append("lock tables ").append(baseTable).append(" write").endOfStatement();
dropTriggers(buffer, baseTable);
addBeforeUpdate(updateTriggerName(baseTable), update);
addBeforeDelete(deleteTriggerName(baseTable), update);
buffer.append("unlock tables").endOfStatement();
}
private void addBeforeUpdate(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
private void addBeforeUpdate(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before update on ").append(baseTable)
.append("create trigger ").append(triggerName).append(" before update on ").append(update.getBaseTable())
.append(" for each row begin").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
appendInsertIntoHistory(apply, update.getHistoryTable(), update.getColumns());
apply
.append(" set NEW.").append(sysPeriod).append("_start = now(6)").endOfStatement()
.append("end$$").newLine();
}
private void addBeforeDelete(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before delete on ").append(baseTable)
.append("create trigger ").append(triggerName).append(" before delete on ").append(update.getBaseTable())
.append(" for each row begin").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
appendInsertIntoHistory(apply, update.getHistoryTable(), update.getColumns());
apply.append("end$$").newLine();
}
@@ -35,7 +35,7 @@ public class NoHistorySupportDdl implements PlatformHistoryDdl {
}
@Override
public void regenerateHistoryTriggers(DdlWrite write, HistoryTableUpdate update) {
public void updateTriggers(DdlWrite write, HistoryTableUpdate update) {
// does nothing
}
}
@@ -200,7 +200,7 @@ public class PlatformDdl {
* Regenerate the history triggers (or function) due to a column being added/dropped/excluded or included.
*/
public void regenerateHistoryTriggers(DdlWrite write, HistoryTableUpdate update) throws IOException {
historyDdl.regenerateHistoryTriggers(write, update);
historyDdl.updateTriggers(write, update);
}
/**
@@ -36,5 +36,5 @@ public interface PlatformHistoryDdl {
/**
* Regenerate the history triggers/stored function due to column added/dropped/included or excluded.
*/
void regenerateHistoryTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException;
void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException;
}
@@ -14,7 +14,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
public PostgresHistoryDdl() {
this.currentTimestamp = "current_timestamp";
this.currentTimestamp = "statement_timestamp()";
}
/**
@@ -45,6 +45,11 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
}
}
@Override
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
appendColumnName(apply, prefix, sysPeriod);
}
@Override
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriod).endOfStatement();
@@ -72,7 +77,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
buffer.end();
}
protected void addFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
protected void createOrReplaceFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
apply
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
.append("begin").newLine();
@@ -80,7 +85,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
.append(" if (TG_OP = 'UPDATE') then").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
.append(" return new;").newLine();
apply
.append(" elsif (TG_OP = 'DELETE') then").newLine();
@@ -96,40 +101,36 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
}
@Override
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
// just replace the stored function with 'create or replace'
addStoredFunction(writer, table, update);
}
@Override
protected void addStoredFunction(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
protected void createStoredFunction(DdlWrite writer, MTable table) throws IOException {
String procedureName = procedureName(table.getName());
String historyTable = historyTableName(table.getName());
List<String> includedColumns = includedColumnNames(table);
List<String> columnNames = columnNamesForApply(table);
createOrReplaceFunction(writer.applyHistory(), procedureName, historyTable, columnNames);
}
DdlBuffer apply = writer.applyHistory();
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
if (update != null) {
apply.append("-- Regenerated ").append(procedureName).newLine();
apply.append("-- changes: ").append(update.description()).newLine();
}
String procedureName = procedureName(update.getBaseTable());
addFunction(apply, procedureName, historyTable, includedColumns);
recreateHistoryView(update);
createOrReplaceFunction(update.historyBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
/**
* For postgres we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
private void recreateHistoryView(DbTriggerUpdate update) throws IOException {
if (update != null) {
// put a reverted version into the rollback buffer
update.toRevertedColumns(includedColumns);
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
DdlBuffer rollback = writer.rollback();
rollback.append("-- Revert regenerated ").append(procedureName).newLine();
rollback.append("-- revert changes: ").append(update.description()).newLine();
addFunction(rollback, procedureName, historyTable, includedColumns);
}
// recreate the view with specific columns specified (the columns generally are not dropped until later)
createWithHistoryView(update);
}
@Override
@@ -137,7 +138,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
appendColumnNames(buffer, columns, "");
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), current_timestamp), ");
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), ").append(currentTimestamp).append("), ");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}
@@ -27,6 +27,8 @@ import javax.xml.bind.annotation.XmlType;
* &lt;/choice>
* &lt;/sequence>
* &lt;attribute name="type" use="required" type="{http://ebean-orm.github.io/xml/ns/dbmigration}changeSetType" />
* &lt;attribute name="dropsFor" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="suppressDropsForever" type="{http://www.w3.org/2001/XMLSchema}boolean" />
* &lt;attribute name="generated" type="{http://www.w3.org/2001/XMLSchema}boolean" />
* &lt;attribute name="author" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="comment" type="{http://www.w3.org/2001/XMLSchema}string" />
@@ -62,6 +64,10 @@ public class ChangeSet {
protected List<Object> changeSetChildren;
@XmlAttribute(name = "type", required = true)
protected ChangeSetType type;
@XmlAttribute(name = "dropsFor")
protected String dropsFor;
@XmlAttribute(name = "suppressDropsForever")
protected Boolean suppressDropsForever;
@XmlAttribute(name = "generated")
protected Boolean generated;
@XmlAttribute(name = "author")
@@ -134,6 +140,54 @@ public class ChangeSet {
this.type = value;
}
/**
* Gets the value of the dropsFor property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getDropsFor() {
return dropsFor;
}
/**
* Sets the value of the dropsFor property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setDropsFor(String value) {
this.dropsFor = value;
}
/**
* Gets the value of the suppressDropsForever property.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isSuppressDropsForever() {
return suppressDropsForever;
}
/**
* Sets the value of the suppressDropsForever property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setSuppressDropsForever(Boolean value) {
this.suppressDropsForever = value;
}
/**
* Gets the value of the generated property.
*
@@ -15,7 +15,7 @@ import javax.xml.bind.annotation.XmlType;
* &lt;simpleType name="changeSetType">
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string">
* &lt;enumeration value="apply"/>
* &lt;enumeration value="drop"/>
* &lt;enumeration value="pendingDrops"/>
* &lt;enumeration value="baseline"/>
* &lt;/restriction>
* &lt;/simpleType>
@@ -28,8 +28,8 @@ public enum ChangeSetType {
@XmlEnumValue("apply")
APPLY("apply"),
@XmlEnumValue("drop")
DROP("drop"),
@XmlEnumValue("pendingDrops")
PENDING_DROPS("pendingDrops"),
@XmlEnumValue("baseline")
BASELINE("baseline");
private final String value;
@@ -30,6 +30,7 @@ import javax.xml.bind.annotation.XmlType;
* &lt;attGroup ref="{http://ebean-orm.github.io/xml/ns/dbmigration}tablespaceAttributes"/>
* &lt;attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="withHistory" type="{http://www.w3.org/2001/XMLSchema}boolean" />
* &lt;attribute name="draft" type="{http://www.w3.org/2001/XMLSchema}boolean" />
* &lt;attribute name="identityType" type="{http://ebean-orm.github.io/xml/ns/dbmigration}identityType" />
* &lt;attribute name="sequenceName" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="sequenceInitial" type="{http://www.w3.org/2001/XMLSchema}positiveInteger" />
@@ -59,6 +60,8 @@ public class CreateTable {
protected String name;
@XmlAttribute(name = "withHistory")
protected Boolean withHistory;
@XmlAttribute(name = "draft")
protected Boolean draft;
@XmlAttribute(name = "identityType")
protected IdentityType identityType;
@XmlAttribute(name = "sequenceName")
@@ -213,6 +216,30 @@ public class CreateTable {
this.withHistory = value;
}
/**
* Gets the value of the draft property.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isDraft() {
return draft;
}
/**
* Sets the value of the draft property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setDraft(Boolean value) {
this.draft = value;
}
/**
* Gets the value of the identityType property.
*
@@ -7,22 +7,46 @@ import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Simple writer for output of the Migration/ChangeSet as an XML document.
*/
public class MigrationXmlWriter {
private final String comment;
public MigrationXmlWriter(String comment) {
this.comment = comment;
}
/**
* Write a Migration to a file as an xml document to the file.
*/
public void write(Migration migration, File file) {
try {
FileWriter writer = new FileWriter(file);
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
if (comment != null) {
writer.write("<!-- ");
writer.write(comment);
writer.write(" -->\n");
}
JAXBContext jaxbContext = JAXBContext.newInstance(Migration.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(migration, file);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.marshal(migration, writer);
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (JAXBException e) {
throw new RuntimeException(e);
@@ -117,13 +117,13 @@ public class CurrentModel {
/**
* Return the 'Drop' DDL.
*/
public String getDropDdl() throws IOException {
public String getDropAllDdl() throws IOException {
createDdl();
StringBuilder ddl = new StringBuilder(2000);
ddl.append(write.rollbackForeignKeys().getBuffer());
ddl.append(write.rollback().getBuffer());
ddl.append(write.dropAllForeignKeys().getBuffer());
ddl.append(write.dropAll().getBuffer());
return ddl.toString();
}
@@ -159,12 +159,7 @@ public class CurrentModel {
ModelDiff diff = new ModelDiff();
diff.compareTo(model);
List<Object> applyChanges = diff.getApplyChanges();
// put the changes into a ChangeSet
ChangeSet applyChangeSet = new ChangeSet();
applyChangeSet.getChangeSetChildren().addAll(applyChanges);
return applyChangeSet;
return diff.getApplyChangeSet();
}
}
@@ -283,7 +283,6 @@ public class MColumn {
/**
* Compare the column meta data and return true if there is a change that means
* the history table column needs
*/
public void compare(ModelDiff modelDiff, MTable table, MColumn newColumn) {
@@ -10,13 +10,14 @@ import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
import com.avaje.ebean.dbmigration.migration.DropTable;
import com.avaje.ebean.dbmigration.migration.IdentityType;
import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -38,6 +39,8 @@ import java.util.Set;
*/
public class MTable {
private static final Logger logger = LoggerFactory.getLogger(MTable.class);
/**
* Table name.
*/
@@ -118,6 +121,8 @@ public class MTable {
*/
private AddColumn addColumn;
private List<String> droppedColumns = new ArrayList<String>();
/**
* Create a copy of this table structure as a 'draft' table.
*
@@ -126,14 +131,14 @@ public class MTable {
*/
public MTable createDraftTable() {
draftTable = new MTable(name+"_draft");
draftTable = new MTable(name + "_draft");
draftTable.draft = true;
draftTable.whenCreatedColumn = whenCreatedColumn;
// compoundKeys
// compoundUniqueConstraints
draftTable.identityType = identityType;
for (MColumn col: columns.values()) {
for (MColumn col : allColumns()) {
draftTable.addColumn(col.copyForDraft());
}
@@ -150,6 +155,7 @@ public class MTable {
this.tablespace = createTable.getTablespace();
this.indexTablespace = createTable.getIndexTablespace();
this.withHistory = Boolean.TRUE.equals(createTable.isWithHistory());
this.draft = Boolean.TRUE.equals(createTable.isDraft());
this.sequenceName = createTable.getSequenceName();
this.sequenceInitial = toInt(createTable.getSequenceInitial());
this.sequenceAllocate = toInt(createTable.getSequenceAllocate());
@@ -194,8 +200,11 @@ public class MTable {
if (withHistory) {
createTable.setWithHistory(Boolean.TRUE);
}
if (draft) {
createTable.setDraft(Boolean.TRUE);
}
for (MColumn column : this.columns.values()) {
for (MColumn column : allColumns()) {
// filter out draftOnly columns from the base table
if (draft || !column.isDraftOnly()) {
createTable.getColumn().add(column.createColumn());
@@ -244,26 +253,33 @@ public class MTable {
addColumn = null;
Set<String> mappedColumns = new LinkedHashSet<String>();
Map<String, MColumn> newColumnMap = newTable.getColumns();
Collection<MColumn> newColumns = newTable.getColumns().values();
for (MColumn newColumn : newColumns) {
MColumn localColumn = columns.get(newColumn.getName());
// compare newColumns to existing columns (look for new and diff columns)
for (MColumn newColumn : newColumnMap.values()) {
MColumn localColumn = getColumn(newColumn.getName());
if (localColumn == null) {
diffNewColumn(newColumn);
// can ignore if draftOnly column and non-draft table
if (!newColumn.isDraftOnly() || draft) {
diffNewColumn(newColumn);
}
} else {
// note that if there are alter column changes in here then
// the table withHistory is taken into account
localColumn.compare(modelDiff, this, newColumn);
mappedColumns.add(newColumn.getName());
}
}
Collection<MColumn> existingColumns = columns.values();
for (MColumn existingColumn : existingColumns) {
if (!mappedColumns.contains(existingColumn.getName())) {
diffDropColumn(modelDiff, existingColumn);
// compare existing columns (look for dropped columns)
int columnPosition = 0;
for (MColumn existingColumn : allColumns()) {
MColumn newColumn = newColumnMap.get(existingColumn.getName());
if (newColumn == null) {
diffDropColumn(modelDiff, existingColumn, columnPosition, newTable);
} else if (newColumn.isDraftOnly() && !draft) {
// effectively a drop column (draft only column on a non-draft table)
logger.trace("... drop column {} from table {} as now draftOnly", newColumn.getName(), name);
diffDropColumn(modelDiff, existingColumn, columnPosition, newTable);
}
columnPosition++;
}
if (addColumn != null) {
@@ -287,7 +303,7 @@ public class MTable {
public void apply(AlterColumn alterColumn) {
checkTableName(alterColumn.getTableName());
String columnName = alterColumn.getColumnName();
MColumn existingColumn = columns.get(columnName);
MColumn existingColumn = getColumn(columnName);
if (existingColumn == null) {
throw new IllegalStateException("Column [" + columnName + "] does not exist for AlterColumn change?");
}
@@ -299,7 +315,10 @@ public class MTable {
*/
public void apply(DropColumn dropColumn) {
checkTableName(dropColumn.getTableName());
columns.remove(dropColumn.getColumnName());
MColumn removed = columns.remove(dropColumn.getColumnName());
if (removed == null) {
throw new IllegalStateException("Column [" + dropColumn.getColumnName() + "] does not exist for DropColumn change on table [" + dropColumn.getTableName() + "]?");
}
}
public String getName() {
@@ -313,10 +332,6 @@ public class MTable {
return draft;
}
public String getPkName() {
return pkName;
}
public void setPkName(String pkName) {
this.pkName = pkName;
}
@@ -341,11 +356,43 @@ public class MTable {
return withHistory;
}
public void setWithHistory(boolean withHistory) {
public MTable setWithHistory(boolean withHistory) {
this.withHistory = withHistory;
return this;
}
public Map<String, MColumn> getColumns() {
public List<String> allHistoryColumns(boolean includeDropped) {
List<String> columnNames = new ArrayList<String>(columns.size());
for (MColumn column : columns.values()) {
if (column.isIncludeInHistory()) {
columnNames.add(column.getName());
}
}
if (includeDropped && !droppedColumns.isEmpty()) {
for (String droppedColumn : droppedColumns) {
columnNames.add(droppedColumn);
}
}
return columnNames;
}
/**
* Return all the columns (excluding columns marked as dropped).
*/
public Collection<MColumn> allColumns() {
return columns.values();
}
/**
* Return the column by name.
*/
public MColumn getColumn(String name) {
return columns.get(name);
}
private Map<String, MColumn> getColumns() {
return columns;
}
@@ -357,26 +404,14 @@ public class MTable {
return compoundKeys;
}
public String getSequenceName() {
return sequenceName;
}
public void setSequenceName(String sequenceName) {
this.sequenceName = sequenceName;
}
public int getSequenceInitial() {
return sequenceInitial;
}
public void setSequenceInitial(int sequenceInitial) {
this.sequenceInitial = sequenceInitial;
}
public int getSequenceAllocate() {
return sequenceAllocate;
}
public void setSequenceAllocate(int sequenceAllocate) {
this.sequenceAllocate = sequenceAllocate;
}
@@ -399,22 +434,12 @@ public class MTable {
this.identityType = identityType;
}
/**
* Returns the identity type to use for this table.
* <p>
* If set then this overrides the platform default so for UUID generated values
* or DB's supporting both sequences and autoincrement.
*/
public IdentityType getIdentityType() {
return identityType;
}
/**
* Return the list of columns that make the primary key.
*/
public List<MColumn> primaryKeyColumns() {
List<MColumn> pk = new ArrayList<MColumn>(3);
for (MColumn column : columns.values()) {
for (MColumn column : allColumns()) {
if (column.isPrimaryKey()) {
pk.add(column);
}
@@ -473,7 +498,7 @@ public class MTable {
*/
public MColumn addColumn(String dbCol, String columnDefn, boolean notnull) {
MColumn existingColumn = columns.get(dbCol);
MColumn existingColumn = getColumn(dbCol);
if (existingColumn != null) {
if (notnull) {
existingColumn.setNotnull(true);
@@ -507,12 +532,12 @@ public class MTable {
/**
* Add a 'drop column' to the diff.
*/
private void diffDropColumn(ModelDiff modelDiff, MColumn existingColumn) {
private void diffDropColumn(ModelDiff modelDiff, MColumn existingColumn, int columnPosition, MTable newTable) {
DropColumn dropColumn = new DropColumn();
dropColumn.setTableName(name);
dropColumn.setColumnName(existingColumn.getName());
if (withHistory) {
if (withHistory && !existingColumn.isHistoryExclude()) {
// These dropColumns should occur on the history
// table as well as the base table
dropColumn.setWithHistory(Boolean.TRUE);
@@ -521,6 +546,16 @@ public class MTable {
modelDiff.addDropColumn(dropColumn);
}
/**
* Register a pending un-applied drop column.
* <p>
* This means this column still needs to be included in history views/triggers etc even
* though it is not part of the current model.
*/
public void registerPendingDropColumn(String columnName) {
droppedColumns.add(columnName);
}
private int toInt(BigInteger value) {
return (value == null) ? 0 : value.intValue();
}
@@ -532,7 +567,7 @@ public class MTable {
/**
* Check if there are duplicate foreign keys.
* <p>
* This can occur when an ManyToMany relates back to itself.
* This can occur when an ManyToMany relates back to itself.
* </p>
*/
public void checkDuplicateForeignKeys() {
@@ -563,7 +598,7 @@ public class MTable {
*/
public void adjustReferences(ModelContainer modelContainer) {
Collection<MColumn> cols = columns.values();
Collection<MColumn> cols = allColumns();
for (MColumn col : cols) {
String references = col.getReferences();
if (references != null) {
@@ -583,7 +618,7 @@ public class MTable {
*/
private String extractBaseTable(String references) {
int lastDot = references.lastIndexOf('.');
return references.substring(0,lastDot);
return references.substring(0, lastDot);
}
/**
@@ -592,7 +627,7 @@ public class MTable {
*/
private String deriveReferences(String references, String draftTableName) {
int lastDot = references.lastIndexOf('.');
return draftTableName+"."+references.substring(lastDot+1);
return draftTableName + "." + references.substring(lastDot + 1);
}
}
@@ -52,7 +52,7 @@ public class MigrationModel {
List<MigrationResource> resources = new ArrayList<MigrationResource>();
for (File xmlFile: xmlFiles) {
resources.add(new MigrationResource(xmlFile));
resources.add(new MigrationResource(xmlFile, createVersion(xmlFile)));
}
// sort into version order before applying
@@ -60,7 +60,7 @@ public class MigrationModel {
for (MigrationResource migrationResource: resources) {
logger.debug("read {}", migrationResource);
model.apply(migrationResource.read());
model.apply(migrationResource.read(), migrationResource.getVersion());
}
// remember the last version
@@ -69,6 +69,12 @@ public class MigrationModel {
}
}
private MigrationVersion createVersion(File xmlFile) {
String fileName = xmlFile.getName();
String versionName = fileName.substring(0, fileName.length() - modelSuffix.length());
return MigrationVersion.parse(versionName);
}
public String getNextVersion(String initialVersion) {
return lastVersion == null ? initialVersion : lastVersion.nextVersion();
@@ -17,9 +17,9 @@ public class MigrationResource implements Comparable<MigrationResource> {
/**
* Construct with a migration xml file.
*/
public MigrationResource(File migrationFile) {
public MigrationResource(File migrationFile, MigrationVersion version) {
this.migrationFile = migrationFile;
this.version = MigrationVersion.parse(migrationFile.getName());
this.version = version;
}
public String toString() {
@@ -1,5 +1,7 @@
package com.avaje.ebean.dbmigration.model;
import java.util.Arrays;
/**
* The version of a migration used so that migrations are processed in order.
*/
@@ -15,23 +17,74 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
*/
private final int[] ordering;
private MigrationVersion(String raw, int[] ordering) {
private final boolean[] underscores;
private final String comment;
private MigrationVersion(String raw, int[] ordering, boolean[] underscores, String comment) {
this.raw = raw;
this.ordering = ordering;
this.underscores = underscores;
this.comment = comment;
}
public String toString() {
return raw;
}
/**
* Return the version comment.
*/
public String getComment() {
return comment;
}
/**
* Return the version in raw form.
*/
public String getRaw() {
return raw;
}
/**
* Return the trimmed version excluding version comment and un-parsable string.
*/
public String asString() {
return formattedVersion(false, false);
}
/**
* Return the trimmed version with any underscores replaced with '.'
*/
public String normalised() {
return formattedVersion(true, false);
}
/**
* Return the next version based on this version.
*/
public String nextVersion() {
return formattedVersion(false, true);
}
/**
* Returns the version part of the string.
*
* Normalised means always use '.' delimiters (no underscores).
* NextVersion means bump/increase the last version number by 1.
*/
private String formattedVersion(boolean normalised, boolean nextVersion) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ordering.length; i++) {
if (i < ordering.length -1 ) {
sb.append(ordering[i]).append(".");
if (i < ordering.length - 1) {
sb.append(ordering[i]);
if (normalised) {
sb.append('.');
} else {
sb.append(underscores[i] ? '_' : '.');
}
} else {
sb.append(ordering[i]+1);
sb.append((nextVersion) ? ordering[i] + 1 : ordering[i]);
}
}
return sb.toString();
@@ -54,33 +107,54 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
return 0;
}
/**
* Parse the raw version string and just return the leading version number;
*/
public static String trim(String raw) {
return parse(raw).asString();
}
/**
* Parse the raw version string into a MigrationVersion.
*/
public static MigrationVersion parse(String raw) {
String value = raw.replace("__",".");
value = value.replace('_','.');
String comment = "";
String value = raw;
int commentStart = raw.indexOf("__");
if (commentStart > -1) {
// trim off the trailing comment
comment = raw.substring(commentStart + 2);
value = value.substring(0, commentStart);
}
value = value.replace('_', '.');
String[] sections = value.split("\\.");
boolean[] underscores = new boolean[sections.length];
int[] ordering = new int[sections.length];
int delimiterPos = 0;
int stopIndex = 0;
for (int i = 0; i < sections.length; i++) {
try {
ordering[i] = Integer.parseInt(sections[i]);
stopIndex++;
delimiterPos += sections[i].length();
underscores[i] = (delimiterPos < raw.length() - 1 && raw.charAt(delimiterPos) == '_');
delimiterPos++;
} catch (NumberFormatException e) {
// stop parsing
break;
}
}
int[] actualOrder = new int[stopIndex];
System.arraycopy(ordering, 0, actualOrder, 0, stopIndex);
int[] actualOrder = Arrays.copyOf(ordering, stopIndex);
boolean[] actualUnderscores = Arrays.copyOf(underscores, stopIndex);
return new MigrationVersion(raw, actualOrder);
return new MigrationVersion(raw, actualOrder, actualUnderscores, comment);
}
}
@@ -4,6 +4,7 @@ import com.avaje.ebean.dbmigration.migration.AddColumn;
import com.avaje.ebean.dbmigration.migration.AddHistoryTable;
import com.avaje.ebean.dbmigration.migration.AlterColumn;
import com.avaje.ebean.dbmigration.migration.ChangeSet;
import com.avaje.ebean.dbmigration.migration.ChangeSetType;
import com.avaje.ebean.dbmigration.migration.CreateIndex;
import com.avaje.ebean.dbmigration.migration.CreateTable;
import com.avaje.ebean.dbmigration.migration.DropColumn;
@@ -12,7 +13,6 @@ import com.avaje.ebean.dbmigration.migration.DropIndex;
import com.avaje.ebean.dbmigration.migration.DropTable;
import com.avaje.ebean.dbmigration.migration.Migration;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -28,23 +28,23 @@ public class ModelContainer {
/**
* All the tables in the model.
*/
private Map<String, MTable> tables = new LinkedHashMap<String, MTable>();
private final Map<String, MTable> tables = new LinkedHashMap<String, MTable>();
/**
* All the non unique non foreign key indexes.
*/
private Map<String, MIndex> indexes = new LinkedHashMap<String, MIndex>();
private final Map<String, MIndex> indexes = new LinkedHashMap<String, MIndex>();
private final PendingDrops pendingDrops = new PendingDrops();
public ModelContainer() {
}
/**
* Adjust the FK references on all the draft tables.
*/
public void adjustDraftReferences() {
Collection<MTable> tables = this.tables.values();
for (MTable table : tables) {
for (MTable table : this.tables.values()) {
if (table.isDraft()) {
table.adjustReferences(this);
}
@@ -82,14 +82,31 @@ public class ModelContainer {
/**
* Apply a migration with associated changeSets to the model.
*/
public void apply(Migration migration) {
public void apply(Migration migration, MigrationVersion version) {
List<ChangeSet> changeSets = migration.getChangeSet();
for (ChangeSet changeSet : changeSets) {
applyChangeSet(changeSet);
boolean pending = changeSet.getType() == ChangeSetType.PENDING_DROPS;
if (pending) {
// un-applied drop columns etc
pendingDrops.add(version, changeSet);
} else if (isDropsFor(changeSet)) {
pendingDrops.appliedDropsFor(changeSet);
}
if (!isDropsFor(changeSet)) {
applyChangeSet(changeSet);
}
}
}
/**
* Return true if the changeSet contains drops for a previous PENDING_DROPS changeSet.
*/
private boolean isDropsFor(ChangeSet changeSet) {
return changeSet.getDropsFor() != null;
}
/**
* Apply a changeSet to the model.
*/
@@ -245,4 +262,53 @@ public class ModelContainer {
indexes.put(indexName, new MIndex(indexName, tableName, columnNames));
}
/**
* Return the list of versions containing un-applied pending drops.
*/
public List<String> getPendingDrops() {
return pendingDrops.pendingDrops();
}
/**
* Return the migration for the pending drops for a given version.
*/
public Migration migrationForPendingDrop(String pendingVersion) {
return pendingDrops.migrationForVersion(pendingVersion);
}
/**
* Register the drop columns on history tables that have not been applied yet.
*/
public void registerPendingHistoryDropColumns(ModelContainer newModel) {
pendingDrops.registerPendingHistoryDropColumns(newModel);
}
/**
* Register any pending drop columns on history tables. These columns are now not in the current
* logical model but we still need to include them in the history views and triggers until they
* are actually dropped.
*/
public void registerPendingHistoryDropColumns(ChangeSet changeSet) {
for (Object change : changeSet.getChangeSetChildren()) {
if (change instanceof DropColumn) {
DropColumn dropColumn = (DropColumn) change;
if (Boolean.TRUE.equals(dropColumn.isWithHistory())) {
registerPendingDropColumn(dropColumn);
}
}
}
}
/**
* Register a drop column on a history tables that has not been applied yet.
*/
private void registerPendingDropColumn(DropColumn dropColumn) {
MTable table = getTable(dropColumn.getTableName());
if (table == null) {
throw new IllegalArgumentException("Table ["+dropColumn.getTableName()+"] not found?");
}
table.registerPendingDropColumn(dropColumn.getColumnName());
}
}
@@ -66,16 +66,14 @@ public class ModelDiff {
public Migration getMigration() {
Migration migration = new Migration();
ChangeSet applyChangeSet = getApplyChangeSet();
if (!applyChangeSet.getChangeSetChildren().isEmpty()) {
if (!applyChanges.isEmpty()) {
// add a non empty apply changeSet
migration.getChangeSet().add(applyChangeSet);
migration.getChangeSet().add(getApplyChangeSet());
}
ChangeSet dropChangeSet = getDropChangeSet();
if (!dropChangeSet.getChangeSetChildren().isEmpty()) {
if (!dropChanges.isEmpty()) {
// add a non empty drop changeSet
migration.getChangeSet().add(dropChangeSet);
migration.getChangeSet().add(getDropChangeSet());
}
return migration;
}
@@ -83,14 +81,14 @@ public class ModelDiff {
/**
* Return the list of 'apply' changes.
*/
public List<Object> getApplyChanges() {
List<Object> getApplyChanges() {
return applyChanges;
}
/**
* Return the list of 'drop' changes.
*/
public List<Object> getDropChanges() {
List<Object> getDropChanges() {
return dropChanges;
}
@@ -108,10 +106,10 @@ public class ModelDiff {
/**
* Return the 'drop' changeSet.
*/
public ChangeSet getDropChangeSet() {
ChangeSet getDropChangeSet() {
// put the changes into a ChangeSet
ChangeSet createChangeSet = new ChangeSet();
createChangeSet.setType(ChangeSetType.DROP);
createChangeSet.setType(ChangeSetType.PENDING_DROPS);
createChangeSet.getChangeSetChildren().addAll(dropChanges);
return createChangeSet;
}
@@ -156,6 +154,12 @@ public class ModelDiff {
}
}
// register un-applied ones from the previous migrations
baseModel.registerPendingHistoryDropColumns(newModel);
if (!dropChanges.isEmpty()) {
// register new ones created just now as part of this diff
newModel.registerPendingHistoryDropColumns(getDropChangeSet());
}
}
protected void addDropTable(MTable existingTable) {
@@ -0,0 +1,263 @@
package com.avaje.ebean.dbmigration.model;
import com.avaje.ebean.dbmigration.migration.ChangeSet;
import com.avaje.ebean.dbmigration.migration.ChangeSetType;
import com.avaje.ebean.dbmigration.migration.DropColumn;
import com.avaje.ebean.dbmigration.migration.DropTable;
import com.avaje.ebean.dbmigration.migration.Migration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
/**
* The migrations with pending un-applied drops.
*/
public class PendingDrops {
private final LinkedHashMap<String, Entry> map = new LinkedHashMap<String, Entry>();
/**
* Add a 'pending drops' changeSet for the given version.
*/
public void add(MigrationVersion version, ChangeSet changeSet) {
Entry entry = map.get(version.normalised());
if (entry == null) {
entry = new Entry(version);
map.put(version.normalised(), entry);
}
entry.add(changeSet);
}
/**
* Return the list of versions with pending drops.
*/
public List<String> pendingDrops() {
List<String> versions = new ArrayList<String>();
for (Entry value : map.values()) {
if (value.hasPendingDrops()) {
versions.add(value.version.asString());
}
}
return versions;
}
/**
* All the pending drops for this migration version have been applied so we need
* to remove the (unsuppressed) pending drops for this version.
*/
public boolean appliedDropsFor(ChangeSet changeSet) {
MigrationVersion version = MigrationVersion.parse(changeSet.getDropsFor());
Entry entry = map.get(version.normalised());
if (entry.removeDrops(changeSet)) {
// it had no suppressForever changeSets so remove completely
map.remove(version.normalised());
return true;
}
return false;
}
/**
* Return the migration for the pending drops from a version.
* <p>
* The value of version can be "next" to find the first un-applied pending drops.
* </p>
*/
public Migration migrationForVersion(String pendingVersion) {
Entry entry = getEntry(pendingVersion);
Migration migration = new Migration();
Iterator<ChangeSet> it = entry.list.iterator();
while (it.hasNext()) {
ChangeSet changeSet = it.next();
if (!isSuppressForever(changeSet)) {
it.remove();
changeSet.setType(ChangeSetType.APPLY);
changeSet.setDropsFor(entry.version.asString());
migration.getChangeSet().add(changeSet);
}
}
if (migration.getChangeSet().isEmpty()) {
throw new IllegalArgumentException("The remaining pendingDrops changeSets in migration ["+pendingVersion+"] are suppressDropsForever=true and can't be applied");
}
if (!entry.containsSuppressForever()) {
// we can remove it completely as it has no suppressForever changes
map.remove(entry.version.normalised());
}
return migration;
}
private Entry getEntry(String pendingVersion) {
if ("next".equalsIgnoreCase(pendingVersion)) {
Iterator<Entry> it = map.values().iterator();
if (it.hasNext()) {
return it.next();
}
} else {
Entry remove = map.get(MigrationVersion.parse(pendingVersion).normalised());
if (remove != null) {
return remove;
}
}
throw new IllegalArgumentException("No 'pendingDrops' changeSets for migration version [" + pendingVersion + "] found");
}
/**
* Register pending drop columns on history tables to the new model.
*/
public void registerPendingHistoryDropColumns(ModelContainer newModel) {
for (Entry entry : map.values()) {
for (ChangeSet changeSet : entry.list) {
newModel.registerPendingHistoryDropColumns(changeSet);
}
}
}
/**
* Return true if there is an Entry for the given version.
*/
boolean testContainsEntryFor(MigrationVersion version) {
return map.containsKey(version.normalised());
}
/**
* Return the Entry for the given version.
*/
Entry testGetEntryFor(MigrationVersion version) {
return map.get(version.normalised());
}
static class Entry {
final MigrationVersion version;
final List<ChangeSet> list = new ArrayList<ChangeSet>();
Entry(MigrationVersion version) {
this.version = version;
}
void add(ChangeSet changeSet) {
list.add(changeSet);
}
/**
* Return true if this contains suppressForever changeSets.
*/
boolean containsSuppressForever() {
for (ChangeSet changeSet : list) {
if (isSuppressForever(changeSet)) {
return true;
}
}
return false;
}
/**
* Return true if this contains drops that can be applied / migrated.
*/
boolean hasPendingDrops() {
for (ChangeSet changeSet : list) {
if (!isSuppressForever(changeSet)) {
return true;
}
}
return false;
}
/**
* Remove the drops that are not suppressForever and return true if that
* removed all the changeSets (and there are no suppressForever ones).
*/
boolean removeDrops(ChangeSet appliedDrops) {
Iterator<ChangeSet> iterator = list.iterator();
while (iterator.hasNext()) {
ChangeSet next = iterator.next();
if (!isSuppressForever(next)) {
removeMatchingChanges(next, appliedDrops);
if (next.getChangeSetChildren().isEmpty()) {
iterator.remove();
}
}
}
return list.isEmpty();
}
/**
* Remove the applied drops from the pending ones matching by table name and column name.
*/
private void removeMatchingChanges(ChangeSet pendingDrops, ChangeSet appliedDrops) {
List<Object> pending = pendingDrops.getChangeSetChildren();
Iterator<Object> iterator = pending.iterator();
while (iterator.hasNext()) {
Object pendingDrop = iterator.next();
if (pendingDrop instanceof DropColumn && dropColumnIn((DropColumn)pendingDrop, appliedDrops)) {
iterator.remove();
} else if (pendingDrop instanceof DropTable && dropTableIn((DropTable)pendingDrop, appliedDrops)) {
iterator.remove();
}
}
}
/**
* Return true if the pendingDrop is contained in the appliedDrops.
*/
private boolean dropTableIn(DropTable pendingDrop, ChangeSet appliedDrops) {
for (Object o : appliedDrops.getChangeSetChildren()) {
if (o instanceof DropTable && sameTable(pendingDrop, (DropTable)o)) {
return true;
}
}
return false;
}
/**
* Return true if the pendingDrop is contained in the appliedDrops.
*/
private boolean dropColumnIn(DropColumn pendingDrop, ChangeSet appliedDrops) {
for (Object o : appliedDrops.getChangeSetChildren()) {
if (o instanceof DropColumn && sameColumn(pendingDrop, (DropColumn) o)) {
return true;
}
}
return false;
}
/**
* Return true if the DropTable match by table name.
*/
private boolean sameTable(DropTable pendingDrop, DropTable o) {
return pendingDrop.getName().equals(o.getName());
}
/**
* Return true if the DropColumns match by table and column name.
*/
private boolean sameColumn(DropColumn pending, DropColumn o) {
return pending.getColumnName().equals(o.getColumnName())
&& pending.getTableName().equals(o.getTableName());
}
}
private static boolean isSuppressForever(ChangeSet next) {
return Boolean.TRUE.equals(next.isSuppressDropsForever());
}
}
@@ -3,9 +3,11 @@ 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.DdlBuffer;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
import com.avaje.ebean.dbmigration.migration.ChangeSet;
import com.avaje.ebean.dbmigration.migration.ChangeSetType;
import com.avaje.ebean.dbmigration.migration.Migration;
import java.io.File;
@@ -43,7 +45,7 @@ public class PlatformDdlWriter {
List<ChangeSet> changeSets = dbMigration.getChangeSet();
for (ChangeSet changeSet : changeSets) {
if (!changeSet.getChangeSetChildren().isEmpty()) {
if (isApply(changeSet)) {
handler.generate(write, changeSet);
}
}
@@ -52,6 +54,13 @@ public class PlatformDdlWriter {
writePlatformDdl(write, writePath, fullVersion);
}
/**
* Return true if the changeSet is APPLY and not empty.
*/
private boolean isApply(ChangeSet changeSet) {
return changeSet.getType() == ChangeSetType.APPLY && !changeSet.getChangeSetChildren().isEmpty();
}
/**
* Write the ddl files.
*/
@@ -65,26 +74,6 @@ public class PlatformDdlWriter {
} finally {
applyWriter.close();
}
if (!config.isSuppressRollback() && !write.isApplyRollbackEmpty()) {
FileWriter applyRollbackWriter = createWriter(resourcePath, fullVersion, config.getRollbackPath(), config.getRollbackSuffix());
try {
writeApplyRollbackDdl(applyRollbackWriter, write);
applyRollbackWriter.flush();
} finally {
applyRollbackWriter.close();
}
}
}
if (!write.isDropEmpty()) {
FileWriter dropWriter = createWriter(resourcePath, fullVersion, config.getDropPath(), config.getDropSuffix());
try {
writeDropDdl(dropWriter, write);
dropWriter.flush();
} finally {
dropWriter.close();
}
}
}
@@ -116,29 +105,19 @@ public class PlatformDdlWriter {
protected void writeApplyDdl(Writer writer, DdlWrite write) throws IOException {
// merge the apply buffers in the appropriate order
prependDropDependencies(writer, write.applyDropDependencies());
writer.append("-- apply changes\n");
writer.append(write.apply().getBuffer());
writer.append(write.applyForeignKeys().getBuffer());
writer.append(write.applyHistory().getBuffer());
}
/**
* Write the 'Rollback' DDL buffers to the writer.
*/
protected void writeApplyRollbackDdl(Writer writer, DdlWrite write) throws IOException {
// merge the rollback buffers in the appropriate order
writer.append(write.rollbackForeignKeys().getBuffer());
writer.append(write.rollback().getBuffer());
}
/**
* Write the 'Drop' DDL buffers to the writer.
*/
protected void writeDropDdl(Writer writer, DdlWrite write) throws IOException {
// merge the rollback buffers in the appropriate order
writer.append(write.dropHistory().getBuffer());
writer.append(write.drop().getBuffer());
private void prependDropDependencies(Writer writer, DdlBuffer buffer) throws IOException {
if (!buffer.isEmpty()) {
writer.append("-- drop dependencies\n");
writer.append(buffer.getBuffer());
writer.append("\n");
}
}
/**
@@ -43,6 +43,10 @@ public class ModelBuildContext {
model.adjustDraftReferences();
}
public String normaliseTable(String baseTable) {
return constraintNaming.normaliseTable(baseTable);
}
public String primaryKeyName(String tableName) {
return maxLength(constraintNaming.primaryKeyName(tableName), 0);
}
@@ -154,7 +158,7 @@ public class ModelBuildContext {
int fkCount = 0;
int ixCount = 0;
int uqCount = 0;
Collection<MColumn> cols = draftTable.getColumns().values();
Collection<MColumn> cols = draftTable.allColumns();
for (MColumn col: cols) {
if (col.getForeignKeyName() != null) {
// Note that we adjust the 'references' table later in a second pass
@@ -64,8 +64,7 @@ public class ModelBuildIntersectionTable {
private void buildFkConstraints(BeanDescriptor<?> desc, TableJoinColumn[] columns, boolean direction) {
String tableName = intersectionTableJoin.getTable();
String baseTable = desc.getBaseTable();
String baseTable = ctx.normaliseTable(desc.getBaseTable());
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, ++countForeignKey);
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, countForeignKey);
@@ -84,7 +84,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
table.setPkName(determinePrimaryKeyName());
// check if indexes on foreign keys should be suppressed
for (MColumn column : table.getColumns().values()) {
for (MColumn column : table.allColumns()) {
if (hasValue(column.getForeignKeyIndex())) {
if (indexSet.contains(column.getName())) {
// suppress index on foreign key as there is already
@@ -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);
}
@@ -12,7 +12,7 @@ public class HashQueryPlanBuilder {
private String rawSql;
public HashQueryPlanBuilder() {
this.planHash = 31;
this.planHash = 92821;
}
public String toString() {
@@ -23,7 +23,7 @@ public class HashQueryPlanBuilder {
* Add a class to the hash calculation.
*/
public HashQueryPlanBuilder add(Class<?> cls) {
planHash = planHash * 31 + cls.getName().hashCode();
planHash = planHash * 92821 + cls.getName().hashCode();
return this;
}
@@ -31,7 +31,7 @@ public class HashQueryPlanBuilder {
* Add an object to the hash calculation.
*/
public HashQueryPlanBuilder add(Object object) {
planHash = planHash * 31 + (object == null ? 0 : object.hashCode());
planHash = planHash * 92821 + (object == null ? 0 : object.hashCode());
return this;
}
@@ -39,7 +39,7 @@ public class HashQueryPlanBuilder {
* Add an integer to the hash calculation.
*/
public HashQueryPlanBuilder add(int hashValue) {
planHash = planHash * 31 + (hashValue);
planHash = planHash * 92821 + (hashValue);
return this;
}
@@ -47,7 +47,7 @@ public class HashQueryPlanBuilder {
* Add a boolean to the hash calculation.
*/
public HashQueryPlanBuilder add(boolean booleanValue) {
planHash = planHash * 31 + (booleanValue ? 31 : 0);
planHash = planHash * 92821 + (booleanValue ? 92821 : 0);
return this;
}
@@ -1,18 +1,27 @@
package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Request for loading ManyToOne and OneToOne relationships.
*/
public class LoadBeanRequest extends LoadRequest {
private static final Logger logger = LoggerFactory.getLogger(LoadBeanRequest.class);
private final List<EntityBeanIntercept> batch;
private final LoadBeanBuffer LoadBuffer;
private final LoadBeanBuffer loadBuffer;
private final String lazyLoadProperty;
@@ -32,20 +41,28 @@ public class LoadBeanRequest extends LoadRequest {
this(LoadBuffer, parentRequest, false, null, false);
}
private LoadBeanRequest(LoadBeanBuffer LoadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy, String lazyLoadProperty, boolean loadCache) {
private LoadBeanRequest(LoadBeanBuffer loadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy,
String lazyLoadProperty, boolean loadCache) {
super(parentRequest, lazy);
this.LoadBuffer = LoadBuffer;
this.batch = LoadBuffer.getBatch();
this.loadBuffer = loadBuffer;
this.batch = loadBuffer.getBatch();
this.lazyLoadProperty = lazyLoadProperty;
this.loadCache = loadCache;
}
@Override
public Class<?> getBeanType() {
return loadBuffer.getBeanDescriptor().getBeanType();
}
public boolean isLoadCache() {
return loadCache;
}
public String getDescription() {
return "path:" + LoadBuffer.getFullPath() + " batch:" + batch.size();
return "path:" + loadBuffer.getFullPath() + " batch:" + batch.size();
}
/**
@@ -59,7 +76,7 @@ public class LoadBeanRequest extends LoadRequest {
* Return the load context.
*/
public LoadBeanBuffer getLoadContext() {
return LoadBuffer;
return loadBuffer;
}
/**
@@ -72,4 +89,90 @@ public class LoadBeanRequest extends LoadRequest {
public int getBatchSize() {
return getLoadContext().getBatchSize();
}
/**
* Return the list of Id values for the beans in the lazy load buffer.
*/
public List<Object> getIdList(int batchSize) {
List<Object> idList = new ArrayList<Object>(batchSize);
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
for (int i = 0; i < batch.size(); i++) {
EntityBeanIntercept ebi = batch.get(i);
EntityBean bean = ebi.getOwner();
idList.add(desc.getId(bean));
}
if (!idList.isEmpty()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
// so we get the same query (for Ebean and the db)
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
// just add the first Id again
idList.add(firstId);
}
}
}
return idList;
}
/**
* Configure the query for lazy loading execution.
*/
public void configureQuery(SpiQuery<?> query, List<Object> idList) {
query.setMode(SpiQuery.Mode.LAZYLOAD_BEAN);
query.setPersistenceContext(loadBuffer.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, getDescription());
if (isLazy()) {
// cascade the batch size (if set) for further lazy loading
query.setLazyLoadBatchSize(getBatchSize());
}
loadBuffer.configureQuery(query, lazyLoadProperty);
if (idList.size() == 1) {
query.where().idEq(idList.get(0));
} else {
query.where().idIn(idList);
}
}
/**
* Load the beans into the L2 cache if that is requested and check for load failures due to deletes.
*/
public void postLoad(List<?> list) {
Set<Object> loadedIds = new HashSet<Object>();
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
// collect Ids and maybe load bean cache
for (int i = 0; i < list.size(); i++) {
EntityBean loadedBean = (EntityBean) list.get(i);
loadedIds.add(desc.getId(loadedBean));
if (isLoadCache()) {
desc.cacheBeanPutData(loadedBean);
}
}
if (lazyLoadProperty != null) {
for (int i = 0; i < batch.size(); i++) {
// check if the underlying row in DB was deleted. Mark the bean as 'failed' if
// necessary but allow processing to continue until it is accessed by client code
EntityBeanIntercept ebi = batch.get(i);
Object id = desc.getId(ebi.getOwner());
if (!loadedIds.contains(id)) {
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
ebi.setLazyLoadFailure(id);
}
}
}
}
}
@@ -1,15 +1,25 @@
package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Request for loading Associated Many Beans.
*/
public class LoadManyRequest extends LoadRequest {
private static final Logger logger = LoggerFactory.getLogger(LoadManyRequest.class);
private final List<BeanCollection<?>> batch;
private final LoadManyBuffer loadContext;
@@ -40,6 +50,11 @@ public class LoadManyRequest extends LoadRequest {
this.loadCache = loadCache;
}
@Override
public Class<?> getBeanType() {
return loadContext.getBeanDescriptor().getBeanType();
}
public String getDescription() {
return "path:" + loadContext.getFullPath() + " size:" + batch.size();
}
@@ -83,4 +98,98 @@ public class LoadManyRequest extends LoadRequest {
public int getBatchSize() {
return loadContext.getBatchSize();
}
private List<Object> getParentIdList(int batchSize) {
ArrayList<Object> idList = new ArrayList<Object>(batchSize);
BeanPropertyAssocMany<?> many = getMany();
for (int i = 0; i < batch.size(); i++) {
BeanCollection<?> bc = batch.get(i);
idList.add(many.getParentId(bc.getOwnerBean()));
}
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
return idList;
}
private BeanPropertyAssocMany<?> getMany() {
return loadContext.getBeanProperty();
}
public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {
BeanPropertyAssocMany<?> many = getMany();
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
String orderBy = many.getLazyFetchOrderBy();
if (orderBy != null) {
query.orderBy(orderBy);
}
String extraWhere = many.getExtraWhere();
if (extraWhere != null) {
// replace special ${ta} placeholder with the base table alias
// which is always t0 and add the extra where clause
String ew = StringHelper.replaceString(extraWhere, "${ta}", "t0");
query.where().raw(ew);
}
query.setLazyLoadForParents(many);
List<Object> idList = getParentIdList(batchSize);
many.addWhereParentIdIn(query, idList);
query.setPersistenceContext(loadContext.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, getDescription());
if (isLazy()) {
// cascade the batch size (if set) for further lazy loading
query.setLazyLoadBatchSize(getBatchSize());
}
// potentially changes the joins and selected properties
loadContext.configureQuery(query);
if (isOnlyIds()) {
// override to just select the Id values
query.select(many.getTargetIdProperty());
}
return query;
}
/**
* After the query execution check for empty collections and load L2 cache if desired.
*/
public void postLoad() {
BeanDescriptor<?> desc = loadContext.getBeanDescriptor();
BeanPropertyAssocMany<?> many = getMany();
// check for BeanCollection's that where never processed
// in the +query or +lazy load due to no rows (predicates)
for (int i = 0; i < batch.size(); i++) {
BeanCollection<?> bc = batch.get(i);
if (bc.checkEmptyLazyLoad()) {
if (logger.isDebugEnabled()) {
EntityBean ownerBean = bc.getOwnerBean();
Object parentId = desc.getId(ownerBean);
logger.debug("BeanCollection after lazy load was empty. type:" + ownerBean.getClass().getName() + " id:" + parentId + " owner:" + ownerBean);
}
} else if (isLoadCache()) {
Object parentId = desc.getId(bc.getOwnerBean());
desc.cacheManyPropPut(many, bc, parentId);
}
}
}
}
@@ -21,6 +21,11 @@ public abstract class LoadRequest {
this.lazy = lazy;
}
/**
* Return the associated bean type for this load request.
*/
public abstract Class<?> getBeanType();
/**
* Log the just executed secondary query with the 'root' query if 'logSecondaryQuery' is set to
* true. This is for testing purposes to confirm the secondary query executes etc.
@@ -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.
*/
@@ -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());
}
}
}
@@ -0,0 +1,162 @@
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 void process() {
for (AutoTuneCollection.Entry entry : profiling.getEntries()) {
addToDocument(entry);
}
}
/**
* 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.
*/
@@ -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,9 +21,13 @@ 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;
private final boolean skipGarbageCollectionOnShutdown;
private final boolean skipProfileReportingOnShutdown;
private final BaseQueryTuner queryTuner;
@@ -53,18 +43,25 @@ 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);
this.skipCollectionOnShutdown = config.isSkipCollectionOnShutdown();
this.skipGarbageCollectionOnShutdown = config.isSkipGarbageCollectionOnShutdown();
this.skipProfileReportingOnShutdown = config.isSkipProfileReportingOnShutdown();
this.defaultGarbageCollectionWait = (long) config.getGarbageCollectionWait();
}
@@ -75,169 +72,122 @@ 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);
event.process();
if (event.isEmpty()) {
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);
event.process();
if (event.isEmpty()) {
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);
}
}
/**
@@ -250,12 +200,29 @@ public class DefaultAutoTuneService implements AutoTuneService {
*/
@Override
public void shutdown() {
if (profiling && !skipCollectionOnShutdown) {
collectProfiling(-1);
saveProfiling(false);
if (profiling) {
if (!skipGarbageCollectionOnShutdown && !skipProfileReportingOnShutdown) {
// trigger GC to update profiling information on recently executed queries
collectProfiling(-1);
}
if (!skipProfileReportingOnShutdown) {
saveProfilingOnShutdown(false);
}
}
}
/**
* Output the profiling.
* <p>
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
* to the existing tuning.
* </p>
*/
public void reportProfiling() {
saveProfilingOnShutdown(false);
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
@@ -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);
@@ -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;
}
/**
@@ -6,23 +6,20 @@ import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.LoadBeanBuffer;
import com.avaje.ebeaninternal.api.LoadBeanRequest;
import com.avaje.ebeaninternal.api.LoadManyBuffer;
import com.avaje.ebeaninternal.api.LoadManyRequest;
import com.avaje.ebeaninternal.api.LoadRequest;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;
/**
@@ -86,89 +83,11 @@ public class DefaultBeanLoader {
int batchSize = getBatchSize(batch.size());
LoadManyBuffer ctx = loadRequest.getLoadContext();
BeanPropertyAssocMany<?> many = ctx.getBeanProperty();
SpiQuery<?> query = loadRequest.createQuery(server, batchSize);
PersistenceContext pc = ctx.getPersistenceContext();
executeQuery(loadRequest, query);
ArrayList<Object> idList = new ArrayList<Object>(batchSize);
for (int i = 0; i < batch.size(); i++) {
BeanCollection<?> bc = batch.get(i);
EntityBean ownerBean = bc.getOwnerBean();
Object id = many.getParentId(ownerBean);
idList.add(id);
}
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
BeanDescriptor<?> desc = ctx.getBeanDescriptor();
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
String orderBy = many.getLazyFetchOrderBy();
if (orderBy != null) {
query.orderBy(orderBy);
}
String extraWhere = many.getExtraWhere();
if (extraWhere != null) {
// replace special ${ta} placeholder with the base table alias
// which is always t0 and add the extra where clause
String ew = StringHelper.replaceString(extraWhere, "${ta}", "t0");
query.where().raw(ew);
}
query.setLazyLoadForParents(many);
many.addWhereParentIdIn(query, idList);
query.setPersistenceContext(pc);
String mode = loadRequest.isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, loadRequest.getDescription());
if (loadRequest.isLazy()) {
// cascade the batch size (if set) for further lazy loading
query.setLazyLoadBatchSize(loadRequest.getBatchSize());
}
// potentially changes the joins and selected properties
ctx.configureQuery(query);
if (loadRequest.isOnlyIds()) {
// override to just select the Id values
query.select(many.getTargetIdProperty());
}
if (onIterateUseExtraTxn && loadRequest.isParentFindIterate()) {
// MySql - we need a different transaction to execute the secondary query
SpiTransaction extraTxn = server.createQueryTransaction();
try {
server.findList(query, extraTxn);
} finally {
extraTxn.end();
}
} else {
server.findList(query, loadRequest.getTransaction());
}
// check for BeanCollection's that where never processed
// in the +query or +lazy load due to no rows (predicates)
for (int i = 0; i < batch.size(); i++) {
BeanCollection<?> bc = batch.get(i);
if (bc.checkEmptyLazyLoad()) {
if (logger.isDebugEnabled()) {
logger.debug("BeanCollection after load was empty. Owner:" + batch.get(i).getOwnerBean());
}
} else if (loadRequest.isLoadCache()) {
Object parentId = desc.getId(bc.getOwnerBean());
desc.cacheManyPropPut(many, bc, parentId);
}
}
loadRequest.postLoad();
// log the query (for testing secondary queries)
loadRequest.logSecondaryQuery(query);
@@ -274,96 +193,43 @@ public class DefaultBeanLoader {
public void loadBean(LoadBeanRequest loadRequest) {
List<EntityBeanIntercept> batch = loadRequest.getBatch();
if (batch.isEmpty()) {
throw new RuntimeException("Nothing in batch?");
}
int batchSize = getBatchSize(batch.size());
LoadBeanBuffer ctx = loadRequest.getLoadContext();
BeanDescriptor<?> desc = ctx.getBeanDescriptor();
Class<?> beanType = desc.getBeanType();
EntityBeanIntercept[] ebis = batch.toArray(new EntityBeanIntercept[batch.size()]);
ArrayList<Object> idList = new ArrayList<Object>(batchSize);
for (int i = 0; i < batch.size(); i++) {
EntityBeanIntercept ebi = batch.get(i);
EntityBean bean = ebi.getOwner();
Object id = desc.getId(bean);
idList.add(id);
}
List<Object> idList = loadRequest.getIdList(batchSize);
if (idList.isEmpty()) {
// everything was loaded from cache
return;
}
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
// so we get the same query (for Ebean and the db)
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
// just add the first Id again
idList.add(firstId);
}
}
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(loadRequest.getBeanType());
loadRequest.configureQuery(query, idList);
PersistenceContext persistenceContext = ctx.getPersistenceContext();
List<?> list = executeQuery(loadRequest, query);
loadRequest.postLoad(list);
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(beanType);
// log the query (for testing secondary queries)
loadRequest.logSecondaryQuery(query);
}
query.setMode(Mode.LAZYLOAD_BEAN);
query.setPersistenceContext(persistenceContext);
String mode = loadRequest.isLazy() ? "+lazy" : "+query";
query.setLoadDescription(mode, loadRequest.getDescription());
if (loadRequest.isLazy()) {
// cascade the batch size (if set) for further lazy loading
query.setLazyLoadBatchSize(loadRequest.getBatchSize());
}
ctx.configureQuery(query, loadRequest.getLazyLoadProperty());
// make sure the query doesn't use the cache
// query.setUseCache(false);
if (idList.size() == 1) {
query.where().idEq(idList.get(0));
} else {
query.where().idIn(idList);
}
List<?> list;
/**
* Execute the lazy load query taking into account MySql transaction oddness.
*/
private List<?> executeQuery(LoadRequest loadRequest, SpiQuery<?> query) {
if (onIterateUseExtraTxn && loadRequest.isParentFindIterate()) {
// MySql - we need a different transaction to execute the secondary query
SpiTransaction extraTxn = server.createQueryTransaction();
try {
list = server.findList(query, extraTxn);
return server.findList(query, extraTxn);
} finally {
extraTxn.end();
}
} else {
list = server.findList(query, loadRequest.getTransaction());
return server.findList(query, loadRequest.getTransaction());
}
if (loadRequest.isLoadCache()) {
for (int i = 0; i < list.size(); i++) {
desc.cacheBeanPutData((EntityBean) list.get(i));
}
}
for (int i = 0; i < ebis.length; i++) {
// Check if the underlying row in DB was deleted. Mark this bean as 'failed' if
// necessary but allow processing to continue until it is accessed by client code
ebis[i].checkLazyLoadFailure();
}
// log the query (for testing secondary queries)
loadRequest.logSecondaryQuery(query);
}
public void refresh(EntityBean bean) {
@@ -142,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;
@@ -235,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());
@@ -261,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);
@@ -289,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);
}
@@ -337,10 +329,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return expressionFactory;
}
public DdlGenerator getDdlGenerator() {
return ddlGenerator;
}
@Override
public AutoTune getAutoTune() {
return autoTuneService;
@@ -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;
}
@@ -42,7 +42,7 @@ public interface BeanCollectionHelp<T> {
/**
* Add a bean to the List Set or Map.
*/
void add(BeanCollection<?> collection, EntityBean bean);
void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck);
/**
* Create a lazy loading proxy for a List Set or Map.
@@ -2316,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());
@@ -46,8 +46,12 @@ public final class BeanListHelp<T> implements BeanCollectionHelp<T> {
* Internal add bypassing any modify listening.
*/
@Override
public void add(BeanCollection<?> collection, EntityBean bean) {
collection.internalAdd(bean);
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
@@ -108,13 +108,14 @@ public final class BeanMapHelp<T> implements BeanCollectionHelp<T> {
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean) {
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (bean == null) {
((BeanMap<?, ?>) collection).internalPutNull();
} else {
Object keyValue = beanProperty.getValueIntercept(bean);
((BeanMap<?, ?>) collection).internalPut(keyValue, bean);
BeanMap<?, ?> map = ((BeanMap<?, ?>) collection);
map.internalPutWithCheck(keyValue, bean);
}
}
@@ -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();
@@ -182,13 +182,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
/**
* Add the bean to the appropriate collection on the parent bean.
*/
public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean) {
public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean, boolean withCheck) {
BeanCollection<?> bc = (BeanCollection<?>) super.getValue(parentBean);
if (bc == null) {
bc = help.createEmpty(parentBean);
setValue(parentBean, bc);
}
help.add(bc, detailBean);
help.add(bc, detailBean, withCheck);
}
/**
@@ -416,7 +416,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
public void add(BeanCollection<?> collection, EntityBean bean) {
help.add(collection, bean);
help.add(collection, bean, false);
}
/**
@@ -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;
@@ -101,14 +101,23 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
// no imported or exported information
} else if (!oneToOneExported) {
importedId = createImportedId(this, targetDescriptor, tableJoin);
if (importedId.isScalar()) {
// limit JoinColumn mapping to the @Id / primary key
TableJoinColumn[] columns = tableJoin.columns();
String foreignJoinColumn = columns[0].getForeignDbColumn();
String foreignIdColumn = targetDescriptor.getIdProperty().getDbColumn();
if (!foreignJoinColumn.equalsIgnoreCase(foreignIdColumn)) {
throw new PersistenceException("Mapping limitation - @JoinColumn on " + getFullBeanName() + " needs to map to a primary key as per Issue #529 "
+ " - joining to " + foreignJoinColumn + " and not " + foreignIdColumn);
}
}
} else {
exportedProperties = createExported();
String delStmt = "delete from " + targetDescriptor.getBaseTable() + " where ";
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false);
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true);
}
}
}
@@ -61,8 +61,12 @@ public final class BeanSetHelp<T> implements BeanCollectionHelp<T> {
}
}
public void add(BeanCollection<?> collection, EntityBean bean) {
collection.internalAdd(bean);
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
@@ -1,13 +1,12 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Types;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Creates "Counter" GeneratedProperty for various types of number.
* <p>
@@ -16,49 +15,49 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
*/
public class CounterFactory {
final GeneratedCounterInteger integerCounter = new GeneratedCounterInteger();
final GeneratedCounterInteger integerCounter = new GeneratedCounterInteger();
final GeneratedCounterLong longCounter = new GeneratedCounterLong();
final GeneratedCounterLong longCounter = new GeneratedCounterLong();
public void setCounter(DeployBeanProperty property) {
public void setCounter(DeployBeanProperty property) {
property.setGeneratedProperty(createCounter(property));
}
/**
* Create the GeneratedProperty based on the property type.
*/
private GeneratedProperty createCounter(DeployBeanProperty property) {
Class<?> propType = property.getPropertyType();
if (propType.equals(Integer.class) || propType.equals(int.class)) {
return integerCounter;
}
if (propType.equals(Long.class) || propType.equals(long.class)) {
return longCounter;
}
int type = getType(propType);
return new GeneratedCounter(type);
}
private int getType(Class<?> propType){
if (propType.equals(Short.class) || propType.equals(short.class)){
return Types.TINYINT;
}
if (propType.equals(BigDecimal.class)){
return Types.DECIMAL;
}
if (propType.equals(Double.class) || propType.equals(double.class)){
return Types.DOUBLE;
}
if (propType.equals(Float.class) || propType.equals(float.class)){
return Types.REAL;
}
if (propType.equals(BigInteger.class)){
return Types.BIGINT;
}
String msg = "Can not support Counter for type "+propType.getName();
throw new PersistenceException(msg);
}
property.setGeneratedProperty(createCounter(property));
}
/**
* Create the GeneratedProperty based on the property type.
*/
private GeneratedProperty createCounter(DeployBeanProperty property) {
Class<?> propType = property.getPropertyType();
if (propType.equals(Integer.class) || propType.equals(int.class)) {
return integerCounter;
}
if (propType.equals(Long.class) || propType.equals(long.class)) {
return longCounter;
}
int type = getType(propType);
return new GeneratedCounter(type);
}
private int getType(Class<?> propType) {
if (propType.equals(Short.class) || propType.equals(short.class)) {
return Types.TINYINT;
}
if (propType.equals(BigDecimal.class)) {
return Types.DECIMAL;
}
if (propType.equals(Double.class) || propType.equals(double.class)) {
return Types.DOUBLE;
}
if (propType.equals(Float.class) || propType.equals(float.class)) {
return Types.REAL;
}
if (propType.equals(BigInteger.class)) {
return Types.BIGINT;
}
String msg = "Can not support Counter for type " + propType.getName();
throw new PersistenceException(msg);
}
}
@@ -9,49 +9,51 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
*/
public class GeneratedCounter implements GeneratedProperty {
final int numberType;
final int numberType;
public GeneratedCounter(int numberType) {
this.numberType = numberType;
}
public GeneratedCounter(int numberType) {
this.numberType = numberType;
}
/**
* Always returns a 1.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return BasicTypeConverter.convert(1, numberType);
}
/**
* Always returns a 1.
*/
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return BasicTypeConverter.convert(1, numberType);
}
/**
* Increments the current value by one.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
Number currVal = (Number) prop.getValue(bean);
Integer nextVal = currVal.intValue() + 1;
return BasicTypeConverter.convert(nextVal, numberType);
}
/**
* Increments the current value by one.
*/
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
Number currVal = (Number) prop.getValue(bean);
Integer nextVal = currVal.intValue() + 1;
return BasicTypeConverter.convert(nextVal, numberType);
}
/**
* Include this in every update.
*/
public boolean includeInUpdate() {
return true;
}
/**
* Include this in every update.
*/
public boolean includeInUpdate() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
/**
* Include this in every insert setting initial counter value to 1.
*/
public boolean includeInInsert() {
return true;
}
/**
* Include this in every insert setting initial counter value to 1.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -8,46 +8,48 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
*/
public class GeneratedCounterInteger implements GeneratedProperty {
public GeneratedCounterInteger() {
public GeneratedCounterInteger() {
}
}
/**
* Always returns a 1.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return 1;
}
/**
* Always returns a 1.
*/
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return 1;
}
/**
* Increments the current value by one.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
Integer i = (Integer) prop.getValue(bean);
return i + 1;
}
/**
* Increments the current value by one.
*/
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
Integer i = (Integer) prop.getValue(bean);
return i + 1;
}
/**
* Include this in every update.
*/
public boolean includeInUpdate() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
/**
* Include this in every update.
*/
public boolean includeInUpdate() {
return true;
}
/**
* Include this in every insert setting initial counter value to 1.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Include this in every insert setting initial counter value to 1.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -8,46 +8,48 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
*/
public class GeneratedCounterLong implements GeneratedProperty {
public GeneratedCounterLong() {
public GeneratedCounterLong() {
}
}
/**
* Always returns a 1.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return (long) 1;
}
/**
* Always returns a 1.
*/
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return (long) 1;
}
/**
* Increments the current value by one.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
Long i = (Long) prop.getValue(bean);
return i + 1;
}
/**
* Increments the current value by one.
*/
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
Long i = (Long) prop.getValue(bean);
return i + 1;
}
/**
* Include this in every update.
*/
public boolean includeInUpdate() {
return true;
}
/**
* Include this in every update.
*/
public boolean includeInUpdate() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
/**
* Include this in every insert setting initial counter value to 1.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Include this in every insert setting initial counter value to 1.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -1,50 +1,52 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.util.Date;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.util.Date;
/**
* Used to generate a (java.util.Date) timestamp when a bean is inserted.
*/
public class GeneratedInsertDate implements GeneratedProperty {
/**
* Return the current time as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new Date(System.currentTimeMillis());
}
/**
* Return the current time as a Timestamp.
*/
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new Date(now);
}
/**
* Just returns the beans original insert timestamp value.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return prop.getValue(bean);
}
/**
* Just returns the beans original insert timestamp value.
*/
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return prop.getValue(bean);
}
/**
* Return false.
*/
public boolean includeInUpdate() {
return false;
}
/**
* Return false.
*/
public boolean includeInUpdate() {
return false;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
/**
* Return true.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Return true.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -3,10 +3,6 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
/**
* Support java.time types as GeneratedProperty.
*/
@@ -35,7 +31,7 @@ public class GeneratedInsertJavaTime {
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return prop.getValue(bean);
}
}
@@ -46,8 +42,8 @@ public class GeneratedInsertJavaTime {
public static class LocalDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return LocalDateTime.now();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toLocalDateTime(now);
}
}
@@ -57,8 +53,8 @@ public class GeneratedInsertJavaTime {
public static class OffsetDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return OffsetDateTime.now();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toOffsetDateTime(now);
}
}
@@ -69,8 +65,8 @@ public class GeneratedInsertJavaTime {
public static class ZonedDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return ZonedDateTime.now();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toZonedDateTime(now);
}
}
@@ -2,9 +2,8 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import org.joda.time.LocalDateTime;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
/**
* Support joda time types as GeneratedProperty.
@@ -34,7 +33,7 @@ public class GeneratedInsertJodaTime {
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return prop.getValue(bean);
}
}
@@ -45,8 +44,8 @@ public class GeneratedInsertJodaTime {
public static class LocalDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new LocalDateTime();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new LocalDateTime(now);
}
}
@@ -56,8 +55,8 @@ public class GeneratedInsertJodaTime {
public static class DateTimeDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new DateTime();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new DateTime(now);
}
}
@@ -8,41 +8,43 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
*/
public class GeneratedInsertLong implements GeneratedProperty {
/**
* Return the current time as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return System.currentTimeMillis();
}
/**
* Return the current time as a Timestamp.
*/
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return now;
}
/**
* Just returns the beans original insert timestamp value.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return prop.getValue(bean);
}
/**
* Just returns the beans original insert timestamp value.
*/
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return prop.getValue(bean);
}
/**
* Return false.
*/
public boolean includeInUpdate() {
return false;
}
/**
* Return false.
*/
public boolean includeInUpdate() {
return false;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
/**
* Return true.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Return true.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -1,50 +1,52 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.sql.Timestamp;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.sql.Timestamp;
/**
* Used to generate a timestamp when a bean is inserted.
*/
public class GeneratedInsertTimestamp implements GeneratedProperty, GeneratedWhenCreated {
/**
* Return the current time as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new Timestamp(System.currentTimeMillis());
}
/**
* Return the current time as a Timestamp.
*/
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new Timestamp(now);
}
/**
* Just returns the beans original insert timestamp value.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return prop.getValue(bean);
}
/**
* Just returns the beans original insert timestamp value.
*/
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return prop.getValue(bean);
}
/**
* Return false.
*/
public boolean includeInUpdate() {
return false;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
/**
* Return false.
*/
public boolean includeInUpdate() {
return false;
}
/**
* Return true.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return false;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Return true.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -9,39 +9,39 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
*/
public interface GeneratedProperty {
/**
* Get the generated insert value for a specific property of a bean.
*/
Object getInsertValue(BeanProperty prop, EntityBean bean);
/**
* Get the generated insert value for a specific property of a bean.
*/
Object getInsertValue(BeanProperty prop, EntityBean bean, long now);
/**
* Get the generated update value for a specific property of a bean.
*/
Object getUpdateValue(BeanProperty prop, EntityBean bean);
/**
* Get the generated update value for a specific property of a bean.
*/
Object getUpdateValue(BeanProperty prop, EntityBean bean, long now);
/**
* Return true if this should always be includes in an update statement.
* <p>
* Used to include GeneratedUpdateTimestamp in dynamic table updates.
* </p>
*/
boolean includeInUpdate();
/**
* Return true if the property should be included in an update even if
* it is not loaded (ie. Last Updated Timestamp).
*/
boolean includeInAllUpdates();
/**
* Return true if this should always be includes in an update statement.
* <p>
* Used to include GeneratedUpdateTimestamp in dynamic table updates.
* </p>
*/
boolean includeInUpdate();
/**
* Return true if this should be included in insert statements.
*/
boolean includeInInsert();
/**
* Return true if the property should be included in an update even if
* it is not loaded (ie. Last Updated Timestamp).
*/
boolean includeInAllUpdates();
/**
* Return true if the GeneratedProperty implies the DDL to create the DB
* column should have a not null constraint.
*/
boolean isDDLNotNullable();
/**
* Return true if this should be included in insert statements.
*/
boolean includeInInsert();
/**
* Return true if the GeneratedProperty implies the DDL to create the DB
* column should have a not null constraint.
*/
boolean isDDLNotNullable();
}
@@ -1,19 +1,19 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.math.BigDecimal;
import java.util.HashSet;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebean.config.CurrentUserProvider;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import java.math.BigDecimal;
import java.util.HashSet;
/**
* Default implementation of GeneratedPropertyFactory.
*/
public class GeneratedPropertyFactory {
private final CounterFactory counterFactory = new CounterFactory();
private final CounterFactory counterFactory = new CounterFactory();
private final InsertTimestampFactory insertFactory;
@@ -34,7 +34,7 @@ public class GeneratedPropertyFactory {
this.updateFactory = new UpdateTimestampFactory(classLoadConfig);
CurrentUserProvider currentUserProvider = serverConfig.getCurrentUserProvider();
if (currentUserProvider != null) {
if (currentUserProvider != null) {
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
} else {
@@ -42,47 +42,47 @@ public class GeneratedPropertyFactory {
generatedWhoModified = null;
}
numberTypes.add(Integer.class.getName());
numberTypes.add(int.class.getName());
numberTypes.add(Long.class.getName());
numberTypes.add(long.class.getName());
numberTypes.add(Short.class.getName());
numberTypes.add(short.class.getName());
numberTypes.add(Double.class.getName());
numberTypes.add(double.class.getName());
numberTypes.add(BigDecimal.class.getName());
}
numberTypes.add(Integer.class.getName());
numberTypes.add(int.class.getName());
numberTypes.add(Long.class.getName());
numberTypes.add(long.class.getName());
numberTypes.add(Short.class.getName());
numberTypes.add(short.class.getName());
numberTypes.add(Double.class.getName());
numberTypes.add(double.class.getName());
numberTypes.add(BigDecimal.class.getName());
}
public ClassLoadConfig getClassLoadConfig() {
return classLoadConfig;
}
private boolean isNumberType(String typeClassName) {
return numberTypes.contains(typeClassName);
}
public void setVersion(DeployBeanProperty property) {
if (isNumberType(property.getPropertyType().getName())) {
setCounter(property);
} else {
setUpdateTimestamp(property);
}
}
public void setCounter(DeployBeanProperty property) {
counterFactory.setCounter(property);
}
return numberTypes.contains(typeClassName);
}
public void setInsertTimestamp(DeployBeanProperty property) {
insertFactory.setInsertTimestamp(property);
}
public void setVersion(DeployBeanProperty property) {
if (isNumberType(property.getPropertyType().getName())) {
setCounter(property);
} else {
setUpdateTimestamp(property);
}
}
public void setUpdateTimestamp(DeployBeanProperty property) {
updateFactory.setUpdateTimestamp(property);
}
public void setCounter(DeployBeanProperty property) {
counterFactory.setCounter(property);
}
public void setInsertTimestamp(DeployBeanProperty property) {
insertFactory.setInsertTimestamp(property);
}
public void setUpdateTimestamp(DeployBeanProperty property) {
updateFactory.setUpdateTimestamp(property);
}
public void setWhoCreated(DeployBeanProperty property) {
if (generatedWhoCreated == null) {
@@ -1,51 +1,51 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.util.Date;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.util.Date;
/**
* Generate a (java.util.Date) Timestamp whenever the bean is inserted or
* updated.
*/
public class GeneratedUpdateDate implements GeneratedProperty {
/**
* Return now as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new Date(System.currentTimeMillis());
}
/**
* Return now as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new Date(now);
}
/**
* Return now as a Timestamp.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return new Date(System.currentTimeMillis());
}
/**
* Return now as a Timestamp.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return new Date(now);
}
/**
* For dynamic table updates make sure this is included.
*/
public boolean includeInUpdate() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return true;
}
/**
* For dynamic table updates make sure this is included.
*/
public boolean includeInUpdate() {
return true;
}
/**
* Include this in every insert.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Include this in every insert.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -3,10 +3,6 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
/**
* Support java.time DateTime types as GeneratedProperty.
*/
@@ -41,13 +37,13 @@ public class GeneratedUpdateJavaTime {
public static class LocalDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return LocalDateTime.now();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toLocalDateTime(now);
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return LocalDateTime.now();
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toLocalDateTime(now);
}
}
@@ -57,13 +53,13 @@ public class GeneratedUpdateJavaTime {
public static class OffsetDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return OffsetDateTime.now();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toOffsetDateTime(now);
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return OffsetDateTime.now();
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toOffsetDateTime(now);
}
}
@@ -73,13 +69,13 @@ public class GeneratedUpdateJavaTime {
public static class ZonedDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return ZonedDateTime.now();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toZonedDateTime(now);
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return ZonedDateTime.now();
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return JavaTimeUtils.toZonedDateTime(now);
}
}
@@ -2,9 +2,8 @@ package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import org.joda.time.LocalDateTime;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
/**
* Support java.time DateTime types as GeneratedProperty.
@@ -40,13 +39,13 @@ public class GeneratedUpdateJodaTime {
public static class LocalDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new LocalDateTime();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new LocalDateTime(now);
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return new LocalDateTime();
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return new LocalDateTime(now);
}
}
@@ -56,13 +55,13 @@ public class GeneratedUpdateJodaTime {
public static class DateTimeDT extends Base {
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new DateTime();
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new DateTime(now);
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return new DateTime();
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return new DateTime(now);
}
}
@@ -8,41 +8,41 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
*/
public class GeneratedUpdateLong implements GeneratedProperty {
/**
* Return now as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return System.currentTimeMillis();
}
/**
* Return now as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return now;
}
/**
* Return now as a Timestamp.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return System.currentTimeMillis();
}
/**
* Return now as a Timestamp.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return now;
}
/**
* For dynamic table updates make sure this is included.
*/
public boolean includeInUpdate() {
return true;
}
/**
* For dynamic table updates make sure this is included.
*/
public boolean includeInUpdate() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return true;
}
/**
* Include this in every insert.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Include this in every insert.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -1,50 +1,50 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.sql.Timestamp;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import java.sql.Timestamp;
/**
* Generate a Timestamp whenever the bean is inserted or updated.
*/
public class GeneratedUpdateTimestamp implements GeneratedProperty, GeneratedWhenModified {
/**
* Return now as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
return new Timestamp(System.currentTimeMillis());
}
/**
* Return now as a Timestamp.
*/
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return new Timestamp(now);
}
/**
* Return now as a Timestamp.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
return new Timestamp(System.currentTimeMillis());
}
/**
* Return now as a Timestamp.
*/
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return new Timestamp(now);
}
/**
* For dynamic table updates make sure this is included.
*/
public boolean includeInUpdate() {
return true;
}
/**
* For dynamic table updates make sure this is included.
*/
public boolean includeInUpdate() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return true;
}
/**
* Include this in every insert.
*/
public boolean includeInInsert() {
return true;
}
@Override
public boolean includeInAllUpdates() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
/**
* Include this in every insert.
*/
public boolean includeInInsert() {
return true;
}
public boolean isDDLNotNullable() {
return true;
}
}
@@ -16,12 +16,12 @@ public class GeneratedWhoCreated implements GeneratedProperty {
}
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return currentUserProvider.currentUser();
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return null;
}
@@ -16,12 +16,12 @@ public class GeneratedWhoModified implements GeneratedProperty {
}
@Override
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
public Object getInsertValue(BeanProperty prop, EntityBean bean, long now) {
return currentUserProvider.currentUser();
}
@Override
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
public Object getUpdateValue(BeanProperty prop, EntityBean bean, long now) {
return currentUserProvider.currentUser();
}
@@ -1,5 +1,9 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
@@ -7,17 +11,12 @@ import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Helper for creating Insert timestamp GeneratedProperty objects.
*/
public class InsertTimestampFactory {
final GeneratedInsertLong longTime = new GeneratedInsertLong();
final GeneratedInsertLong longTime = new GeneratedInsertLong();
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
@@ -41,21 +40,21 @@ public class InsertTimestampFactory {
public void setInsertTimestamp(DeployBeanProperty property) {
property.setGeneratedProperty(createInsertTimestamp(property));
}
/**
* Create the insert GeneratedProperty depending on the property type.
*/
public GeneratedProperty createInsertTimestamp(DeployBeanProperty property) {
Class<?> propType = property.getPropertyType();
property.setGeneratedProperty(createInsertTimestamp(property));
}
/**
* Create the insert GeneratedProperty depending on the property type.
*/
public GeneratedProperty createInsertTimestamp(DeployBeanProperty property) {
Class<?> propType = property.getPropertyType();
GeneratedProperty generatedProperty = map.get(propType);
if (generatedProperty != null) {
return generatedProperty;
}
throw new PersistenceException("Generated Insert Timestamp not supported on "+propType.getName());
}
throw new PersistenceException("Generated Insert Timestamp not supported on " + propType.getName());
}
}
@@ -0,0 +1,34 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
/**
* Helper methods for Java time conversion.
*/
public class JavaTimeUtils {
/**
* Return the system millis time as a LocalDateTime.
*/
public static Object toLocalDateTime(long systemMillis) {
return new Timestamp(systemMillis).toLocalDateTime();
}
/**
* Return the system millis time as a OffsetDateTime.
*/
public static Object toOffsetDateTime(long systemMillis) {
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(systemMillis), ZoneId.systemDefault());
}
/**
* Return the system millis time as a ZonedDateTime.
*/
public static Object toZonedDateTime(long systemMillis) {
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(systemMillis), ZoneId.systemDefault());
}
}
@@ -1,5 +1,9 @@
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
@@ -7,17 +11,12 @@ import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebean.config.ClassLoadConfig;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Helper for creating Update timestamp GeneratedProperty objects.
*/
public class UpdateTimestampFactory {
final GeneratedUpdateLong longTime = new GeneratedUpdateLong();
final GeneratedUpdateLong longTime = new GeneratedUpdateLong();
final Map<Class<?>, GeneratedProperty> map = new HashMap<Class<?>, GeneratedProperty>();
@@ -38,15 +37,15 @@ public class UpdateTimestampFactory {
}
}
public void setUpdateTimestamp(DeployBeanProperty property) {
public void setUpdateTimestamp(DeployBeanProperty property) {
property.setGeneratedProperty(createUpdateTimestamp(property));
}
/**
* Create the update GeneratedProperty depending on the property type.
*/
protected GeneratedProperty createUpdateTimestamp(DeployBeanProperty property) {
property.setGeneratedProperty(createUpdateTimestamp(property));
}
/**
* Create the update GeneratedProperty depending on the property type.
*/
protected GeneratedProperty createUpdateTimestamp(DeployBeanProperty property) {
Class<?> propType = property.getPropertyType();
GeneratedProperty generatedProperty = map.get(propType);
@@ -54,7 +53,7 @@ public class UpdateTimestampFactory {
return generatedProperty;
}
throw new PersistenceException("Generated update Timestamp not supported on "+propType.getName());
}
throw new PersistenceException("Generated update Timestamp not supported on " + propType.getName());
}
}
@@ -1,7 +1,7 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Counter, Insert Timestamp, Update Timestamp support</TITLE>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Counter, Insert Timestamp, Update Timestamp support</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Counter, Insert Timestamp, Update Timestamp support
@@ -14,6 +14,8 @@ import com.avaje.ebeaninternal.api.SpiExpressionValidation;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.query.SplitName;
/**
* A "Query By Example" type of expression.
@@ -207,25 +209,41 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
OrmQueryRequest<?> r = (OrmQueryRequest<?>) request;
BeanDescriptor<?> beanDescriptor = r.getBeanDescriptor();
for (BeanProperty beanProperty : beanDescriptor.propertiesAll()) {
String propName = beanProperty.getName();
Object value = beanProperty.getValue(entity);
addExpressions(list, beanDescriptor, entity, null);
if (beanProperty.isScalar() && value != null) {
if (value instanceof String) {
list.add(new LikeExpression(propName, (String) value, caseInsensitive, likeType));
} else {
// exclude the zero values typically to weed out
// primitive int and long that initialise to 0
if (includeZeros || !isZero(value)) {
list.add(new SimpleExpression(propName, Op.EQ, value));
return list;
}
/**
* Add expressions to the list for all the non-null properties (and do this recursively).
*/
private void addExpressions(ArrayList<SpiExpression> list, BeanDescriptor<?> beanDescriptor, EntityBean bean, String prefix) {
for (BeanProperty beanProperty : beanDescriptor.propertiesAll()) {
if (!beanProperty.isTransient()) {
Object value = beanProperty.getValue(bean);
if (value != null) {
String propName = SplitName.add(prefix, beanProperty.getName());
if (beanProperty.isScalar()) {
if (value instanceof String) {
list.add(new LikeExpression(propName, (String) value, caseInsensitive, likeType));
} else {
// exclude the zero values typically to weed out
// primitive int and long that initialise to 0
if (includeZeros || !isZero(value)) {
list.add(new SimpleExpression(propName, Op.EQ, value));
}
}
} else if ((beanProperty instanceof BeanPropertyAssocOne) && (value instanceof EntityBean)) {
BeanPropertyAssocOne assocOne = (BeanPropertyAssocOne)beanProperty;
BeanDescriptor targetDescriptor = assocOne.getTargetDescriptor();
addExpressions(list, targetDescriptor, (EntityBean)value, propName);
}
}
}
}
return list;
}
/**
@@ -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);

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