mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
327
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
436988694d | ||
|
|
90f786babd | ||
|
|
0d8146d453 | ||
|
|
0eca613b41 | ||
|
|
dcde54fca4 | ||
|
|
49b8efbc30 | ||
|
|
fc70161909 | ||
|
|
44ba6d3bb2 | ||
|
|
d56e071f53 | ||
|
|
ce33c230c7 | ||
|
|
2da6a6960e | ||
|
|
62f88c9e4f | ||
|
|
42f65ead47 | ||
|
|
8e98e88e8a | ||
|
|
5de67e1939 | ||
|
|
3ddd7099b5 | ||
|
|
5d889790c2 | ||
|
|
636acc61e5 | ||
|
|
cb347ff5fa | ||
|
|
db9d39ef61 | ||
|
|
5216014d50 | ||
|
|
c40715733e | ||
|
|
9bcc8a77fe | ||
|
|
7be3a01823 | ||
|
|
0ca4f92b26 | ||
|
|
b99f002968 | ||
|
|
fa451078b7 | ||
|
|
49121a9c1f | ||
|
|
33f69902dc | ||
|
|
cf783fbbb7 | ||
|
|
cf62ea4c2f | ||
|
|
049bf76cc4 | ||
|
|
75a84af3f8 | ||
|
|
93680f5d19 | ||
|
|
faf7df1c69 | ||
|
|
34dcc98908 | ||
|
|
3c63b79324 | ||
|
|
2d93d592c1 | ||
|
|
57836af5b5 | ||
|
|
ba871d6684 | ||
|
|
583fc11945 | ||
|
|
3ba4660bbb | ||
|
|
37b86f2191 | ||
|
|
da8766533f | ||
|
|
ae31117b8f | ||
|
|
cf825f7ac4 | ||
|
|
a0609b1726 | ||
|
|
32bcd5e853 | ||
|
|
f082fce1ff | ||
|
|
7fc58a1842 | ||
|
|
f2ffbb2c5b | ||
|
|
5c0fb7c8cd | ||
|
|
04c9db1e42 | ||
|
|
8f7de950e2 | ||
|
|
53dd810b9a | ||
|
|
290ca6ba90 | ||
|
|
992f5797a7 | ||
|
|
798976fbd2 | ||
|
|
c315067ff0 | ||
|
|
a4f0e3936a | ||
|
|
fc96685ed0 | ||
|
|
5b10d06348 | ||
|
|
ca73eff519 | ||
|
|
646de50ae8 | ||
|
|
e2558a1e24 | ||
|
|
ad596a2043 | ||
|
|
0a60cd4e58 | ||
|
|
ec38137e5c | ||
|
|
bfcbd335c8 | ||
|
|
2cafdf709d | ||
|
|
ed2ea13deb | ||
|
|
8d3b4a0407 | ||
|
|
cb1fe297d6 | ||
|
|
6561c118ec | ||
|
|
eda7fe24c1 | ||
|
|
2353049c6f | ||
|
|
6c49ac0857 | ||
|
|
76033385dc | ||
|
|
b61cd30e29 | ||
|
|
18edaf877b | ||
|
|
f055a0d0cb | ||
|
|
a22574b0c9 | ||
|
|
3130f751f5 | ||
|
|
c83606c1da | ||
|
|
439858b259 | ||
|
|
3d3948aa8b | ||
|
|
27045baf44 | ||
|
|
5c6d6608ef | ||
|
|
2fcaab44c6 | ||
|
|
69bf1758bc | ||
|
|
af554410fd | ||
|
|
ae7827c483 | ||
|
|
4df2d4020e | ||
|
|
1aeffd31e0 | ||
|
|
5c59066ff8 | ||
|
|
a2bf8a0e46 | ||
|
|
3ef77ea410 | ||
|
|
fe2c2e5331 | ||
|
|
3d91a4f927 | ||
|
|
165d4aca92 | ||
|
|
076f8a03b9 | ||
|
|
ca944eb756 | ||
|
|
769e4bb488 | ||
|
|
0d456b2ad6 | ||
|
|
39e913cbe2 | ||
|
|
ae38672e02 | ||
|
|
8a8b36ec1e | ||
|
|
62aca1e77b | ||
|
|
f0b80068b6 | ||
|
|
5721fe3807 | ||
|
|
afffb88a29 | ||
|
|
4c3a125192 | ||
|
|
f8804f8d18 | ||
|
|
4848c24a0a | ||
|
|
4c3d1d9edd | ||
|
|
42b112c3d7 | ||
|
|
59996da07a | ||
|
|
32d4b83a86 | ||
|
|
ca824c5e0d | ||
|
|
b2f7664316 | ||
|
|
062f7a281f | ||
|
|
019ce4e614 | ||
|
|
6b490ed8b1 | ||
|
|
1bee6fcae5 | ||
|
|
097552b8d3 | ||
|
|
9fbfca5f73 | ||
|
|
9e70ba915f | ||
|
|
a1a5dcd035 | ||
|
|
d2b048b0b4 | ||
|
|
4272c17689 | ||
|
|
11723e3a32 | ||
|
|
237cdcdd3d | ||
|
|
e96bee74ae | ||
|
|
c8b57c3302 | ||
|
|
f65ea96b89 | ||
|
|
a1bd0b469a | ||
|
|
fd5563e3da | ||
|
|
f9156ede55 | ||
|
|
10d2d591db | ||
|
|
998d8ed6f2 | ||
|
|
a8da472872 | ||
|
|
a9cdf9736a | ||
|
|
c930a1dd6b | ||
|
|
67ce6ef58e | ||
|
|
c4f28efc45 | ||
|
|
afa4bc06d9 | ||
|
|
828c9ce128 | ||
|
|
6f37a0bd72 | ||
|
|
036086af02 | ||
|
|
f34b5e52e0 | ||
|
|
a70e289b00 | ||
|
|
4ad0e06aab | ||
|
|
86de7e845e | ||
|
|
38501ca372 | ||
|
|
4790c83fa1 | ||
|
|
b410c83492 | ||
|
|
b19145f16f | ||
|
|
c28cff2f40 | ||
|
|
0c8cf78ecc | ||
|
|
df112815ff | ||
|
|
85522ed8bc | ||
|
|
cf88f2375b | ||
|
|
24151f11fd | ||
|
|
424a83fa0a | ||
|
|
53dd91416c | ||
|
|
5a463c5071 | ||
|
|
bc3cf1348a | ||
|
|
f3b1024283 | ||
|
|
7ba35d7a62 | ||
|
|
990ca2980b | ||
|
|
38dd3d82fc | ||
|
|
6be26aaff2 | ||
|
|
d1462b032a | ||
|
|
6ab974a330 | ||
|
|
64618da6a7 | ||
|
|
d242ff7351 | ||
|
|
7d54aaa9a7 | ||
|
|
0e46184656 | ||
|
|
c9e1a6edbd | ||
|
|
3c0cffd146 | ||
|
|
d31547fb69 | ||
|
|
93adaa12bc | ||
|
|
d1cdbe94de | ||
|
|
e7c6223799 | ||
|
|
d33afd85de | ||
|
|
77bcb4aef9 | ||
|
|
a70fe184c2 | ||
|
|
7238f39be5 | ||
|
|
ae44b59c5f | ||
|
|
ced4e71307 | ||
|
|
9cb43bd5dd | ||
|
|
9665de2dbe | ||
|
|
e2666f0c4c | ||
|
|
89c83c90d1 | ||
|
|
cdd25351a6 | ||
|
|
17e0413837 | ||
|
|
b04419d9c7 | ||
|
|
0453892ebd | ||
|
|
55493c5b7d | ||
|
|
eb2f7cd852 | ||
|
|
f44eb234c8 | ||
|
|
404b57ab21 | ||
|
|
a54f09ca4b | ||
|
|
a3bb909308 | ||
|
|
7eb443859e | ||
|
|
8b39788ae3 | ||
|
|
1ec132b7dd | ||
|
|
343a975073 | ||
|
|
944f7bd086 | ||
|
|
3639c2a540 | ||
|
|
a9cc8ed702 | ||
|
|
0351a15067 | ||
|
|
c7300a1707 | ||
|
|
c48008c4b2 | ||
|
|
e5d8804591 | ||
|
|
f6dc294bc2 | ||
|
|
692baf8bb6 | ||
|
|
e1c00f3856 | ||
|
|
cf173d0695 | ||
|
|
a3798c6ca3 | ||
|
|
7bcf1da823 | ||
|
|
2ccd8dabeb | ||
|
|
f9800dd59e | ||
|
|
fb3e8b9fc4 | ||
|
|
18be5989c7 | ||
|
|
be91e3f3f7 | ||
|
|
03a9aa3dec | ||
|
|
a2a301beda | ||
|
|
695e4f653b | ||
|
|
e84c75929c | ||
|
|
7592178b6b | ||
|
|
70d65b734f | ||
|
|
f9a3dfeda0 | ||
|
|
eb85931fe2 | ||
|
|
1745334737 | ||
|
|
ea92ddfd89 | ||
|
|
3400928798 | ||
|
|
4e665fd66f | ||
|
|
09cd2c12e2 | ||
|
|
e3e45929f0 | ||
|
|
2caeafaaa2 | ||
|
|
5cd21de174 | ||
|
|
78df595861 | ||
|
|
3c89a0537f | ||
|
|
282463c2e8 | ||
|
|
03bb7a4aaf | ||
|
|
e4fa031cd3 | ||
|
|
00d227c598 | ||
|
|
fb86cb91bc | ||
|
|
f11ee20b73 | ||
|
|
3665c5bfaf | ||
|
|
638446680c | ||
|
|
bd9bfa25be | ||
|
|
753d17a8d2 | ||
|
|
f5cfce3f44 | ||
|
|
1c6363e85c | ||
|
|
b8bcbd8af9 | ||
|
|
d70768277b | ||
|
|
b39b4a2e8c | ||
|
|
93b2f4035c | ||
|
|
31ee0c4582 | ||
|
|
6c2dad4851 | ||
|
|
ed190ede03 | ||
|
|
f252b80c2d | ||
|
|
2859ea657e | ||
|
|
664b1cbd75 | ||
|
|
8bc283fef4 | ||
|
|
7d85398139 | ||
|
|
f42be3d65a | ||
|
|
ff8e5af4e5 | ||
|
|
526d67eeb4 | ||
|
|
374fc8d702 | ||
|
|
9c6c59afab | ||
|
|
c3eae0d3a5 | ||
|
|
25ec73364e | ||
|
|
c7818e7f23 | ||
|
|
8318bbc4ca | ||
|
|
337646a6ce | ||
|
|
fa6ee93e5b | ||
|
|
0fe691112f | ||
|
|
6946ff07e0 | ||
|
|
7c3d052da0 | ||
|
|
aea7a7fd65 | ||
|
|
f39a684c8e | ||
|
|
3cb5854786 | ||
|
|
e8192723cc | ||
|
|
3787748eae | ||
|
|
0ad52cf6a7 | ||
|
|
51bee836d4 | ||
|
|
9193b6b971 | ||
|
|
e48b9fab0f | ||
|
|
a14ca24b33 | ||
|
|
761f43d6a1 | ||
|
|
737f9a893e | ||
|
|
579812d717 | ||
|
|
fae39fe650 | ||
|
|
debd8ad617 | ||
|
|
6450ba219c | ||
|
|
a578eda2b1 | ||
|
|
a2ee0bfdbe | ||
|
|
872f0e21cb | ||
|
|
8620dbf337 | ||
|
|
4f51a3b130 | ||
|
|
5f194f066a | ||
|
|
31f6e56333 | ||
|
|
0efbe061ab | ||
|
|
555e5ba6fd | ||
|
|
5d3da60828 | ||
|
|
2ac0d136e6 | ||
|
|
97462cb21a | ||
|
|
afaacd4eeb | ||
|
|
5e8afbe50d | ||
|
|
db371cbc6a | ||
|
|
fd9a5167b1 | ||
|
|
7b76e13bce | ||
|
|
42e7103aa6 | ||
|
|
29ca678f01 | ||
|
|
cca6143420 | ||
|
|
fe7b110577 | ||
|
|
16df55eafa | ||
|
|
905a0ce9ec | ||
|
|
bfa8837e61 | ||
|
|
8d5845b9a2 | ||
|
|
87a7d4958e | ||
|
|
511ebd8a29 | ||
|
|
11f4c8d7a3 | ||
|
|
d417a92843 |
+2
-1
@@ -1,5 +1,6 @@
|
||||
*.autofetch
|
||||
*.sql
|
||||
*create-all.sql
|
||||
*drop-all.sql
|
||||
*.orig
|
||||
.classpath
|
||||
.project
|
||||
|
||||
+32
@@ -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
|
||||
Executable
+10
@@ -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
|
||||
Executable
+14
@@ -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;'
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.10.2</version>
|
||||
<version>7.2.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
<url>http://www.avaje.org</url>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -73,7 +73,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.5.3</version>
|
||||
<version>2.6.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.5.3</version>
|
||||
<version>2.6.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -138,8 +138,8 @@
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<scope>test</scope>
|
||||
<version>2.4.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -152,7 +152,7 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-agent</artifactId>
|
||||
<version>4.7.1</version>
|
||||
<version>4.9.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -168,7 +168,7 @@
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.7.2</version>
|
||||
<version>3.8.11.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -219,7 +219,7 @@
|
||||
<plugin>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
|
||||
<version>4.7.1</version>
|
||||
<version>4.9.2</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();
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ final class DRawSqlColumnsParser {
|
||||
String colInfo = sqlSelect.substring(start, pos++);
|
||||
colInfo = colInfo.trim();
|
||||
|
||||
String[] split = colInfo.split(" ");
|
||||
String[] split = colInfo.split("\\s(?=[^\\)]*(?:\\(|$))");
|
||||
if (split.length > 1) {
|
||||
ArrayList<String> tmp = new ArrayList<String>(split.length);
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
@@ -82,11 +82,13 @@ final class DRawSqlColumnsParser {
|
||||
|
||||
private void nextComma() {
|
||||
boolean inQuote = false;
|
||||
int inbrackets = 0;
|
||||
while (pos < end) {
|
||||
char c = sqlSelect.charAt(pos);
|
||||
if (c == '\'') {
|
||||
inQuote = !inQuote;
|
||||
} else if (!inQuote && c == ',') {
|
||||
if (c == '\'') inQuote = !inQuote;
|
||||
else if (c == '(') inbrackets++;
|
||||
else if (c == ')') inbrackets--;
|
||||
else if (!inQuote && inbrackets == 0 && c == ',') {
|
||||
return;
|
||||
}
|
||||
pos++;
|
||||
|
||||
@@ -46,6 +46,7 @@ class DRawSqlParser {
|
||||
|
||||
private DRawSqlParser(String sqlString) {
|
||||
sqlString = sqlString.trim();
|
||||
sqlString = sqlString.replace('\n',' ');
|
||||
this.sql = sqlString;
|
||||
this.hasPlaceHolders = findAndRemovePlaceHolders();
|
||||
this.textParser = new SimpleTextParser(sqlString);
|
||||
@@ -70,7 +71,7 @@ class DRawSqlParser {
|
||||
|
||||
preFrom = trimSelectKeyword(preFrom);
|
||||
|
||||
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
|
||||
return new Sql(sql, preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,8 +121,7 @@ class DRawSqlParser {
|
||||
// trim of distinct keyword
|
||||
String distinct = preWhereExprSql.substring(0, 9);
|
||||
if (!distinct.equalsIgnoreCase("distinct ")) {
|
||||
throw new RuntimeException("Expecting [" + preWhereExprSql
|
||||
+ "] to start with \"select distinct\"");
|
||||
throw new RuntimeException("Expecting [" + preWhereExprSql + "] to start with \"select distinct\"");
|
||||
}
|
||||
preWhereExprSql = preWhereExprSql.substring(9);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
/**
|
||||
* Bean holding the details to update the document store.
|
||||
*/
|
||||
public final class DocStoreQueueEntry {
|
||||
|
||||
/**
|
||||
* Action to either update or delete a document from the index.
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
/**
|
||||
* Action is to update a document in the doc store.
|
||||
*/
|
||||
INDEX(1),
|
||||
|
||||
/**
|
||||
* Action is to delete a document from the doc store..
|
||||
*/
|
||||
DELETE(2),
|
||||
|
||||
/**
|
||||
* An update is required based on a change to a nested/embedded object at a given path.
|
||||
*/
|
||||
NESTED(3);
|
||||
|
||||
int value;
|
||||
|
||||
Action(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value associated with this action type.
|
||||
*/
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
final Action type;
|
||||
|
||||
final String queueId;
|
||||
|
||||
final String path;
|
||||
|
||||
final Object beanId;
|
||||
|
||||
/**
|
||||
* Construct for an INDEX or DELETE action.
|
||||
*/
|
||||
public DocStoreQueueEntry(Action type, String queueId, Object beanId) {
|
||||
this(type, queueId, null, beanId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for an NESTED/embedded path invalidation action.
|
||||
*/
|
||||
public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) {
|
||||
this.type = type;
|
||||
this.queueId = queueId;
|
||||
this.path = path;
|
||||
this.beanId = beanId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the event type.
|
||||
*/
|
||||
public Action getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associate queueId.
|
||||
*/
|
||||
public String getQueueId() {
|
||||
return queueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path if this is a nested update.
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean id (which matches the document id).
|
||||
*/
|
||||
public Object getBeanId() {
|
||||
return beanId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebeanservice.docstore.api.DocQueryRequest;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Document storage operations.
|
||||
*/
|
||||
public interface DocumentStore {
|
||||
|
||||
/**
|
||||
* Update the associated document store using the result of the query.
|
||||
* <p>
|
||||
* This will execute the query against the database creating a document for each
|
||||
* bean graph and sending this to the document store.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the select and fetch paths of the query is set for you to match the
|
||||
* document structure needed based on <code>@DocStore</code> and <code>@DocStoreEmbedded</code>
|
||||
* so what this query requires is the predicates only.
|
||||
* </p>
|
||||
* <p>
|
||||
* This query will be executed using findEach so it is safe to use a query
|
||||
* that will fetch a lot of beans. The default bulkBatchSize is used.
|
||||
* </p>
|
||||
*
|
||||
* @param query The query that selects object to send to the document store.
|
||||
*/
|
||||
<T> void indexByQuery(Query<T> query);
|
||||
|
||||
/**
|
||||
* Update the associated document store index using the result of the query additionally specifying a
|
||||
* bulkBatchSize to use for sending the messages to ElasticSearch.
|
||||
*
|
||||
* @param query The query that selects object to send to the document store.
|
||||
* @param bulkBatchSize The batch size to use when bulk sending to the document store.
|
||||
*/
|
||||
<T> void indexByQuery(Query<T> query, int bulkBatchSize);
|
||||
|
||||
/**
|
||||
* Update the document store for all beans of this type.
|
||||
* <p>
|
||||
* This is the same as indexByQuery where the query has no predicates and so fetches all rows.
|
||||
* </p>
|
||||
*/
|
||||
void indexAll(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Return the bean by fetching it's content from the document store.
|
||||
* If the document is not found null is returned.
|
||||
* <p>
|
||||
* Typically this is called indirectly by findUnique() on the query.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Customer customer =
|
||||
* server.find(Customer.class)
|
||||
* .setUseDocStore(true)
|
||||
* .setId(42)
|
||||
* .findUnique();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Nullable
|
||||
<T> T find(DocQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the find list query. This request is prepared to execute secondary queries.
|
||||
* <p>
|
||||
* Typically this is called indirectly by findList() on the query that has setUseDocStore(true).
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> newCustomers =
|
||||
* server.find(Customer.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where().eq("status, Customer.Status.NEW)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> List<T> findList(DocQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the query against the document store returning the paged list.
|
||||
* <p>
|
||||
* The query should have <code>firstRow</code> or <code>maxRows</code> set prior to calling this method.
|
||||
* </p>
|
||||
* <p>
|
||||
* Typically this is called indirectly by findPagedList() on the query that has setUseDocStore(true).
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Customer> newCustomers =
|
||||
* server.find(Customer.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where().eq("status, Customer.Status.NEW)
|
||||
* .setMaxRows(50)
|
||||
* .findPagedList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(DocQueryRequest<T> request);
|
||||
|
||||
/**
|
||||
* Execute the query against the document store with the expectation of a large set of results
|
||||
* that are processed in a scrolling resultSet fashion.
|
||||
* <p>
|
||||
* For example, with the ElasticSearch doc store this uses SCROLL.
|
||||
* </p>
|
||||
* <p>
|
||||
* Typically this is called indirectly by findEach() on the query that has setUseDocStore(true).
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* server.find(Order.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where()... // perhaps add predicates
|
||||
* .findEach(new QueryEachConsumer<Order>() {
|
||||
* @Override
|
||||
* public void accept(Order bean) {
|
||||
* // process the bean
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> void findEach(DocQueryRequest<T> query, QueryEachConsumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query against the document store with the expectation of a large set of results
|
||||
* that are processed in a scrolling resultSet fashion.
|
||||
* <p>
|
||||
* Unlike findEach() this provides the opportunity to stop iterating through the large query.
|
||||
* </p>
|
||||
* <p>
|
||||
* For example, with the ElasticSearch doc store this uses SCROLL.
|
||||
* </p>
|
||||
* <p>
|
||||
* Typically this is called indirectly by findEachWhile() on the query that has setUseDocStore(true).
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* server.find(Order.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where()... // perhaps add predicates
|
||||
* .findEachWhile(new QueryEachWhileConsumer<Order>() {
|
||||
* @Override
|
||||
* public void accept(Order bean) {
|
||||
* // process the bean
|
||||
*
|
||||
* // return true to continue, false to stop
|
||||
* // boolean shouldContinue = ...
|
||||
* return shouldContinue;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> void findEachWhile(DocQueryRequest<T> query, QueryEachWhileConsumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Process the queue entries sending updates to the document store or queuing them for later processing.
|
||||
*/
|
||||
long process(List<DocStoreQueueEntry> queueEntries) throws IOException;
|
||||
|
||||
/**
|
||||
* Drop the index from the document store (similar to DDL drop table).
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* DocumentStore documentStore = server.docStore();
|
||||
*
|
||||
* documentStore.dropIndex("product_copy");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
void dropIndex(String indexName);
|
||||
|
||||
/**
|
||||
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* DocumentStore documentStore = server.docStore();
|
||||
*
|
||||
* // uses product_copy.mapping.json resource
|
||||
* // ... to define mappings for the index
|
||||
*
|
||||
* documentStore.createIndex("product_copy", null);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param indexName the name of the new index
|
||||
* @param alias the alias of the index
|
||||
*/
|
||||
void createIndex(String indexName, String alias);
|
||||
|
||||
/**
|
||||
* Copy the index to a new index.
|
||||
* <p>
|
||||
* This copy process does not use the database but instead will copy from the source index to a destination index.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* long copyCount = documentStore.copyIndex(Product.class, "product_copy");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param beanType The bean type of the source index
|
||||
* @param newIndex The name of the index to copy to
|
||||
* @return the number of documents copied to the new index
|
||||
*/
|
||||
long copyIndex(Class<?> beanType, String newIndex);
|
||||
|
||||
/**
|
||||
* Copy entries from an index to a new index but limiting to documents that have been
|
||||
* modified since the sinceEpochMillis time.
|
||||
* <p>
|
||||
* To support this the document needs to have a <code>@WhenModified</code> property.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* long copyCount = documentStore.copyIndex(Product.class, "product_copy", sinceMillis);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param beanType The bean type of the source index
|
||||
* @param newIndex The name of the index to copy to
|
||||
* @return the number of documents copied to the new index
|
||||
*/
|
||||
long copyIndex(Class<?> beanType, String newIndex, long sinceEpochMillis);
|
||||
|
||||
/**
|
||||
* Copy from a source index to a new index taking only the documents
|
||||
* matching the given query.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // predicates to select the source documents to copy
|
||||
* Query<Product> query = server.find(Product.class)
|
||||
* .where()
|
||||
* .ge("whenModified", new Timestamp(since))
|
||||
* .ge("name", "A")
|
||||
* .lt("name", "D")
|
||||
* .query();
|
||||
*
|
||||
* // copy from the source index to "product_copy" index
|
||||
* long copyCount = documentStore.copyIndex(query, "product_copy", 1000);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param query The query to select the source documents to copy
|
||||
* @param newIndex The target index to copy the documents to
|
||||
* @param bulkBatchSize The ElasticSearch bulk batch size, if 0 uses the default.
|
||||
* @return The number of documents copied to the new index.
|
||||
*/
|
||||
long copyIndex(Query<?> query, String newIndex, int bulkBatchSize);
|
||||
}
|
||||
@@ -697,12 +697,31 @@ public final class Ebean {
|
||||
/**
|
||||
* Delete the bean.
|
||||
* <p>
|
||||
* This will return true if the bean was deleted successfully or JDBC batch is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* If there is no current transaction one will be created and committed for
|
||||
* you automatically.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the bean is configured with <code>@SoftDelete</code> then this will perform a soft
|
||||
* delete rather than a hard/permanent delete.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Bean does not have a version property (or loaded version property) and
|
||||
* the bean does not exist then this returns false indicating that nothing was
|
||||
* deleted. Note that, if JDBC batch mode is used then this always returns true.
|
||||
* </p>
|
||||
*/
|
||||
public static void delete(Object bean) throws OptimisticLockException {
|
||||
serverMgr.getDefaultServer().delete(bean);
|
||||
public static boolean delete(Object bean) throws OptimisticLockException {
|
||||
return serverMgr.getDefaultServer().delete(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the bean in permanent fashion (will not use soft delete).
|
||||
*/
|
||||
public static boolean deletePermanent(Object bean) throws OptimisticLockException {
|
||||
return serverMgr.getDefaultServer().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -726,6 +745,13 @@ public final class Ebean {
|
||||
return serverMgr.getDefaultServer().deleteAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete permanent all the beans in the Collection (will not use soft delete).
|
||||
*/
|
||||
public static int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException {
|
||||
return serverMgr.getDefaultServer().deleteAllPermanent(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the values of a bean.
|
||||
* <p>
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.avaje.ebean.text.csv.CsvReader;
|
||||
import com.avaje.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
@@ -1019,7 +1020,7 @@ public interface EbeanServer {
|
||||
SqlFutureList findFutureList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -1041,6 +1042,38 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
@@ -1091,14 +1124,17 @@ public interface EbeanServer {
|
||||
<T> Map<?, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean. This will throw a
|
||||
* PersistenceException if the query finds more than one result.
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findUnique()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @param <T>
|
||||
* the type of entity bean to fetch.
|
||||
* @param query
|
||||
@@ -1106,6 +1142,7 @@ public interface EbeanServer {
|
||||
* @param transaction
|
||||
* the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
*
|
||||
* @see Query#findUnique()
|
||||
*/
|
||||
@@ -1246,16 +1283,52 @@ public interface EbeanServer {
|
||||
/**
|
||||
* Delete the bean.
|
||||
* <p>
|
||||
* This will return true if the bean was deleted successfully or JDBC batch is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* If there is no current transaction one will be created and committed for
|
||||
* you automatically.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Bean does not have a version property (or loaded version property) and
|
||||
* the bean does not exist then this returns false indicating that nothing was
|
||||
* deleted. Note that, if JDBC batch mode is used then this always returns true.
|
||||
* </p>
|
||||
*/
|
||||
void delete(Object bean) throws OptimisticLockException;
|
||||
boolean delete(Object bean) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete the bean with an explicit transaction.
|
||||
* <p>
|
||||
* This will return true if the bean was deleted successfully or JDBC batch is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Bean does not have a version property (or loaded version property) and
|
||||
* the bean does not exist then this returns false indicating that nothing was
|
||||
* deleted. However, if JDBC batch mode is used then this always returns true.
|
||||
* </p>
|
||||
*/
|
||||
void delete(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
boolean delete(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete.
|
||||
*/
|
||||
boolean deletePermanent(Object bean) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete using an explicit transaction.
|
||||
*/
|
||||
boolean deletePermanent(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection permanently without soft delete.
|
||||
*/
|
||||
int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection permanently without soft delete using an explicit transaction.
|
||||
*/
|
||||
int deleteAllPermanent(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Delete the bean given its type and id.
|
||||
@@ -1456,7 +1529,7 @@ public interface EbeanServer {
|
||||
void markAsDirty(Object bean);
|
||||
|
||||
/**
|
||||
* Saves the bean using an update. If you know you are updating a bean then it is preferrable to
|
||||
* Saves the bean using an update. If you know you are updating a bean then it is preferable to
|
||||
* use this update() method rather than save().
|
||||
* <p>
|
||||
* <b>Stateless updates:</b> Note that the bean does not have to be previously fetched to call
|
||||
@@ -1508,7 +1581,6 @@ public interface EbeanServer {
|
||||
* @param deleteMissingChildren
|
||||
* specify false if you do not want 'missing children' of a OneToMany
|
||||
* or ManyToMany to be automatically deleted.
|
||||
|
||||
*/
|
||||
void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException;
|
||||
|
||||
@@ -1814,9 +1886,124 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see com.avaje.ebean.text.PathProperties
|
||||
* @see Query#apply(com.avaje.ebean.text.PathProperties)
|
||||
* @see FetchPath
|
||||
* @see Query#apply(FetchPath)
|
||||
*/
|
||||
JsonContext json();
|
||||
|
||||
/**
|
||||
* Return the Document store.
|
||||
*/
|
||||
DocumentStore docStore();
|
||||
|
||||
/**
|
||||
* Publish a single bean given its type and id returning the resulting live bean.
|
||||
* <p>
|
||||
* The values are published from the draft to the live bean.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param beanType the type of the entity bean
|
||||
* @param id the id of the entity bean
|
||||
* @param transaction the transaction the publish process should use (can be null)
|
||||
*/
|
||||
<T> T publish(Class<T> beanType, Object id, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Publish a single bean given its type and id returning the resulting live bean.
|
||||
* This will use the current transaction or create one if required.
|
||||
* <p>
|
||||
* The values are published from the draft to the live bean.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param beanType the type of the entity bean
|
||||
* @param id the id of the entity bean
|
||||
*/
|
||||
<T> T publish(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
* Publish the beans that match the query returning the resulting published beans.
|
||||
* <p>
|
||||
* The values are published from the draft beans to the live beans.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param query the query used to select the draft beans to publish
|
||||
* @param transaction the transaction the publish process should use (can be null)
|
||||
*/
|
||||
<T> List<T> publish(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Publish the beans that match the query returning the resulting published beans.
|
||||
* This will use the current transaction or create one if required.
|
||||
* <p>
|
||||
* The values are published from the draft beans to the live beans.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param query the query used to select the draft beans to publish
|
||||
*/
|
||||
<T> List<T> publish(Query<T> query);
|
||||
|
||||
/**
|
||||
* Restore the draft bean back to the live state.
|
||||
* <p>
|
||||
* The values from the live beans are set back to the draft bean and the
|
||||
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param beanType the type of the entity bean
|
||||
* @param id the id of the entity bean to restore
|
||||
* @param transaction the transaction the restore process should use (can be null)
|
||||
*/
|
||||
<T> T draftRestore(Class<T> beanType, Object id, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Restore the draft bean back to the live state.
|
||||
* <p>
|
||||
* The values from the live beans are set back to the draft bean and the
|
||||
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param beanType the type of the entity bean
|
||||
* @param id the id of the entity bean to restore
|
||||
*/
|
||||
<T> T draftRestore(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
* Restore the draft beans matching the query back to the live state.
|
||||
* <p>
|
||||
* The values from the live beans are set back to the draft bean and the
|
||||
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param query the query used to select the draft beans to restore
|
||||
* @param transaction the transaction the restore process should use (can be null)
|
||||
*/
|
||||
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Restore the draft beans matching the query back to the live state.
|
||||
* <p>
|
||||
* The values from the live beans are set back to the draft bean and the
|
||||
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of the entity bean
|
||||
* @param query the query used to select the draft beans to restore
|
||||
*/
|
||||
<T> List<T> draftRestore(Query<T> query);
|
||||
|
||||
/**
|
||||
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
|
||||
* <p>
|
||||
* Validate the query checking the where and orderBy expression paths to confirm if
|
||||
* they represent valid properties/path for the given bean type.
|
||||
* </p>
|
||||
*/
|
||||
<T> Set<String> validateQuery(Query<T> query);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* An expression that is part of a WHERE or HAVING clause.
|
||||
*/
|
||||
public interface Expression extends Serializable {
|
||||
public interface Expression {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.search.Match;
|
||||
import com.avaje.ebean.search.MultiMatch;
|
||||
import com.avaje.ebean.search.TextCommonTerms;
|
||||
import com.avaje.ebean.search.TextQueryString;
|
||||
import com.avaje.ebean.search.TextSimple;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -285,6 +291,31 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression raw(String raw);
|
||||
|
||||
/**
|
||||
* Create a Text Match expression (currently doc store/Elastic only).
|
||||
*/
|
||||
Expression textMatch(String propertyName, String search, Match options);
|
||||
|
||||
/**
|
||||
* Create a Text Multi match expression (currently doc store/Elastic only).
|
||||
*/
|
||||
Expression textMultiMatch(String query, MultiMatch options);
|
||||
|
||||
/**
|
||||
* Create a text simple query expression (currently doc store/Elastic only).
|
||||
*/
|
||||
Expression textSimple(String search, TextSimple options);
|
||||
|
||||
/**
|
||||
* Create a text query string expression (currently doc store/Elastic only).
|
||||
*/
|
||||
Expression textQueryString(String search, TextQueryString options);
|
||||
|
||||
/**
|
||||
* Create a text common terms expression (currently doc store/Elastic only).
|
||||
*/
|
||||
Expression textCommonTerms(String search, TextCommonTerms options);
|
||||
|
||||
/**
|
||||
* And - join two expressions with a logical and.
|
||||
*/
|
||||
@@ -319,4 +350,13 @@ public interface ExpressionFactory {
|
||||
* Return a list of expressions that will be joined by OR's.
|
||||
*/
|
||||
<T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent);
|
||||
|
||||
/**
|
||||
* Return a Text query junction for MUST, SHOULD or MUST NOT.
|
||||
* <p>
|
||||
* This is doc store Elastic only.
|
||||
* </p>
|
||||
*/
|
||||
<T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebean.search.Match;
|
||||
import com.avaje.ebean.search.MultiMatch;
|
||||
import com.avaje.ebean.search.TextCommonTerms;
|
||||
import com.avaje.ebean.search.TextQueryString;
|
||||
import com.avaje.ebean.search.TextSimple;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -30,7 +34,7 @@ import java.util.Set;
|
||||
*
|
||||
* @see Query#where()
|
||||
*/
|
||||
public interface ExpressionList<T> extends Serializable {
|
||||
public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Return the query that owns this expression list.
|
||||
@@ -94,7 +98,7 @@ public interface ExpressionList<T> extends Serializable {
|
||||
/**
|
||||
* Apply the path properties to the query replacing the select and fetch clauses.
|
||||
*/
|
||||
Query<T> apply(PathProperties pathProperties);
|
||||
Query<T> apply(FetchPath fetchPath);
|
||||
|
||||
/**
|
||||
* Perform an 'As of' query using history tables to return the object graph
|
||||
@@ -107,6 +111,16 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Execute the query against the draft set of tables.
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Execute the query including soft deleted rows.
|
||||
*/
|
||||
Query<T> includeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
@@ -183,8 +197,15 @@ public interface ExpressionList<T> extends Serializable {
|
||||
<K> Map<K, T> findMap(String keyProperty, Class<K> keyType);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean.
|
||||
*
|
||||
* Execute the query returning a single bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* If more than 1 row is found for this query then a NonUniqueResultException is
|
||||
* thrown.
|
||||
* </p>
|
||||
*
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
*
|
||||
* @see Query#findUnique()
|
||||
*/
|
||||
@Nullable
|
||||
@@ -227,7 +248,7 @@ public interface ExpressionList<T> extends Serializable {
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -247,6 +268,38 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
@@ -331,6 +384,32 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useCache);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
* When setting this you may also consider disabling lazy loading.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setUseDocStore(boolean useDocsStore);
|
||||
|
||||
/**
|
||||
* Set true if you want to disable lazy loading.
|
||||
* <p>
|
||||
* That is, once the object graph is returned further lazy loading is disabled.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
|
||||
|
||||
/**
|
||||
* Disable read auditing for this query.
|
||||
* <p>
|
||||
* This is intended to be used when the query is not a user initiated query and instead
|
||||
* part of the internal processing in an application to load a cache or document store etc.
|
||||
* In these cases we don't want the query to be part of read auditing.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setDisableReadAuditing();
|
||||
|
||||
/**
|
||||
* Add expressions to the having clause.
|
||||
* <p>
|
||||
@@ -735,7 +814,7 @@ public interface ExpressionList<T> extends Serializable {
|
||||
* qualified) will still be translated to their physical name.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> raw(String raw, Object[] values);
|
||||
ExpressionList<T> raw(String raw, Object... values);
|
||||
|
||||
/**
|
||||
* Add raw expression with no parameters.
|
||||
@@ -753,6 +832,47 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
ExpressionList<T> raw(String raw);
|
||||
|
||||
/**
|
||||
* Add a match expression.
|
||||
*
|
||||
* @param propertyName The property name for the match
|
||||
* @param search The search value
|
||||
*/
|
||||
ExpressionList<T> match(String propertyName, String search);
|
||||
|
||||
/**
|
||||
* Add a match expression with options.
|
||||
*
|
||||
* @param propertyName The property name for the match
|
||||
* @param search The search value
|
||||
*/
|
||||
ExpressionList<T> match(String propertyName, String search, Match options);
|
||||
|
||||
/**
|
||||
* Add a multi-match expression.
|
||||
*/
|
||||
ExpressionList<T> multiMatch(String search, String... properties);
|
||||
|
||||
/**
|
||||
* Add a multi-match expression using options.
|
||||
*/
|
||||
ExpressionList<T> multiMatch(String search, MultiMatch options);
|
||||
|
||||
/**
|
||||
* Add a simple query string expression.
|
||||
*/
|
||||
ExpressionList<T> textSimple(String search, TextSimple options);
|
||||
|
||||
/**
|
||||
* Add a query string expression.
|
||||
*/
|
||||
ExpressionList<T> textQueryString(String search, TextQueryString options);
|
||||
|
||||
/**
|
||||
* Add common terms expression.
|
||||
*/
|
||||
ExpressionList<T> textCommonTerms(String search, TextCommonTerms options);
|
||||
|
||||
/**
|
||||
* And - join two expressions with a logical and.
|
||||
*/
|
||||
@@ -769,20 +889,168 @@ public interface ExpressionList<T> extends Serializable {
|
||||
ExpressionList<T> not(Expression exp);
|
||||
|
||||
/**
|
||||
* Return a list of expressions that will be joined by AND's.
|
||||
* Start a list of expressions that will be joined by AND's
|
||||
* returning the expression list the expressions are added to.
|
||||
* <p>
|
||||
* This is exactly the same as conjunction();
|
||||
* </p>
|
||||
* <p>
|
||||
* Use endJunction() to end the AND junction.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that a where() clause defaults to an AND junction so
|
||||
* typically you only explicitly need to use the and() junction
|
||||
* when it is nested inside an or() or not() junction.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Example: Nested and()
|
||||
*
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .or()
|
||||
* .and() // nested and
|
||||
* .startsWith("name", "r")
|
||||
* .eq("anniversary", onAfter)
|
||||
* .endJunction() // end AND junction
|
||||
* .and()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .gt("id", 0)
|
||||
* .endJunction() // end AND junction
|
||||
* .orderBy().asc("name")
|
||||
* .findList();
|
||||
* }</pre>
|
||||
*/
|
||||
Junction<T> and();
|
||||
|
||||
/**
|
||||
* Return a list of expressions that will be joined by OR's.
|
||||
* This is exactly the same as disjunction();
|
||||
*
|
||||
* <p>
|
||||
* Use endJunction() to end the OR junction.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Example: Use or() to join
|
||||
* // two nested and() expressions
|
||||
*
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .or()
|
||||
* .and()
|
||||
* .startsWith("name", "r")
|
||||
* .eq("anniversary", onAfter)
|
||||
* .endJunction() // end AND junction
|
||||
* .and()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .gt("id", 0)
|
||||
* .endJunction() // end AND junction
|
||||
* .orderBy().asc("name")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Junction<T> or();
|
||||
|
||||
/**
|
||||
* Return a list of expressions that will be wrapped by NOT.
|
||||
* <p>
|
||||
* Use endJunction() to end expressions being added to the
|
||||
* NOT expression list.
|
||||
* </p>
|
||||
*
|
||||
* <pre>@{code
|
||||
*
|
||||
* .where()
|
||||
* .not()
|
||||
* .gt("id", 1)
|
||||
* .eq("anniversary", onAfter)
|
||||
* .endJunction() // end the not expressions
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <pre>@{code
|
||||
*
|
||||
* // Example: nested not()
|
||||
*
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .not()
|
||||
* .gt("id", 1)
|
||||
* .eq("anniversary", onAfter)
|
||||
* .endJunction() // end the not expressions
|
||||
* .orderBy()
|
||||
* .asc("name")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Junction<T> not();
|
||||
|
||||
/**
|
||||
* Start (and return) a list of expressions that will be joined by AND's.
|
||||
* <p>
|
||||
* This is the same as and().
|
||||
* </p>
|
||||
*/
|
||||
Junction<T> conjunction();
|
||||
|
||||
/**
|
||||
* Return a list of expressions that will be joined by OR's.
|
||||
* Start (and return) a list of expressions that will be joined by OR's.
|
||||
* <p>
|
||||
* This is the same as or().
|
||||
* </p>
|
||||
*/
|
||||
Junction<T> disjunction();
|
||||
|
||||
/**
|
||||
* End a Conjunction or Disjunction returning the parent expression list.
|
||||
* Start a list of expressions that will be joined by MUST.
|
||||
* <p>
|
||||
* Alternatively you can always use where() to return the top level expression
|
||||
* list.
|
||||
* This automatically makes the query a useDocStore(true) query that
|
||||
* will execute against the document store (ElasticSearch etc).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is logically similar to and().
|
||||
* </p>
|
||||
*/
|
||||
Junction<T> must();
|
||||
|
||||
/**
|
||||
* Start a list of expressions that will be joined by SHOULD.
|
||||
* <p>
|
||||
* This automatically makes the query a useDocStore(true) query that
|
||||
* will execute against the document store (ElasticSearch etc).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is logically similar to or().
|
||||
* </p>
|
||||
*/
|
||||
Junction<T> should();
|
||||
|
||||
/**
|
||||
* Start a list of expressions that will be joined by MUST NOT.
|
||||
* <p>
|
||||
* This automatically makes the query a useDocStore(true) query that
|
||||
* will execute against the document store (ElasticSearch etc).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is logically similar to not().
|
||||
* </p>
|
||||
*/
|
||||
Junction<T> mustNot();
|
||||
|
||||
/**
|
||||
* End a junction returning the parent expression list.
|
||||
* <p>
|
||||
* Ends a and(), or(), not(), must(), mustNot() or should() junction
|
||||
* such that you get the parent expression.
|
||||
* </p>
|
||||
* <p>
|
||||
* Alternatively you can always use where() to return the top level expression list.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> endJunction();
|
||||
|
||||
@@ -249,4 +249,22 @@ public class FetchConfig implements Serializable {
|
||||
return queryAll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
FetchConfig that = (FetchConfig) o;
|
||||
if (lazyBatchSize != that.lazyBatchSize) return false;
|
||||
if (queryBatchSize != that.queryBatchSize) return false;
|
||||
return queryAll == that.queryAll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = lazyBatchSize;
|
||||
result = 92821 * result + queryBatchSize;
|
||||
result = 92821 * result + (queryAll ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides paths and properties for an object graph that can be used to control what parts of the object graph
|
||||
* is fetching (select and fetch clauses) and also can be used to control JSON marshalling (what parts of the object
|
||||
* graph are included in the JSON).
|
||||
*/
|
||||
public interface FetchPath {
|
||||
|
||||
/**
|
||||
* Return true if the path is included in this FetchPath.
|
||||
*/
|
||||
boolean hasPath(String path);
|
||||
|
||||
/**
|
||||
* Return the properties at the given path.
|
||||
*/
|
||||
Set<String> getProperties(String path);
|
||||
|
||||
/**
|
||||
* Apply the fetch path to the query.
|
||||
*/
|
||||
<T> void apply(Query<T> query);
|
||||
}
|
||||
@@ -9,70 +9,129 @@ package com.avaje.ebean;
|
||||
* <p>
|
||||
* Note: where() always takes you to the top level WHERE expression list.
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* <pre>{@code
|
||||
* Query q =
|
||||
* Ebean.find(Person.class)
|
||||
* .where().disjunction()
|
||||
* .like("name", "Rob%")
|
||||
* .eq("status", Status.NEW)
|
||||
*
|
||||
* // where() returns us to the top level expression list
|
||||
* .where().gt("id", 10);
|
||||
*
|
||||
* .where()
|
||||
* .or()
|
||||
* .like("name", "Rob%")
|
||||
* .eq("status", Status.NEW)
|
||||
*
|
||||
* // where() returns us to the top level expression list
|
||||
* .where().gt("id", 10);
|
||||
*
|
||||
* // read as...
|
||||
* // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
|
||||
* </pre>
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Note: endJunction() takes you to the parent expression list
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* <pre>{@code
|
||||
* Query q =
|
||||
* Ebean.find(Person.class)
|
||||
* .where().disjunction()
|
||||
* .like("name", "Rob%")
|
||||
* .eq("status", Status.NEW)
|
||||
* .endJunction()
|
||||
*
|
||||
* // endJunction().. takes us to the 'parent' expression list
|
||||
* // which in this case is the top level (same as where())
|
||||
*
|
||||
* .gt("id", 10);
|
||||
*
|
||||
* .where()
|
||||
* .or()
|
||||
* .like("name", "Rob%")
|
||||
* .eq("status", Status.NEW)
|
||||
* .endJunction()
|
||||
*
|
||||
* // endJunction().. takes us to the 'parent' expression list
|
||||
* // which in this case is the top level (same as where())
|
||||
*
|
||||
* .gt("id", 10);
|
||||
*
|
||||
* // read as...
|
||||
* // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
|
||||
* </pre>
|
||||
*
|
||||
* // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Example of a nested disjunction.
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* Query<Customer> q =
|
||||
*
|
||||
* <pre>{@code
|
||||
* Query<Customer> q =
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .disjunction()
|
||||
* .conjunction()
|
||||
* .startsWith("name", "r")
|
||||
* .eq("anniversary", onAfter)
|
||||
* .endJunction()
|
||||
* .conjunction()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .gt("id", 0)
|
||||
* .endJunction()
|
||||
* .order().asc("name");
|
||||
*
|
||||
* .or()
|
||||
* .and()
|
||||
* .startsWith("name", "r")
|
||||
* .eq("anniversary", onAfter)
|
||||
* .endJunction()
|
||||
* .and()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .gt("id", 0)
|
||||
* .endJunction()
|
||||
* .order().asc("name");
|
||||
*
|
||||
* q.findList();
|
||||
* String s = q.getGeneratedSql();
|
||||
*
|
||||
*
|
||||
* // this produces an expression like:
|
||||
*
|
||||
* ( name like ? and c.anniversary = ? ) or (c.status = ? and c.id > ? )
|
||||
*
|
||||
* </pre>
|
||||
* ( name like ? and c.anniversary = ? ) or (c.status = ? and c.id > ? )
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface Junction<T> extends Expression, ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* The type of Junction used in full text expressions.
|
||||
*/
|
||||
enum Type {
|
||||
|
||||
/**
|
||||
* AND group.
|
||||
*/
|
||||
AND(" and ", ""),
|
||||
|
||||
/**
|
||||
* OR group.
|
||||
*/
|
||||
OR(" or ", ""),
|
||||
|
||||
/**
|
||||
* NOT group.
|
||||
*/
|
||||
NOT(" and ", "not "),
|
||||
|
||||
/**
|
||||
* Text search AND group.
|
||||
*/
|
||||
MUST("must", ""),
|
||||
|
||||
/**
|
||||
* Text search NOT group.
|
||||
*/
|
||||
MUST_NOT("must_not", ""),
|
||||
|
||||
/**
|
||||
* Text search OR group.
|
||||
*/
|
||||
SHOULD("should", "");
|
||||
|
||||
String prefix;
|
||||
String literal;
|
||||
|
||||
Type(String literal, String prefix) {
|
||||
this.literal = literal;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the literal value for this type.
|
||||
*/
|
||||
public String literal() {
|
||||
return literal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the prefix value for this type.
|
||||
*/
|
||||
public String prefix() {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebean.util.ClassUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -251,12 +250,24 @@ public abstract class Model {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this entity.
|
||||
* Delete this bean.
|
||||
* <p>
|
||||
* This will return true if the bean was deleted successfully or JDBC batch is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* If there is no current transaction one will be created and committed for
|
||||
* you automatically.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the Bean does not have a version property (or loaded version property) and
|
||||
* the bean does not exist then this returns false indicating that nothing was
|
||||
* deleted. Note that, if JDBC batch mode is used then this always returns true.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#delete(Object)
|
||||
*/
|
||||
public void delete() {
|
||||
db().delete(this);
|
||||
public boolean delete() {
|
||||
return db().delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,8 +287,8 @@ public abstract class Model {
|
||||
/**
|
||||
* Perform a delete using this entity against the specified server.
|
||||
*/
|
||||
public void delete(String server) {
|
||||
db(server).delete(this);
|
||||
public boolean delete(String server) {
|
||||
return db(server).delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,10 +587,10 @@ public abstract class Model {
|
||||
/**
|
||||
* Creates a query applying the path properties to set the select and fetch clauses.
|
||||
* <p>
|
||||
* Equivalent to {@link Query#apply(com.avaje.ebean.text.PathProperties)}
|
||||
* Equivalent to {@link Query#apply(FetchPath)}
|
||||
*/
|
||||
public Query<T> apply(PathProperties pathProperties) {
|
||||
return db().find(type).apply(pathProperties);
|
||||
public Query<T> apply(FetchPath fetchPath) {
|
||||
return db().find(type).apply(fetchPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,33 @@ import java.util.concurrent.Future;
|
||||
* limit the result set.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <h4>Example: typical use including total row count</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // We want to find the first 50 new orders
|
||||
* // ... so we don't really need setFirstRow(0)
|
||||
*
|
||||
* PagedList<Order> pagedList
|
||||
* = ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .setFirstRow(0)
|
||||
* .setMaxRows(50)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // Optional: initiate the loading of the total
|
||||
* // row count in a background thread
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* // fetch and return the list in the foreground thread
|
||||
* List<Order> orders = pagedList.getList();
|
||||
*
|
||||
* // get the total row count (from the future)
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h4>Example: typical use including total row count</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -156,11 +183,15 @@ public interface PagedList<T> {
|
||||
|
||||
/**
|
||||
* Return the index position of this page. Zero based.
|
||||
* <p>
|
||||
* Note that if firstRows/maxRows is used rather than pageIndex/pageSize then
|
||||
* this always returns 0.
|
||||
* </p>
|
||||
*/
|
||||
int getPageIndex();
|
||||
|
||||
/**
|
||||
* Return the page size used for this query.
|
||||
* Return the page size used for this query. This is the same value as maxRows used by the query.
|
||||
*/
|
||||
int getPageSize();
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
/**
|
||||
* Captures and wraps IOException's occurring during ElasticSearch processing etc.
|
||||
*/
|
||||
public class PersistenceIOException extends PersistenceException {
|
||||
|
||||
public PersistenceIOException(String msg, Exception cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
public PersistenceIOException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -274,7 +273,7 @@ import java.util.Set;
|
||||
* @param <T>
|
||||
* the type of Entity bean this query will fetch.
|
||||
*/
|
||||
public interface Query<T> extends Serializable {
|
||||
public interface Query<T> {
|
||||
|
||||
/**
|
||||
* Return the RawSql that was set to use for this query.
|
||||
@@ -297,6 +296,16 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Query<T> asOf(Timestamp asOf);
|
||||
|
||||
/**
|
||||
* Execute the query against the draft set of tables.
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Execute the query including soft deleted rows.
|
||||
*/
|
||||
Query<T> includeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -511,10 +520,10 @@ public interface Query<T> extends Serializable {
|
||||
/**
|
||||
* Apply the path properties replacing the select and fetch clauses.
|
||||
* <p>
|
||||
* This is typically used when the PathProperties is applied to both the query and the JSON output.
|
||||
* This is typically used when the FetchPath is applied to both the query and the JSON output.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> apply(PathProperties pathProperties);
|
||||
Query<T> apply(FetchPath fetchPath);
|
||||
|
||||
/**
|
||||
* Execute the query returning the list of Id's.
|
||||
@@ -690,7 +699,7 @@ public interface Query<T> extends Serializable {
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* If more than 1 row is found for this query then a PersistenceException is
|
||||
* If more than 1 row is found for this query then a NonUniqueResultException is
|
||||
* thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -726,6 +735,8 @@ public interface Query<T> extends Serializable {
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
* ...
|
||||
* }</pre>
|
||||
*
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
*/
|
||||
@Nullable
|
||||
T findUnique();
|
||||
@@ -809,7 +820,7 @@ public interface Query<T> extends Serializable {
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query.
|
||||
* Return a PagedList for this query using pageIndex and pageSize.
|
||||
* <p>
|
||||
* The benefit of using this over just using the normal {@link Query#setFirstRow(int)} and
|
||||
* {@link Query#setMaxRows(int)} is that it additionally wraps an optional call to
|
||||
@@ -854,6 +865,36 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
PagedList<T> findPagedList(int pageIndex, int pageSize);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Set a named bind parameter. Named parameters have a colon to prefix the name.
|
||||
*
|
||||
@@ -1000,6 +1041,27 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
ExpressionList<T> where();
|
||||
|
||||
/**
|
||||
* Add Full text search expressions for Document store queries.
|
||||
* <p>
|
||||
* This is currently ElasticSearch only and provides the full text
|
||||
* expressions such as Match and Multi-Match.
|
||||
* </p>
|
||||
* <p>
|
||||
* This automatically makes this query a "Doc Store" query and will execute
|
||||
* against the document store (ElasticSearch).
|
||||
* </p>
|
||||
* <p>
|
||||
* Expressions added here are added to the "query" section of an ElasticSearch
|
||||
* query rather than the "filter" section.
|
||||
* </p>
|
||||
* <p>
|
||||
* Expressions added to the where() are added to the "filter" section of an
|
||||
* ElasticSearch query.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> text();
|
||||
|
||||
/**
|
||||
* This applies a filter on the 'many' property list rather than the root
|
||||
* level objects.
|
||||
@@ -1243,6 +1305,14 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useQueryCache);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
* When setting this you may also consider disabling lazy loading.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setUseDocStore(boolean useDocStore);
|
||||
|
||||
/**
|
||||
* When set to true when you want the returned beans to be read only.
|
||||
*/
|
||||
@@ -1313,4 +1383,14 @@ public interface Query<T> extends Serializable {
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
|
||||
|
||||
/**
|
||||
* Returns the set of properties or paths that are unknown (do not map to known properties or paths).
|
||||
* <p>
|
||||
* Validate the query checking the where and orderBy expression paths to confirm if
|
||||
* they represent valid properties or paths for the given bean type.
|
||||
* </p>
|
||||
*/
|
||||
Set<String> validate();
|
||||
|
||||
}
|
||||
|
||||
@@ -206,6 +206,14 @@ public final class RawSql implements Serializable {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key;
|
||||
*/
|
||||
public Key getKey() {
|
||||
boolean parsed = sql != null && sql.parsed;
|
||||
String unParsedSql = (sql == null) ? "" : sql.unparsedSql;
|
||||
return new Key(parsed, unParsedSql, columnMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resultSet if this is a ResultSet based RawSql.
|
||||
@@ -221,16 +229,6 @@ public final class RawSql implements Serializable {
|
||||
return columnMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash for this query.
|
||||
*/
|
||||
public int queryHash() {
|
||||
if (resultSet != null) {
|
||||
return 31 * columnMapping.queryHash();
|
||||
}
|
||||
return 31 * sql.queryHash() + columnMapping.queryHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the sql part of the query. For parsed RawSql the sql is broken
|
||||
* up so that Ebean can insert extra WHERE and HAVING expressions into the
|
||||
@@ -260,13 +258,10 @@ public final class RawSql implements Serializable {
|
||||
|
||||
private final boolean distinct;
|
||||
|
||||
private final int queryHashCode;
|
||||
|
||||
/**
|
||||
* Construct for unparsed SQL.
|
||||
*/
|
||||
protected Sql(String unparsedSql) {
|
||||
this.queryHashCode = unparsedSql.hashCode();
|
||||
this.parsed = false;
|
||||
this.unparsedSql = unparsedSql;
|
||||
this.preFrom = null;
|
||||
@@ -282,12 +277,11 @@ public final class RawSql implements Serializable {
|
||||
/**
|
||||
* Construct for parsed SQL.
|
||||
*/
|
||||
protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr,
|
||||
protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr,
|
||||
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
|
||||
|
||||
this.queryHashCode = queryHashCode;
|
||||
this.unparsedSql = unparsedSql;
|
||||
this.parsed = true;
|
||||
this.unparsedSql = null;
|
||||
this.preFrom = preFrom;
|
||||
this.preHaving = preHaving;
|
||||
this.preWhere = preWhere;
|
||||
@@ -298,13 +292,6 @@ public final class RawSql implements Serializable {
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash for this query.
|
||||
*/
|
||||
public int queryHash() {
|
||||
return queryHashCode;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (!parsed) {
|
||||
return "unparsed[" + unparsedSql + "]";
|
||||
@@ -406,13 +393,10 @@ public final class RawSql implements Serializable {
|
||||
|
||||
private final boolean immutable;
|
||||
|
||||
private final int queryHashCode;
|
||||
|
||||
/**
|
||||
* Construct from parsed sql where the columns have been identified.
|
||||
*/
|
||||
protected ColumnMapping(List<Column> columns) {
|
||||
this.queryHashCode = 0;
|
||||
this.immutable = false;
|
||||
this.parsed = true;
|
||||
this.propertyMap = null;
|
||||
@@ -428,7 +412,6 @@ public final class RawSql implements Serializable {
|
||||
* Construct for unparsed sql.
|
||||
*/
|
||||
protected ColumnMapping() {
|
||||
this.queryHashCode = 0;
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
@@ -443,17 +426,13 @@ public final class RawSql implements Serializable {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
//this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<String, Column>();
|
||||
|
||||
int hc = 31;
|
||||
|
||||
int pos = 0;
|
||||
for (String prop : propertyNames) {
|
||||
hc = 31 * hc + prop.hashCode();
|
||||
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
|
||||
}
|
||||
propertyColumnMap = dbColumnMap;
|
||||
this.queryHashCode = hc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,20 +443,28 @@ public final class RawSql implements Serializable {
|
||||
this.parsed = parsed;
|
||||
this.dbColumnMap = dbColumnMap;
|
||||
|
||||
int hc = ColumnMapping.class.getName().hashCode();
|
||||
|
||||
HashMap<String, Column> pcMap = new HashMap<String, Column>();
|
||||
HashMap<String, String> pMap = new HashMap<String, String>();
|
||||
|
||||
for (Column c : dbColumnMap.values()) {
|
||||
pMap.put(c.getPropertyName(), c.getDbColumn());
|
||||
pcMap.put(c.getPropertyName(), c);
|
||||
hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode());
|
||||
hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode());
|
||||
}
|
||||
this.propertyMap = Collections.unmodifiableMap(pMap);
|
||||
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
|
||||
this.queryHashCode = hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ColumnMapping that = (ColumnMapping) o;
|
||||
return dbColumnMap.equals(that.dbColumnMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return dbColumnMap.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,16 +508,6 @@ public final class RawSql implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query hash for this column mapping.
|
||||
*/
|
||||
public int queryHash() {
|
||||
if (queryHashCode == 0) {
|
||||
throw new RuntimeException("Bug: queryHashCode == 0");
|
||||
}
|
||||
return queryHashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Columns where supplied by parsing the sql select
|
||||
* clause.
|
||||
@@ -585,6 +562,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) {
|
||||
|
||||
@@ -645,6 +625,27 @@ public final class RawSql implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Column that = (Column) o;
|
||||
if (indexPos != that.indexPos) return false;
|
||||
if (!dbColumn.equals(that.dbColumn)) return false;
|
||||
if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false;
|
||||
return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = indexPos;
|
||||
result = 31 * result + dbColumn.hashCode();
|
||||
result = 31 * result + (dbAlias != null ? dbAlias.hashCode() : 0);
|
||||
result = 31 * result + (propertyName != null ? propertyName.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return dbColumn + "->" + propertyName;
|
||||
}
|
||||
@@ -696,4 +697,39 @@ public final class RawSql implements Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A key for the RawSql object using for the query plan.
|
||||
*/
|
||||
public static final class Key {
|
||||
|
||||
private final boolean parsed;
|
||||
private final ColumnMapping columnMapping;
|
||||
private final String unParsedSql;
|
||||
|
||||
Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) {
|
||||
this.parsed = parsed;
|
||||
this.unParsedSql = unParsedSql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key that = (Key) o;
|
||||
return parsed == that.parsed
|
||||
&& columnMapping.equals(that.columnMapping)
|
||||
&& unParsedSql.equals(that.unParsedSql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (parsed ? 1 : 0);
|
||||
result = 31 * result + columnMapping.hashCode();
|
||||
result = 31 * result + unParsedSql.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.annotation.DocStoreMode;
|
||||
import com.avaje.ebean.config.DocStoreConfig;
|
||||
import com.avaje.ebean.config.PersistBatch;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
@@ -81,6 +83,29 @@ public interface Transaction extends Closeable {
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Set the behavior for document store updates on this transaction.
|
||||
* <p>
|
||||
* For example, set the mode to DocStoreEvent.IGNORE for this transaction and
|
||||
* then any changes via this transaction are not sent to the doc store. This
|
||||
* would be used when doing large bulk inserts into the database and we want
|
||||
* to control how that is sent to the document store.
|
||||
* </p>
|
||||
*/
|
||||
void setDocStoreMode(DocStoreMode mode);
|
||||
|
||||
/**
|
||||
* Set the batch size to use for sending messages to the document store.
|
||||
* <p>
|
||||
* You might set this if you know the changes in this transaction result in especially large or
|
||||
* especially small payloads and want to adjust the batch size to match.
|
||||
* </p>
|
||||
* <p>
|
||||
* Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
|
||||
* </p>
|
||||
*/
|
||||
void setDocStoreBatchSize(int batchSize);
|
||||
|
||||
/**
|
||||
* Explicitly turn off or on the cascading nature of save() and delete(). This
|
||||
* gives the developer exact control over what beans are saved and deleted
|
||||
|
||||
@@ -9,6 +9,10 @@ import java.lang.annotation.Target;
|
||||
* For a timestamp property that is set to the datetime when the entity is
|
||||
* created/inserted.
|
||||
* <p>
|
||||
* This is effectively an alias for @WhenCreated which was added as it hints
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
* <p>
|
||||
* An alternative to using this annotation would be to use insertable=false,
|
||||
* updateable=false with @Column and have the DB insert the current time
|
||||
* (default value on the DB column is SYSTIME etc).
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A database table or column comment.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
|
||||
public @interface DbComment {
|
||||
|
||||
/**
|
||||
* The database table or column comment.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to indicate that a particular string property should be treated as a 'code' and not analysed for text searching.
|
||||
* <p>
|
||||
* By default all Id properties and all Enum properties are treated as 'code' and not analysed.
|
||||
* </p>
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DocCode {
|
||||
|
||||
/**
|
||||
* Set to true to have the property additionally stored separately from _source.
|
||||
*/
|
||||
boolean store() default false;
|
||||
|
||||
/**
|
||||
* Set a boost value specific to this property.
|
||||
*/
|
||||
float boost() default 1;
|
||||
|
||||
/**
|
||||
* Set a value to use instead of null.
|
||||
*/
|
||||
String nullValue() default "";
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify the entity type maps to a document store (like ElasticSearch).
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DocMapping {
|
||||
|
||||
/**
|
||||
* The property name the mapping applies to.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Mapping options.
|
||||
*/
|
||||
DocProperty options();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify the entity type maps to a document store (like ElasticSearch).
|
||||
*/
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DocProperty {
|
||||
|
||||
/**
|
||||
* Set this to true to indicate that this property should be un-analysed.
|
||||
*/
|
||||
boolean code() default false;
|
||||
|
||||
/**
|
||||
* Set this to true to get an additional un-analysed 'raw' field to use for sorting etc.
|
||||
*/
|
||||
boolean sortable() default false;
|
||||
|
||||
/**
|
||||
* Set to true to have the property additionally stored separately from _source.
|
||||
*/
|
||||
boolean store() default false;
|
||||
|
||||
/**
|
||||
* Set a boost value specific to this property.
|
||||
*/
|
||||
float boost() default 1;
|
||||
|
||||
/**
|
||||
* Set a value to use instead of null.
|
||||
*/
|
||||
String nullValue() default "";
|
||||
|
||||
/**
|
||||
* Set this to false to exclude this from the _all property.
|
||||
*/
|
||||
boolean includeInAll() default true;
|
||||
|
||||
/**
|
||||
* The analyzer to use.
|
||||
*/
|
||||
String analyzer() default "";
|
||||
|
||||
/**
|
||||
* The analyzer to use for searches.
|
||||
*/
|
||||
String searchAnalyzer() default "";
|
||||
|
||||
/**
|
||||
* The index options for this property.
|
||||
*/
|
||||
Option options() default Option.DEFAULT;
|
||||
|
||||
/**
|
||||
* Set this to false such that doc values are not stored separately for this property.
|
||||
*/
|
||||
boolean docValues() default true;
|
||||
|
||||
/**
|
||||
* Set a copyTo field.
|
||||
*/
|
||||
String copyTo() default "";
|
||||
|
||||
/**
|
||||
* Set to false to disable the field from indexing, it will only be get/set in _source.
|
||||
*/
|
||||
boolean enabled() default true;
|
||||
|
||||
/**
|
||||
* Set to false such that norms are not stored.
|
||||
*/
|
||||
boolean norms() default true;
|
||||
|
||||
/**
|
||||
* Index options for a property.
|
||||
*/
|
||||
enum Option {
|
||||
|
||||
/**
|
||||
* Only index the doc number.
|
||||
*/
|
||||
DOCS,
|
||||
|
||||
/**
|
||||
* Doc number and term frequencies are indexed.
|
||||
*/
|
||||
FREQS,
|
||||
|
||||
/**
|
||||
* Doc number, term frequencies and term positions are indexed.
|
||||
*/
|
||||
POSITIONS,
|
||||
|
||||
/**
|
||||
* Doc number, term frequencies, term positions and start/end offsets are indexed.
|
||||
*/
|
||||
OFFSETS,
|
||||
|
||||
/**
|
||||
* Use the default which means analysed string properties use POSITIONS as the default and all other types use DOCS.
|
||||
*/
|
||||
DEFAULT
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to indicate that a particular string property should support sorting. What this typically means is that
|
||||
* for ElasticSearch an additional 'raw' field is added that stores the un-analysed value. This un-analysed value
|
||||
* can be used for sorting etc and the original field used for text searching.
|
||||
* <p>
|
||||
* For example, customer name and product name are good candidates for marking with @DocSortable.
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DocSortable {
|
||||
|
||||
/**
|
||||
* Set to true to have the property additionally stored separately from _source.
|
||||
*/
|
||||
boolean store() default false;
|
||||
|
||||
/**
|
||||
* Set a boost value specific to this property.
|
||||
*/
|
||||
float boost() default 1;
|
||||
|
||||
/**
|
||||
* Set a value to use instead of null.
|
||||
*/
|
||||
String nullValue() default "";
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify the entity type maps to a document store (like ElasticSearch).
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DocStore {
|
||||
|
||||
/**
|
||||
* A unique Id used when queuing reindex events.
|
||||
*/
|
||||
String queueId() default "";
|
||||
|
||||
/**
|
||||
* The ElasticSearch index name. If left unspecified the short name of the bean type is used.
|
||||
*/
|
||||
String indexName() default "";
|
||||
|
||||
/**
|
||||
* The ElasticSearch index type. If left unspecified the short name of the bean type is used.
|
||||
*/
|
||||
String indexType() default "";
|
||||
|
||||
/**
|
||||
* The number of shards this index should use.
|
||||
*/
|
||||
int shards() default 0;
|
||||
|
||||
/**
|
||||
* The number of replicas this index should use.
|
||||
*/
|
||||
int replicas() default 0;
|
||||
|
||||
/**
|
||||
* Additional mapping that can be defined on the properties.
|
||||
*/
|
||||
DocMapping[] mapping() default {};
|
||||
|
||||
/**
|
||||
* Specify the behavior when bean Insert, Update, Delete events occur.
|
||||
*/
|
||||
DocStoreMode persist() default DocStoreMode.DEFAULT;
|
||||
|
||||
/**
|
||||
* Specify the behavior when bean Insert occurs.
|
||||
*/
|
||||
DocStoreMode insert() default DocStoreMode.DEFAULT;
|
||||
|
||||
/**
|
||||
* Specify the behavior when bean Update occurs.
|
||||
*/
|
||||
DocStoreMode update() default DocStoreMode.DEFAULT;
|
||||
|
||||
/**
|
||||
* Specify the behavior when bean Delete occurs.
|
||||
*/
|
||||
DocStoreMode delete() default DocStoreMode.DEFAULT;
|
||||
|
||||
/**
|
||||
* Specify to include only some properties in the doc store document.
|
||||
* <p>
|
||||
* If this is left as default then all scalar properties are included,
|
||||
* all @ManyToOne properties are included with just the nested id property
|
||||
* and no @OneToMany properties are included.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that typically DocStoreEmbedded is used on @ManyToOne and @OneToMany
|
||||
* properties to indicate what part of the nested document should be included.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // only include the customer id and name
|
||||
* @DocStore(doc = "id,name")
|
||||
* @Entity @Table(name = "o_order")
|
||||
* public class Customer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
String doc() default "";
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify the property is included in the parent document store index.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
*
|
||||
* @DocStore
|
||||
* @Entity @Table(name = "o_order")
|
||||
* public class Order {
|
||||
*
|
||||
* ...
|
||||
* // include some customer details including
|
||||
* // nested billingAddress
|
||||
* @DocStoreEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
|
||||
* @ManyToOne
|
||||
* Customer customer;
|
||||
*
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Target({ ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DocStoreEmbedded {
|
||||
|
||||
/**
|
||||
* The properties on the embedded bean to include in the index.
|
||||
*/
|
||||
String doc() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
/**
|
||||
* Defines the behavior options when a Insert, Update or Delete event occurs
|
||||
* on a bean with an associated ElasticSearch index.
|
||||
* <p>
|
||||
* For some indexes or some transactions it can be beneficial to queue the event
|
||||
* for later processing rather than look to update ElasticSearch at that time.
|
||||
* </p>
|
||||
*/
|
||||
public enum DocStoreMode {
|
||||
|
||||
/**
|
||||
* Add the event to the queue for processing later (delaying the update to the document store).
|
||||
*/
|
||||
QUEUE,
|
||||
|
||||
/**
|
||||
* Update the document store when transaction succeeds.
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* Ignore the event and not update the document store.
|
||||
* <p>
|
||||
* This can be used on a index or for a transaction where you want to have more
|
||||
* manual programmatic control over the updating of the document store. Say you want to
|
||||
* IGNORE on a particular transaction and instead manually queue a bulk update.
|
||||
* </p>
|
||||
*/
|
||||
IGNORE,
|
||||
|
||||
/**
|
||||
* The actual mode of QUEUE, UPDATE or IGNORE is set from the default configuration.
|
||||
*/
|
||||
DEFAULT
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a boolean property on a @Draftable bean that indicates if the bean instance is a 'draft' or 'live' bean.
|
||||
* The property is transient and has no underlying DB column.
|
||||
* <p>
|
||||
* For beans returned from an <code>asDraft()</code> query this property will be set to true.
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Draft {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a boolean property on a @Draftable bean that only exists on the 'draft' table
|
||||
* and is used to detect when a draft has unpublished changes.
|
||||
* <p>
|
||||
* This property will automatically have it's value set to true when a draft is saved and
|
||||
* automatically have it's value set to false when the bean is published.
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DraftDirty {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a property on a @Draftable bean that only exists on the 'draft' and not the 'live' table.
|
||||
* <p>
|
||||
* Typically this would be used on a property that is used as part of application 'workflow' such as
|
||||
* a publish workflow status or when publish timestamp.
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DraftOnly {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a property on a @Draftable bean that is set to null on the 'draft bean' on publish.
|
||||
* <p>
|
||||
* This is expected to be put on properties that get 'reset' or 'cleared' after a publish.
|
||||
* These properties might represent a publish comment or publish timestamp.
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DraftReset {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to indicate an entity bean that has 'draftable' support.
|
||||
* <p>
|
||||
* This means that a second set of tables is created to hold draft versions of
|
||||
* the rows and that these can then be published which effectively copies/transfers
|
||||
* the values from the 'draft' table to the 'live' table.
|
||||
* </p>
|
||||
* <p>
|
||||
* Ebean Query supports 'find as draft' which builds the resulting object graph using
|
||||
* the draft tables. This object graph is typically edited, approved in some application
|
||||
* specific manor and then published.
|
||||
* </p>
|
||||
* <p>
|
||||
* EbeanServer has a publish method which transfers/copies the draft object graph to
|
||||
* the 'live' tables.
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Draftable {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to indicate an entity bean that has 'draftable' support but it not a 'top level'
|
||||
* (or root level) bean but instead child related to another @Draftable entity bean.
|
||||
* <p>
|
||||
* Relationships to @DraftableElements (@OneToMany, @ManyToMany etc) are automatically
|
||||
* deemed to have Cascade.ALL for save and delete (as well as orphan removal mode).
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface DraftableElement {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Used to indicate a property on an entity bean used to control 'soft delete'
|
||||
* (also known as 'logical delete').
|
||||
* <p>
|
||||
* The property should be of type boolean.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @SoftDelete
|
||||
* boolean deleted;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface SoftDelete {
|
||||
|
||||
}
|
||||
@@ -8,6 +8,10 @@ import java.lang.annotation.Target;
|
||||
/**
|
||||
* For a timestamp property that is set to the datetime when the entity was last
|
||||
* updated.
|
||||
* <p>
|
||||
* This is effectively an alias for @WhenModified which was added as it hints
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -11,7 +11,6 @@ import java.lang.annotation.Target;
|
||||
* This is effectively an alias for @UpdatedTimestamp and added to hint
|
||||
* towards a better naming convention (WhenCreated, WhenModified).
|
||||
* </p>
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
||||
@@ -31,6 +31,21 @@ public interface BeanCollection<E> extends Serializable {
|
||||
ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* Load bean from another collection.
|
||||
*/
|
||||
void loadFrom(BeanCollection<?> other);
|
||||
|
||||
/**
|
||||
* Add a bean to the list/set with modifyListen notification.
|
||||
*/
|
||||
void addBean(E bean);
|
||||
|
||||
/**
|
||||
* Remove a bean to the list/set with modifyListen notification.
|
||||
*/
|
||||
void removeBean(E bean);
|
||||
|
||||
/**
|
||||
* Reset the collection back to an empty state ready for reloading.
|
||||
* <p>
|
||||
@@ -115,6 +130,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.
|
||||
*/
|
||||
|
||||
@@ -12,5 +12,5 @@ public interface BeanCollectionAdd {
|
||||
/**
|
||||
* Add a loaded bean to the collection.
|
||||
*/
|
||||
void addBean(EntityBean bean);
|
||||
void addEntityBean(EntityBean bean);
|
||||
}
|
||||
|
||||
@@ -73,16 +73,6 @@ public interface EntityBean extends Serializable {
|
||||
*/
|
||||
EntityBeanIntercept _ebean_intercept();
|
||||
|
||||
/**
|
||||
* Create a copy of this entity bean.
|
||||
* <p>
|
||||
* This occurs when a bean is changed. The copy represents the bean as it was
|
||||
* initially (oldValues) before any changes where made. This is used for
|
||||
* optimistic concurrency control.
|
||||
* </p>
|
||||
*/
|
||||
Object _ebean_createCopy();
|
||||
|
||||
/**
|
||||
* Set the value of a field of an entity bean of this type.
|
||||
* <p>
|
||||
|
||||
@@ -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>
|
||||
@@ -218,6 +219,18 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.fullyLoadedBean = fullyLoadedBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check each property to see if the bean is partially loaded.
|
||||
*/
|
||||
public boolean isPartial() {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
if (!loadedProps[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean has been directly modified (it has oldValues) or
|
||||
* if any embedded beans are either new or dirty (and hence need saving).
|
||||
@@ -342,18 +355,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,7 +481,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void setLoadedProperty(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the property is loaded.
|
||||
*/
|
||||
@@ -507,7 +513,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
private void setChangedProperty(int propertyIndex) {
|
||||
public void setChangedProperty(int propertyIndex) {
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
@@ -566,6 +572,23 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the array of flags indicating the dirty properties.
|
||||
*/
|
||||
public boolean[] getDirtyProperties() {
|
||||
int len = getPropertyLength();
|
||||
boolean[] dirties = new boolean[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
dirties[i] = true;
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
// an embedded property has been changed - recurse
|
||||
dirties[i] = true;
|
||||
}
|
||||
}
|
||||
return dirties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of dirty properties.
|
||||
*/
|
||||
@@ -748,7 +771,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 +786,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
|
||||
|
||||
@@ -10,31 +10,31 @@ package com.avaje.ebean.bean;
|
||||
public interface PersistenceContext {
|
||||
|
||||
/**
|
||||
* Put the entity bean into the PersistanceContext.
|
||||
* Put the entity bean into the PersistenceContext.
|
||||
*/
|
||||
void put(Object id, Object bean);
|
||||
void put(Class<?> rootType, Object id, Object bean);
|
||||
|
||||
/**
|
||||
* Put the entity bean into the PersistanceContext if one is not already
|
||||
* Put the entity bean into the PersistenceContext if one is not already
|
||||
* present (for this id).
|
||||
* <p>
|
||||
* Returns an existing entity bean (if one is already there) and otherwise
|
||||
* returns null.
|
||||
* </p>
|
||||
*/
|
||||
Object putIfAbsent(Object id, Object bean);
|
||||
Object putIfAbsent(Class<?> rootType, Object id, Object bean);
|
||||
|
||||
/**
|
||||
* Return an object given its type and unique id.
|
||||
*/
|
||||
Object get(Class<?> beanType, Object uid);
|
||||
Object get(Class<?> rootType, Object uid);
|
||||
|
||||
/**
|
||||
* Get the bean from the persistence context also checked to see if it had
|
||||
* been previously deleted (if so then you also can't hit the L2 cache to
|
||||
* fetch the bean for this particular persistence context).
|
||||
*/
|
||||
WithOption getWithOption(Class<?> beanType, Object uid);
|
||||
WithOption getWithOption(Class<?> rootType, Object uid);
|
||||
|
||||
/**
|
||||
* Clear all the references.
|
||||
@@ -44,22 +44,22 @@ public interface PersistenceContext {
|
||||
/**
|
||||
* Clear all the references for a given type of entity bean.
|
||||
*/
|
||||
void clear(Class<?> beanType);
|
||||
void clear(Class<?> rootType);
|
||||
|
||||
/**
|
||||
* Clear the reference to a specific entity bean.
|
||||
*/
|
||||
void clear(Class<?> beanType, Object uid);
|
||||
void clear(Class<?> rootType, Object uid);
|
||||
|
||||
/**
|
||||
* Clear the reference as a result of an entity being deleted.
|
||||
*/
|
||||
void deleted(Class<?> beanType, Object id);
|
||||
void deleted(Class<?> rootType, Object id);
|
||||
|
||||
/**
|
||||
* Return the number of beans of the given type in the persistence context.
|
||||
*/
|
||||
int size(Class<?> beanType);
|
||||
int size(Class<?> rootType);
|
||||
|
||||
/**
|
||||
* Wrapper on a bean to also indicate if a bean has been deleted.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.avaje.ebean.bean;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
|
||||
/**
|
||||
* Utility to find the root bean type.
|
||||
*/
|
||||
public class PersistenceContextUtil {
|
||||
|
||||
/**
|
||||
* Find and return the root bean type for the given class.
|
||||
*/
|
||||
public static Class<?> root(Class<?> beanType) {
|
||||
Class<?> parent = beanType.getSuperclass();
|
||||
while (parent != null && parent.isAnnotationPresent(Entity.class)) {
|
||||
beanType = parent;
|
||||
parent = parent.getSuperclass();
|
||||
}
|
||||
return beanType;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.avaje.ebean.common;
|
||||
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
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 +13,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.
|
||||
*/
|
||||
@@ -60,10 +61,19 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addBean(EntityBean bean) {
|
||||
public void addEntityBean(EntityBean bean) {
|
||||
list.add((E) bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadFrom(BeanCollection<?> other) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<E>();
|
||||
}
|
||||
list.addAll((Collection<? extends E>) other.getActualDetails());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void internalAdd(Object bean) {
|
||||
if (list == null) {
|
||||
@@ -74,6 +84,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 +116,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 +151,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
public Collection<E> getActualDetails() {
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<?> getActualEntries() {
|
||||
return list;
|
||||
@@ -206,6 +223,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBean(E bean) {
|
||||
add(bean);
|
||||
}
|
||||
|
||||
public boolean add(E o) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
@@ -319,6 +341,13 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
return list.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBean(E bean) {
|
||||
if (list.remove(bean)) {
|
||||
getModifyHolder().modifyRemoval(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public E remove(int index) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean.common;
|
||||
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.BeanCollectionLoader;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
|
||||
@@ -51,6 +52,14 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
return !touched && (map == null || map.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadFrom(BeanCollection<?> other) {
|
||||
BeanMap<K,E> otherMap = (BeanMap<K,E>)other;
|
||||
internalPutNull();
|
||||
map.putAll(otherMap.getActualMap());
|
||||
}
|
||||
|
||||
public void internalPutNull() {
|
||||
if (map == null) {
|
||||
map = new LinkedHashMap<K, E>();
|
||||
@@ -66,7 +75,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");
|
||||
}
|
||||
@@ -272,6 +292,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
map.putAll(puts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBean(E bean) {
|
||||
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBean(E bean) {
|
||||
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
|
||||
}
|
||||
|
||||
public E remove(Object key) {
|
||||
checkReadOnly();
|
||||
init();
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package com.avaje.ebean.common;
|
||||
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
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.
|
||||
*/
|
||||
@@ -53,10 +54,26 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addBean(EntityBean bean) {
|
||||
public void addEntityBean(EntityBean bean) {
|
||||
set.add((E) bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadFrom(BeanCollection<?> other) {
|
||||
if (set == null) {
|
||||
set = new LinkedHashSet<E>();
|
||||
}
|
||||
set.addAll((Collection<? extends E>) other.getActualDetails());
|
||||
}
|
||||
|
||||
@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 +124,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) {
|
||||
@@ -174,6 +191,18 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
return set.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBean(E bean) {
|
||||
add(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBean(E bean) {
|
||||
if (set.remove(bean)) {
|
||||
getModifyHolder().modifyRemoval(bean);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------//
|
||||
// proxy method for map
|
||||
// -----------------------------------------------------//
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
|
||||
/**
|
||||
* Helper to find classes taking into account the context class loader.
|
||||
*/
|
||||
public class ClassLoadConfig {
|
||||
|
||||
protected final ClassLoaderContext context;
|
||||
|
||||
/**
|
||||
* Construct with the default classLoader search with context classLoader first.
|
||||
*/
|
||||
public ClassLoadConfig() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the classLoader to use for class detection and new instance creation.
|
||||
*/
|
||||
public ClassLoadConfig(ClassLoader classLoader) {
|
||||
this.context = new ClassLoaderContext(classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the Java.time types are available and should be supported.
|
||||
*/
|
||||
public boolean isJavaTimePresent() {
|
||||
return isPresent("java.time.LocalDate");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the Joda types are available and should be supported.
|
||||
*/
|
||||
public boolean isJodaTimePresent() {
|
||||
return isPresent("org.joda.time.LocalDateTime");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax validation annotations like Size and NotNull are present.
|
||||
*/
|
||||
public boolean isJavaxValidationAnnotationsPresent() {
|
||||
return isPresent("javax.validation.constraints.NotNull");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Jackson annotations like JsonIgnore are present.
|
||||
*/
|
||||
public boolean isJacksonAnnotationsPresent() {
|
||||
return isPresent("com.fasterxml.jackson.annotation.JsonIgnore");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Jackson ObjectMapper is present.
|
||||
*/
|
||||
public boolean isJacksonObjectMapperPresent() {
|
||||
return isPresent("com.fasterxml.jackson.databind.ObjectMapper");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of the class using the default constructor.
|
||||
*/
|
||||
public Object newInstance(String className) {
|
||||
|
||||
try {
|
||||
Class<?> cls = forName(className);
|
||||
return cls.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Error constructing " + className, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given class is present.
|
||||
*/
|
||||
protected boolean isPresent(String className) {
|
||||
try {
|
||||
forName(className);
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
// Class or one of its dependencies is not present...
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a class taking into account a context class loader (if present).
|
||||
*/
|
||||
protected Class<?> forName(String name) throws ClassNotFoundException {
|
||||
return context.forName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the classLoader to use for service loading etc.
|
||||
*/
|
||||
public ClassLoader getClassLoader() {
|
||||
return context.getClassLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the preferred, caller and context class loaders.
|
||||
*/
|
||||
protected class ClassLoaderContext {
|
||||
|
||||
/**
|
||||
* Optional - if set only use this classLoader (no fallback).
|
||||
*/
|
||||
protected final ClassLoader preferredLoader;
|
||||
|
||||
protected final ClassLoader contextLoader;
|
||||
|
||||
protected final ClassLoader callerLoader;
|
||||
|
||||
ClassLoaderContext(ClassLoader preferredLoader) {
|
||||
this.preferredLoader = preferredLoader;
|
||||
this.callerLoader = ServerConfig.class.getClassLoader();
|
||||
this.contextLoader = contextLoader();
|
||||
}
|
||||
|
||||
ClassLoader contextLoader() {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
return (loader != null) ? loader: callerLoader;
|
||||
}
|
||||
|
||||
Class<?> forName(String name) throws ClassNotFoundException {
|
||||
|
||||
if (preferredLoader != null) {
|
||||
// only use the explicitly set classLoader
|
||||
return classForName(name, preferredLoader);
|
||||
}
|
||||
try {
|
||||
// try the context loader first
|
||||
return classForName(name, contextLoader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (callerLoader == contextLoader) {
|
||||
throw e;
|
||||
} else {
|
||||
// fallback to the caller classLoader
|
||||
return classForName(name, callerLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
|
||||
return Class.forName(name, true, classLoader);
|
||||
}
|
||||
|
||||
ClassLoader getClassLoader() {
|
||||
return preferredLoader != null ? preferredLoader : contextLoader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,324 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.config.dbplatform.DbPlatformName;
|
||||
import com.avaje.ebean.dbmigration.DbMigration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Configuration for the DB migration processing.
|
||||
*/
|
||||
public class DbMigrationConfig {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
|
||||
|
||||
/**
|
||||
* The database platform to generate migration DDL for.
|
||||
*/
|
||||
protected DbPlatformName platform;
|
||||
|
||||
/**
|
||||
* Set to true if the DB migration should be generated on server start.
|
||||
*/
|
||||
protected boolean generate;
|
||||
|
||||
/**
|
||||
* The migration version name (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
protected String version;
|
||||
|
||||
/**
|
||||
* Description text that can be appended to the version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Resource path for the migration xml and sql.
|
||||
* Typically you would change 'app' to be a better/more unique.
|
||||
*/
|
||||
private String resourcePath = "dbmigration/app";
|
||||
protected String migrationPath = "dbmigration";
|
||||
|
||||
/**
|
||||
* Subdirectory the model xml files go into.
|
||||
*/
|
||||
protected String modelPath = "model";
|
||||
|
||||
protected String applySuffix = ".sql";
|
||||
|
||||
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.
|
||||
*
|
||||
* We typically need to explicitly specify this as migration can often be generated
|
||||
* when running against H2.
|
||||
*/
|
||||
public DbPlatformName getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB platform to generate migration DDL for.
|
||||
*/
|
||||
public void setPlatform(DbPlatformName platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource path for db migrations.
|
||||
*/
|
||||
public String getResourcePath() {
|
||||
return resourcePath;
|
||||
public String getMigrationPath() {
|
||||
return migrationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the resource path for db migrations.
|
||||
* <p>
|
||||
* Typically this would be something like "dbmigration/myapp" where myapp gives it a
|
||||
* unique resource path in the case there are multiple EbeanServer applications in the
|
||||
* single classpath.
|
||||
* The default of "dbmigration" is reasonable in most cases. You may look to set this
|
||||
* to be something like "dbmigration/myapp" where myapp gives it a unique resource path
|
||||
* in the case there are multiple EbeanServer applications in the single classpath.
|
||||
* </p>
|
||||
*/
|
||||
public void setResourcePath(String resourcePath) {
|
||||
this.resourcePath = resourcePath;
|
||||
public void setMigrationPath(String migrationPath) {
|
||||
this.migrationPath = migrationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path for the model files (defaults to model).
|
||||
*/
|
||||
public String getModelPath() {
|
||||
return modelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relative path for the model files.
|
||||
*/
|
||||
public void setModelPath(String modelPath) {
|
||||
this.modelPath = modelPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the model suffix (defaults to model.xml)
|
||||
*/
|
||||
public String getModelSuffix() {
|
||||
return modelSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model suffix.
|
||||
*/
|
||||
public void setModelSuffix(String modelSuffix) {
|
||||
this.modelSuffix = modelSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the apply script suffix (defaults to sql).
|
||||
*/
|
||||
public String getApplySuffix() {
|
||||
return applySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the apply script suffix (defaults to sql).
|
||||
*/
|
||||
public void setApplySuffix(String applySuffix) {
|
||||
this.applySuffix = applySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the generated file comment should be included.
|
||||
*/
|
||||
public boolean isIncludeGeneratedFileComment() {
|
||||
return includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the generated file comment should be included.
|
||||
*/
|
||||
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
|
||||
this.includeGeneratedFileComment = includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration version (or "next") to generate pending drops for.
|
||||
*/
|
||||
public String getGeneratePendingDrop() {
|
||||
return generatePendingDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration version (or "next") to generate pending drops for.
|
||||
*/
|
||||
public void setGeneratePendingDrop(String generatePendingDrop) {
|
||||
this.generatePendingDrop = generatePendingDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration version.
|
||||
* <p>
|
||||
* Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration name.
|
||||
* <p>
|
||||
* Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the model, rollback and drop paths to be empty such that all the migration files are generated
|
||||
* into a single directory.
|
||||
*/
|
||||
public void singleDirectory() {
|
||||
this.modelPath = "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the PropertiesWrapper.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper properties) {
|
||||
resourcePath = properties.get("migration.resourcePath", resourcePath);
|
||||
|
||||
migrationPath = properties.get("migration.migrationPath", migrationPath);
|
||||
if (properties.getBoolean("migration.singleDirectory", false)) {
|
||||
singleDirectory();
|
||||
} else {
|
||||
modelPath = properties.get("migration.modelPath", modelPath);
|
||||
}
|
||||
applySuffix = properties.get("migration.applySuffix", applySuffix);
|
||||
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);
|
||||
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the migration should be generated.
|
||||
* <p>
|
||||
* It is expected that when an environment variable <code>ddl.migration.enabled</code>
|
||||
* is set to <code>true</code> then the DB migration will generate the migration DDL.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isGenerateOnStart() {
|
||||
|
||||
// environment properties take precedence
|
||||
String envGenerate = readEnvironment("ddl.migration.generate");
|
||||
if (envGenerate != null) {
|
||||
return "true".equalsIgnoreCase(envGenerate.trim());
|
||||
}
|
||||
return generate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by EbeanServer on start.
|
||||
*
|
||||
* <p>
|
||||
* If enabled this generates the migration xml and DDL scripts.
|
||||
* </p>
|
||||
*/
|
||||
public void generateOnStart(EbeanServer server) {
|
||||
|
||||
if (isGenerateOnStart()) {
|
||||
if (platform == null) {
|
||||
logger.warn("No platform set for migration DDL generation");
|
||||
} else {
|
||||
// generate the migration xml and platform specific DDL
|
||||
DbMigration migration = new DbMigration(server);
|
||||
migration.setPlatform(platform);
|
||||
try {
|
||||
migration.generateMigration();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error generating DB migration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration version (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
public String getVersion() {
|
||||
String envVersion = readEnvironment("ddl.migration.version");
|
||||
if (!isEmpty(envVersion)) {
|
||||
return envVersion.trim();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration name which is short description text that can be appended to
|
||||
* the migration version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* </p>
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
* </p>
|
||||
*/
|
||||
public String getName() {
|
||||
String envName = readEnvironment("ddl.migration.name");
|
||||
if (!isEmpty(envName)) {
|
||||
return envName.trim();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system or environment property.
|
||||
*/
|
||||
protected String readEnvironment(String key) {
|
||||
|
||||
String val = System.getProperty(key);
|
||||
if (val == null) {
|
||||
val = System.getenv(key);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the string is null or empty.
|
||||
*/
|
||||
protected boolean isEmpty(String val) {
|
||||
return val == null || val.trim().isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
import com.avaje.ebean.annotation.DocStoreMode;
|
||||
|
||||
/**
|
||||
* Configuration for the Document store integration (e.g. ElasticSearch).
|
||||
*/
|
||||
public class DocStoreConfig {
|
||||
|
||||
/**
|
||||
* True when the Document store integration is active/on.
|
||||
*/
|
||||
protected boolean active;
|
||||
|
||||
/**
|
||||
* Set to true means Ebean will generate mapping files on startup.
|
||||
*/
|
||||
protected boolean generateMapping;
|
||||
|
||||
/**
|
||||
* When true the Document store should drop and re-create document indexes.
|
||||
*/
|
||||
protected boolean dropCreate;
|
||||
|
||||
/**
|
||||
* When true the Document store should create any document indexes that don't already exist.
|
||||
*/
|
||||
protected boolean create;
|
||||
|
||||
/**
|
||||
* The URL of the Document store server. For example: http://localhost:9200.
|
||||
*/
|
||||
protected String url;
|
||||
|
||||
/**
|
||||
* The default mode used by indexes.
|
||||
*/
|
||||
protected DocStoreMode persist = DocStoreMode.UPDATE;
|
||||
|
||||
/**
|
||||
* The default batch size to use for the Bulk API calls.
|
||||
*/
|
||||
protected int bulkBatchSize = 1000;
|
||||
|
||||
/**
|
||||
* Resource path for the Document store mapping files.
|
||||
*/
|
||||
protected String mappingPath;
|
||||
|
||||
/**
|
||||
* Suffix used for mapping files.
|
||||
*/
|
||||
protected String mappingSuffix;
|
||||
|
||||
/**
|
||||
* Location of resources that mapping files are generated into.
|
||||
*/
|
||||
protected String pathToResources = "src/main/resources";
|
||||
|
||||
/**
|
||||
* Return true if the Document store (ElasticSearch) integration is active.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
String systemValue = System.getProperty("ebean.docstore.active");
|
||||
if (systemValue != null) {
|
||||
return Boolean.parseBoolean(systemValue);
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to make the Document store (ElasticSearch) integration active.
|
||||
*/
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URL to the Document store.
|
||||
*/
|
||||
public String getUrl() {
|
||||
String systemValue = System.getProperty("ebean.docstore.url");
|
||||
if (systemValue != null) {
|
||||
return systemValue;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL to the Document store server.
|
||||
* <p>
|
||||
* For a local ElasticSearch server this would be: http://localhost:9200
|
||||
*/
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Ebean should generate mapping files on server startup.
|
||||
*/
|
||||
public boolean isGenerateMapping() {
|
||||
return generateMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Ebean should generate mapping files on server startup.
|
||||
*/
|
||||
public void setGenerateMapping(boolean generateMapping) {
|
||||
this.generateMapping = generateMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the document store should recreate mapped indexes.
|
||||
*/
|
||||
public boolean isDropCreate() {
|
||||
return dropCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the document store should recreate mapped indexes.
|
||||
*/
|
||||
public void setDropCreate(boolean dropCreate) {
|
||||
this.dropCreate = dropCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create true if the document store should create mapped indexes that don't yet exist.
|
||||
* This is only used if dropCreate is false.
|
||||
*/
|
||||
public boolean isCreate() {
|
||||
return create;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the document store should create mapped indexes that don't yet exist.
|
||||
* This is only used if dropCreate is false.
|
||||
*/
|
||||
public void setCreate(boolean create) {
|
||||
this.create = create;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default batch size to use for calls to the Bulk API.
|
||||
*/
|
||||
public int getBulkBatchSize() {
|
||||
return bulkBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default batch size to use for calls to the Bulk API.
|
||||
* <p>
|
||||
* The batch size can be set on a transaction via {@link com.avaje.ebean.Transaction#setDocStoreBatchSize(int)}.
|
||||
* </p>
|
||||
*/
|
||||
public void setBulkBatchSize(int bulkBatchSize) {
|
||||
this.bulkBatchSize = bulkBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping path.
|
||||
*/
|
||||
public String getMappingPath() {
|
||||
return mappingPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mapping path.
|
||||
*/
|
||||
public void setMappingPath(String mappingPath) {
|
||||
this.mappingPath = mappingPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping suffix.
|
||||
*/
|
||||
public String getMappingSuffix() {
|
||||
return mappingSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mapping suffix.
|
||||
*/
|
||||
public void setMappingSuffix(String mappingSuffix) {
|
||||
this.mappingSuffix = mappingSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative file system path to resources when generating mapping files.
|
||||
*/
|
||||
public String getPathToResources() {
|
||||
return pathToResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the relative file system path to resources when generating mapping files.
|
||||
*/
|
||||
public void setPathToResources(String pathToResources) {
|
||||
this.pathToResources = pathToResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default behavior for when Insert, Update and Delete events occur on beans that have an associated
|
||||
* Document store.
|
||||
*/
|
||||
public DocStoreMode getPersist() {
|
||||
return persist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default behavior for when Insert, Update and Delete events occur on beans that have an associated
|
||||
* Document store.
|
||||
* <ul>
|
||||
* <li>DocStoreEvent.UPDATE - build and send message to Bulk API</li>
|
||||
* <li>DocStoreEvent.QUEUE - add an entry with the index type and id only into a queue for later processing</li>
|
||||
* <li>DocStoreEvent.IGNORE - ignore. Most likely used when some scheduled batch job handles updating the index</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* You might choose to use QUEUE if that particular index data is updating very frequently or the cost of indexing
|
||||
* is expensive. Setting it to QUEUE can mean many changes can be batched together potentially coalescing multiple
|
||||
* updates for an index entry into a single update.
|
||||
* </p>
|
||||
* <p>
|
||||
* You might choose to use IGNORE when you have your own external process for updating the indexes. In this case
|
||||
* you don't want Ebean to do anything when the data changes.
|
||||
* </p>
|
||||
*/
|
||||
public void setPersist(DocStoreMode persist) {
|
||||
this.persist = persist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings specified in properties files.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper properties) {
|
||||
|
||||
active = properties.getBoolean("docstore.active", active);
|
||||
url = properties.get("docstore.url", url);
|
||||
persist = properties.getEnum(DocStoreMode.class, "docstore.persist", persist);
|
||||
bulkBatchSize = properties.getInt("docstore.bulkBatchSize", bulkBatchSize);
|
||||
generateMapping = properties.getBoolean("docstore.generateMapping", generateMapping);
|
||||
dropCreate = properties.getBoolean("docstore.dropCreate", dropCreate);
|
||||
create = properties.getBoolean("docstore.create", create);
|
||||
mappingPath = properties.get("docstore.mappingPath", mappingPath);
|
||||
mappingSuffix = properties.get("docstore.mappingSuffix", mappingSuffix);
|
||||
pathToResources = properties.get("docstore.pathToResources", pathToResources);
|
||||
}
|
||||
}
|
||||
@@ -141,8 +141,8 @@ public class PropertiesWrapper {
|
||||
* Return a Enum property value.
|
||||
*/
|
||||
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
|
||||
String level = get(key, defaultValue.name());
|
||||
return Enum.valueOf(enumType, level.toUpperCase());
|
||||
String level = get(key, null);
|
||||
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ final class PropertyMapLoader {
|
||||
* Load the <code>test-ebean.properties</code>.
|
||||
*/
|
||||
public static PropertyMap loadTestProperties() {
|
||||
return load(null, "test-ebean.properties", false);
|
||||
return load(null, "test-ebean.properties");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,10 +36,10 @@ final class PropertyMapLoader {
|
||||
}
|
||||
}
|
||||
|
||||
PropertyMap map = load(null, fileName, true);
|
||||
PropertyMap map = load(null, fileName);
|
||||
if (loadTestProperties) {
|
||||
// load test properties if present in classpath
|
||||
load(map, "test-ebean.properties", false);
|
||||
map = load(map, "test-ebean.properties");
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -52,13 +52,10 @@ final class PropertyMapLoader {
|
||||
* @param fileName
|
||||
* the name of the properties file to load.
|
||||
*/
|
||||
public static PropertyMap load(PropertyMap p, String fileName, boolean errorOnNull) {
|
||||
public static PropertyMap load(PropertyMap p, String fileName) {
|
||||
|
||||
InputStream is = findInputStream(fileName);
|
||||
if (is == null) {
|
||||
if (errorOnNull) {
|
||||
logger.error(fileName + " not found");
|
||||
}
|
||||
return p;
|
||||
} else {
|
||||
return load(p, is);
|
||||
@@ -66,7 +63,7 @@ final class PropertyMapLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the inputstream returning the property map.
|
||||
* Load the InputStream returning the property map.
|
||||
*
|
||||
* @param p
|
||||
* an existing property map to load into.
|
||||
|
||||
@@ -7,20 +7,28 @@ import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
import com.avaje.ebean.cache.ServerCacheManager;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbEncrypt;
|
||||
import com.avaje.ebean.event.*;
|
||||
import com.avaje.ebean.event.BeanFindController;
|
||||
import com.avaje.ebean.event.BeanPersistController;
|
||||
import com.avaje.ebean.event.BeanPersistListener;
|
||||
import com.avaje.ebean.event.BeanPostLoad;
|
||||
import com.avaje.ebean.event.BeanQueryAdapter;
|
||||
import com.avaje.ebean.event.BulkTableEventListener;
|
||||
import com.avaje.ebean.event.ServerConfigStartup;
|
||||
import com.avaje.ebean.event.TransactionEventListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogListener;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
|
||||
import com.avaje.ebean.event.changelog.ChangeLogRegister;
|
||||
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
|
||||
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import com.avaje.ebean.meta.MetaInfoManager;
|
||||
import com.avaje.ebean.util.ClassUtil;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* The configuration used for creating a EbeanServer.
|
||||
@@ -123,6 +131,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private String classPathReaderClassName;
|
||||
|
||||
/**
|
||||
* Configuration for the ElasticSearch integration.
|
||||
*/
|
||||
private DocStoreConfig docStoreConfig = new DocStoreConfig();
|
||||
|
||||
/**
|
||||
* This is used to populate @WhoCreated, @WhoModified and
|
||||
* support other audit features (who executed a query etc).
|
||||
@@ -206,6 +219,12 @@ public class ServerConfig {
|
||||
|
||||
private boolean ddlRun;
|
||||
|
||||
private boolean ddlCreateOnly;
|
||||
|
||||
private String ddlInitSql;
|
||||
|
||||
private String ddlSeedSql;
|
||||
|
||||
private boolean useJtaTransactionManager;
|
||||
|
||||
/**
|
||||
@@ -228,6 +247,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private DbMigrationConfig migrationConfig = new DbMigrationConfig();
|
||||
|
||||
/**
|
||||
* The ClassLoadConfig used to detect Joda, Java8, Jackson etc and create plugin instances given a className.
|
||||
*/
|
||||
private ClassLoadConfig classLoadConfig = new ClassLoadConfig();
|
||||
|
||||
/**
|
||||
* Set to true if the DataSource uses autoCommit.
|
||||
* <p>
|
||||
@@ -266,7 +290,7 @@ public class ServerConfig {
|
||||
private DbConstraintNaming constraintNaming = new DbConstraintNaming();
|
||||
|
||||
/**
|
||||
* Behaviour of update to include on the change properties.
|
||||
* Behaviour of update to include on the change properties.
|
||||
*/
|
||||
private boolean updateChangesOnly = true;
|
||||
|
||||
@@ -370,6 +394,8 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean expressionEqualsWithNullAsNoop;
|
||||
|
||||
private String jodaLocalTimeMode;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -1134,6 +1160,20 @@ public class ServerConfig {
|
||||
this.namingConvention = namingConvention;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration for the ElasticSearch integration.
|
||||
*/
|
||||
public DocStoreConfig getDocStoreConfig() {
|
||||
return docStoreConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for the ElasticSearch integration.
|
||||
*/
|
||||
public void setDocStoreConfig(DocStoreConfig docStoreConfig) {
|
||||
this.docStoreConfig = docStoreConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the constraint naming convention used in DDL generation.
|
||||
*/
|
||||
@@ -1518,19 +1558,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.
|
||||
*/
|
||||
@@ -1561,6 +1663,20 @@ public class ServerConfig {
|
||||
this.disableClasspathSearch = disableClasspathSearch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode to use for Joda LocalTime support 'normal' or 'utc'.
|
||||
*/
|
||||
public String getJodaLocalTimeMode() {
|
||||
return jodaLocalTimeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode to use for Joda LocalTime support 'normal' or 'utc'.
|
||||
*/
|
||||
public void setJodaLocalTimeMode(String jodaLocalTimeMode) {
|
||||
this.jodaLocalTimeMode = jodaLocalTimeMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically add classes (typically entities) that this server should
|
||||
* use.
|
||||
@@ -2023,6 +2139,39 @@ public class ServerConfig {
|
||||
this.persistenceContextScope = persistenceContextScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ClassLoadConfig which is used to detect Joda, Java8 types etc and also
|
||||
* create new instances of plugins given a className.
|
||||
*/
|
||||
public ClassLoadConfig getClassLoadConfig() {
|
||||
return classLoadConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ClassLoadConfig which is used to detect Joda, Java8 types etc and also
|
||||
* create new instances of plugins given a className.
|
||||
*/
|
||||
public void setClassLoadConfig(ClassLoadConfig classLoadConfig) {
|
||||
this.classLoadConfig = classLoadConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service loader using the classLoader defined in ClassLoadConfig.
|
||||
*/
|
||||
public <T> ServiceLoader<T> serviceLoad(Class<T> spiService) {
|
||||
|
||||
return ServiceLoader.load(spiService, classLoadConfig.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first service using the service loader (or null).
|
||||
*/
|
||||
public <T> T service(Class<T> spiService) {
|
||||
ServiceLoader<T> load = serviceLoad(spiService);
|
||||
Iterator<T> serviceInstances = load.iterator();
|
||||
return serviceInstances.hasNext() ? serviceInstances.next() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from ebean.properties.
|
||||
*/
|
||||
@@ -2062,14 +2211,31 @@ public class ServerConfig {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param properties the properties
|
||||
* @param pluginType the type of plugin
|
||||
* @param key properties key
|
||||
* @param instance existing instance
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key, T instance) {
|
||||
protected <T> T createInstance(PropertiesWrapper properties, Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = p.get(key, null);
|
||||
return classname == null ? null : (T) ClassUtil.newInstance(classname);
|
||||
String classname = properties.get(key, null);
|
||||
return createInstance(pluginType, classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
* @param pluginType the type of plugin
|
||||
* @param classname the implementation class as per properties
|
||||
*/
|
||||
protected <T> T createInstance(Class<T> pluginType, String classname) {
|
||||
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2084,7 +2250,14 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
|
||||
* This is broken out to allow overridden behaviour.
|
||||
*/
|
||||
protected void loadDocStoreSettings(PropertiesWrapper p) {
|
||||
docStoreConfig.loadSettings(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is broken out to allow overridden behaviour.
|
||||
*/
|
||||
protected void loadAutoTuneSettings(PropertiesWrapper p) {
|
||||
autoTuneConfig.loadSettings(p);
|
||||
@@ -2111,6 +2284,11 @@ public class ServerConfig {
|
||||
}
|
||||
loadDataSourceSettings(p);
|
||||
|
||||
if (docStoreConfig == null) {
|
||||
docStoreConfig = new DocStoreConfig();
|
||||
}
|
||||
loadDocStoreSettings(p);
|
||||
|
||||
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
@@ -2174,6 +2352,7 @@ public class ServerConfig {
|
||||
dbUuid = DbUuid.BINARY;
|
||||
}
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
|
||||
|
||||
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
|
||||
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
|
||||
@@ -2188,6 +2367,9 @@ public class ServerConfig {
|
||||
|
||||
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
|
||||
ddlRun = p.getBoolean("ddl.run", ddlRun);
|
||||
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
|
||||
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
|
||||
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
|
||||
|
||||
classes = getClasses(p);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class DB2SequenceIdGenerator extends SequenceIdGenerator {
|
||||
*/
|
||||
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
super(be, ds, seqName, batchSize);
|
||||
this.baseSql = "select nextval for " + seqName;
|
||||
this.baseSql = "values nextval for " + seqName;
|
||||
this.unionBaseSql = " union " + baseSql;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
this.name = "oracle";
|
||||
this.maxTableNameLength = 30;
|
||||
this.maxConstraintNameLength = 30;
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Oracle
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.dbEncrypt = new OracleDbEncrypt();
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
this.platformDdl = new Oracle10Ddl(this.dbTypeMap, this.dbIdentity);
|
||||
|
||||
@@ -19,9 +19,6 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.likeClause = "like ? escape''";
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
|
||||
@@ -20,9 +20,10 @@ 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.PlatformDdlWriter;
|
||||
import com.avaje.ebean.dbmigration.model.ModelDiff;
|
||||
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -57,6 +58,15 @@ public class DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
|
||||
|
||||
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.
|
||||
*/
|
||||
protected final boolean online;
|
||||
|
||||
protected SpiEbeanServer server;
|
||||
|
||||
protected DbMigrationConfig migrationConfig;
|
||||
@@ -71,7 +81,19 @@ public class DbMigration {
|
||||
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
*/
|
||||
public DbMigration() {
|
||||
this.online = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create using online EbeanServer.
|
||||
*/
|
||||
public DbMigration(EbeanServer server) {
|
||||
this.online = true;
|
||||
setServer(server);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +147,9 @@ public class DbMigration {
|
||||
*/
|
||||
public void setPlatform(DatabasePlatform databasePlatform) {
|
||||
this.databasePlatform = databasePlatform;
|
||||
DbOffline.setPlatform(databasePlatform.getName());
|
||||
if (!online) {
|
||||
DbOffline.setPlatform(databasePlatform.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,71 +199,210 @@ public class DbMigration {
|
||||
public void generateMigration() throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
DbOffline.setRunningMigration();
|
||||
|
||||
if (!online) {
|
||||
DbOffline.setRunningMigration();
|
||||
}
|
||||
setDefaults();
|
||||
|
||||
try {
|
||||
MigrationModel migrationModel = new MigrationModel(migrationConfig.getResourcePath());
|
||||
ModelContainer migrated = migrationModel.read();
|
||||
int nextMajorVersion = migrationModel.getNextMajorVersion();
|
||||
Request request = createRequest();
|
||||
|
||||
logger.info("next migration version {}", nextMajorVersion);
|
||||
|
||||
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;
|
||||
String pendingVersion = generatePendingDrop();
|
||||
if (pendingVersion != null) {
|
||||
generatePendingDrop(request, pendingVersion);
|
||||
} else {
|
||||
generateDiff(request);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the diff migration.
|
||||
*/
|
||||
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
|
||||
Migration dbMigration = diff.getMigration();
|
||||
generateMigration(request, migration, null);
|
||||
}
|
||||
}
|
||||
|
||||
File writePath = getWritePath();
|
||||
logger.info("migration writing version {} to {}", nextMajorVersion, writePath.getAbsolutePath());
|
||||
writeMigrationXml(dbMigration, writePath, nextMajorVersion);
|
||||
/**
|
||||
* 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(), currentModel.read());
|
||||
PlatformDdlWriter writer = new PlatformDdlWriter(databasePlatform, serverConfig);
|
||||
writer.processMigration(dbMigration, write, writePath, nextMajorVersion);
|
||||
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
|
||||
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
|
||||
writer.processMigration(dbMigration, write, request.migrationDir , fullVersion);
|
||||
}
|
||||
|
||||
writeExtraPlatformDdl(nextMajorVersion, currentModel, dbMigration, writePath);
|
||||
|
||||
} finally {
|
||||
DbOffline.reset();
|
||||
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) {
|
||||
version = migrationModel.getNextVersion(initialVersion);
|
||||
}
|
||||
|
||||
String fullVersion = version;
|
||||
if (migrationConfig.getName() != null) {
|
||||
fullVersion += "__" + toUnderScore(migrationConfig.getName());
|
||||
|
||||
} else if (dropsFor != null) {
|
||||
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
|
||||
|
||||
} else if (version.equals(initialVersion)) {
|
||||
fullVersion += "__initial";
|
||||
}
|
||||
return fullVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace spaces with underscores.
|
||||
*/
|
||||
private String toUnderScore(String name) {
|
||||
return name.replace(' ','_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write any extra platform ddl.
|
||||
*/
|
||||
protected void writeExtraPlatformDdl(int nextMajorVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
|
||||
PlatformDdlWriter platformWriter = new PlatformDdlWriter(pair.platform, serverConfig, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, writePath, nextMajorVersion);
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, writePath, fullVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(Pair pair) {
|
||||
return createDdlWriter(pair.platform, pair.prefix);
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform, String prefix) {
|
||||
return new PlatformDdlWriter(platform, serverConfig, prefix, migrationConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration xml.
|
||||
*/
|
||||
protected void writeMigrationXml(Migration dbMigration, File resourcePath, int migrationVersion) {
|
||||
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
|
||||
File file = new File(resourcePath, "v"+migrationVersion+".0.xml");
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter();
|
||||
String modelFile = fullVersion + migrationConfig.getModelSuffix();
|
||||
File file = new File(resourcePath, modelFile);
|
||||
if (file.exists()) {
|
||||
return false;
|
||||
}
|
||||
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
|
||||
xmlWriter.write(dbMigration, file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,14 +423,13 @@ public class DbMigration {
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
protected File getWritePath() {
|
||||
protected File getMigrationDirectory() {
|
||||
|
||||
// path to src/main/resources in typical maven project
|
||||
File resourceRootDir = new File(pathToResources);
|
||||
String resourcePath = migrationConfig.getMigrationPath();
|
||||
|
||||
String resourcePath = migrationConfig.getResourcePath();
|
||||
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/myapp
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/model
|
||||
File path = new File(resourceRootDir, resourcePath);
|
||||
if (!path.exists()) {
|
||||
if (!path.mkdirs()) {
|
||||
@@ -277,6 +439,21 @@ public class DbMigration {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the model directory (relative to the migration directory).
|
||||
*/
|
||||
protected File getModelDirectory(File migrationDirectory) {
|
||||
String modelPath = migrationConfig.getModelPath();
|
||||
if (modelPath == null || modelPath.isEmpty()) {
|
||||
return migrationDirectory;
|
||||
}
|
||||
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
|
||||
if (!modelDir.exists() && !modelDir.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
|
||||
}
|
||||
return modelDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DatabasePlatform given the platform key.
|
||||
*/
|
||||
|
||||
@@ -1,47 +1,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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+96
-45
@@ -21,6 +21,7 @@ import com.avaje.ebean.dbmigration.migration.DropTable;
|
||||
import com.avaje.ebean.dbmigration.migration.ForeignKey;
|
||||
import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
@@ -122,8 +123,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
// defined on the columns
|
||||
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
|
||||
}
|
||||
if (platformDdl.isInlineForeignKeys()) {
|
||||
writeInlineForeignKeys(writer, createTable);
|
||||
}
|
||||
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
apply.newLine().append(")");
|
||||
addTableCommentInline(apply, createTable);
|
||||
apply.endOfStatement();
|
||||
|
||||
addComments(apply, createTable);
|
||||
|
||||
writeUniqueOneToOneConstraints(writer, createTable);
|
||||
|
||||
@@ -135,7 +143,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();
|
||||
@@ -144,10 +152,42 @@ 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);
|
||||
if (!platformDdl.isInlineForeignKeys()) {
|
||||
writeAddForeignKeys(writer, createTable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table and column comments (separate from the create table statement).
|
||||
*/
|
||||
private void addComments(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
if (!platformDdl.isInlineComments()) {
|
||||
String tableComment = createTable.getComment();
|
||||
if (!StringHelper.isNull(tableComment)) {
|
||||
platformDdl.addTableComment(apply, createTable.getName(), tableComment);
|
||||
}
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
if (!StringHelper.isNull(column.getComment())) {
|
||||
platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the table comment inline with the create table statement.
|
||||
*/
|
||||
private void addTableCommentInline(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
if (platformDdl.isInlineComments()) {
|
||||
String tableComment = createTable.getComment();
|
||||
if (!StringHelper.isNull(tableComment)) {
|
||||
platformDdl.inlineTableComment(apply, tableComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTableColumns(DdlBuffer apply, List<Column> columns, boolean useIdentity) throws IOException {
|
||||
@@ -168,7 +208,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames))
|
||||
.endOfStatement();
|
||||
|
||||
write.rollbackForeignKeys()
|
||||
write.dropAllForeignKeys()
|
||||
.append(platformDdl.dropIndex(uqName, tableName))
|
||||
.endOfStatement();
|
||||
}
|
||||
@@ -189,7 +229,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +239,43 @@ public class BaseTableDdl implements TableDdl {
|
||||
platformDdl.createWithHistory(writer, table);
|
||||
}
|
||||
|
||||
protected void writeInlineForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
|
||||
|
||||
for (Column column : createTable.getColumn()) {
|
||||
String references = column.getReferences();
|
||||
if (hasValue(references)) {
|
||||
writeInlineForeignKey(write, column);
|
||||
}
|
||||
}
|
||||
writeInlineCompoundForeignKeys(write, createTable);
|
||||
}
|
||||
|
||||
protected void writeInlineForeignKey(DdlWrite write, Column column) throws IOException {
|
||||
|
||||
String references = column.getReferences();
|
||||
int pos = references.lastIndexOf('.');
|
||||
if (pos == -1) {
|
||||
throw new IllegalStateException("Expecting period '.' character for table.column split but not found in [" + references + "]");
|
||||
}
|
||||
String refTableName = references.substring(0, pos);
|
||||
String refColumnName = references.substring(pos + 1);
|
||||
String fkConstraint = platformDdl.tableInlineForeignKey(new String[]{column.getName()}, refTableName, new String[]{refColumnName});
|
||||
write.apply().append(",").newLine().append(" ").append(fkConstraint);
|
||||
}
|
||||
|
||||
protected void writeInlineCompoundForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
|
||||
|
||||
List<ForeignKey> foreignKey = createTable.getForeignKey();
|
||||
for (ForeignKey key : foreignKey) {
|
||||
String refTableName = key.getRefTableName();
|
||||
String[] cols = toColumnNamesSplit(key.getColumnNames());
|
||||
String[] refColumns = toColumnNamesSplit(key.getRefColumnNames());
|
||||
|
||||
String fkConstraint = platformDdl.tableInlineForeignKey(cols, refTableName, refColumns);
|
||||
write.apply().append(",").newLine().append(" ").append(fkConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeAddForeignKeys(DdlWrite write, CreateTable createTable) throws IOException {
|
||||
|
||||
String tableName = createTable.getName();
|
||||
@@ -259,21 +336,24 @@ 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();
|
||||
|
||||
}
|
||||
|
||||
protected void alterTableAddForeignKey(DdlBuffer buffer, String fkName, String tableName, String[] columns, String refTable, String[] refColumns) throws IOException {
|
||||
|
||||
buffer.append(platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns)).endOfStatement();
|
||||
String fkConstraint = platformDdl.alterTableAddForeignKey(tableName, fkName, columns, refTable, refColumns);
|
||||
if (fkConstraint != null && !fkConstraint.isEmpty()) {
|
||||
buffer.append(fkConstraint).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
protected void appendColumns(String[] columns, DdlBuffer buffer) throws IOException {
|
||||
@@ -435,7 +515,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();
|
||||
}
|
||||
@@ -484,7 +564,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())) {
|
||||
@@ -493,13 +572,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,7 +585,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());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,14 +596,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -606,21 +683,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();
|
||||
@@ -631,11 +693,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -653,8 +710,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,14 +718,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,7 +777,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();
|
||||
}
|
||||
|
||||
+79
-32
@@ -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,10 +177,12 @@ 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) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
if (!column.isDraftOnly()) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
}
|
||||
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
|
||||
apply.append(",").newLine();
|
||||
@@ -189,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();
|
||||
@@ -201,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(",");
|
||||
@@ -214,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(", ");
|
||||
}
|
||||
@@ -224,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.isHistoryExclude()) {
|
||||
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)
|
||||
|
||||
+11
-43
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MS SQL Server platform specific DDL.
|
||||
*/
|
||||
@@ -15,6 +18,7 @@ public class MsSqlServerDdl extends PlatformDdl {
|
||||
this.foreignKeyRestrict = "";
|
||||
this.inlineUniqueOneToOne = false;
|
||||
this.columnSetDefault = "add default";
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,4 +87,20 @@ public class MsSqlServerDdl extends PlatformDdl {
|
||||
// can't alter itself - done in alterColumnBaseAttributes()
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.migration.AlterColumn;
|
||||
import com.avaje.ebean.dbmigration.migration.Column;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MySql specific DDL.
|
||||
@@ -14,6 +19,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
this.alterColumn = "modify";
|
||||
this.dropUniqueConstraint = "drop index";
|
||||
this.historyDdl = new MySqlHistoryDdl();
|
||||
this.inlineComments = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,4 +70,26 @@ public class MySqlDdl extends PlatformDdl {
|
||||
// use modify
|
||||
return "alter table " + tableName + " modify " + columnName + " " + type + notnullClause;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeColumnDefinition(DdlBuffer buffer, Column column, boolean useIdentity) throws IOException {
|
||||
super.writeColumnDefinition(buffer, column, useIdentity);
|
||||
String comment = column.getComment();
|
||||
if (!StringHelper.isNull(comment)) {
|
||||
// in mysql 5.5 column comment save in information_schema.COLUMNS.COLUMN_COMMENT(VARCHAR 1024)
|
||||
if (comment.length() > 500) {
|
||||
comment = comment.substring(0, 500);
|
||||
}
|
||||
buffer.append(String.format(" comment '%s'", comment));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void inlineTableComment(DdlBuffer apply, String tableComment) throws IOException {
|
||||
if (tableComment.length() > 1000) {
|
||||
tableComment = tableComment.substring(0, 1000);
|
||||
}
|
||||
apply.append(" comment='").append(tableComment).append("'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+19
-41
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ public class PlatformDdl {
|
||||
*/
|
||||
private final DbIdentity dbIdentity;
|
||||
|
||||
/**
|
||||
* Set to true if table and column comments are included inline with the create statements.
|
||||
*/
|
||||
protected boolean inlineComments;
|
||||
|
||||
/**
|
||||
* Default assumes if exists is supported.
|
||||
*/
|
||||
@@ -53,6 +58,8 @@ public class PlatformDdl {
|
||||
|
||||
protected String identitySuffix = " auto_increment";
|
||||
|
||||
protected String alterTableIfExists = "";
|
||||
|
||||
protected String dropConstraintIfExists = "drop constraint if exists";
|
||||
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
@@ -78,6 +85,11 @@ public class PlatformDdl {
|
||||
|
||||
protected DbConstraintNaming naming;
|
||||
|
||||
/**
|
||||
* Generally not desired as then they are not named (used with SQLite).
|
||||
*/
|
||||
protected boolean inlineForeignKeys;
|
||||
|
||||
public PlatformDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
|
||||
this.dbIdentity = dbIdentity;
|
||||
this.typeConverter = new PlatformTypeConverter(platformTypes);
|
||||
@@ -114,6 +126,21 @@ public class PlatformDdl {
|
||||
return columnDefn + identitySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table and column comments are included inline.
|
||||
*/
|
||||
public boolean isInlineComments() {
|
||||
return inlineComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if foreign key reference constraints need to inlined with create table.
|
||||
* Ideally we don't do this as then the constraints are not named. Do this for SQLite.
|
||||
*/
|
||||
public boolean isInlineForeignKeys() {
|
||||
return inlineForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the table columns converting to platform types as necessary.
|
||||
*/
|
||||
@@ -150,7 +177,7 @@ public class PlatformDdl {
|
||||
* Return the drop foreign key clause.
|
||||
*/
|
||||
public String alterTableDropForeignKey(String tableName, String fkName) {
|
||||
return "alter table " + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +213,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,6 +268,20 @@ public class PlatformDdl {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign key constraint when used inline with create table.
|
||||
*/
|
||||
public String tableInlineForeignKey(String[] columns, String refTable, String[] refColumns) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(90);
|
||||
buffer.append("foreign key");
|
||||
appendColumns(columns, buffer);
|
||||
buffer.append(" references ").append(lowerTableName(refTable));
|
||||
appendColumns(refColumns, buffer);
|
||||
appendWithSpace(foreignKeyRestrict, buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add foreign key.
|
||||
*/
|
||||
@@ -384,4 +425,27 @@ public class PlatformDdl {
|
||||
protected boolean isTrue(Boolean value) {
|
||||
return Boolean.TRUE.equals(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an inline table comment to the create table statement.
|
||||
*/
|
||||
public void inlineTableComment(DdlBuffer apply, String tableComment) throws IOException {
|
||||
// do nothing by default (MySql only)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
apply.append(String.format("comment on table %s is '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public class PostgresDdl extends PlatformDdl {
|
||||
this.historyDdl = new PostgresHistoryDdl();
|
||||
this.dropTableCascade = " cascade";
|
||||
this.columnSetType = "type ";
|
||||
this.alterTableIfExists = "if exists ";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+30
-29
@@ -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,13 +85,13 @@ 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(" return new;").newLine().newLine();
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
|
||||
.append(" return new;").newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" return old;").newLine().newLine();
|
||||
.append(" return old;").newLine();
|
||||
apply
|
||||
.append(" end if;").newLine()
|
||||
.append("end;").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();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* DB2 platform specific DDL.
|
||||
@@ -10,7 +13,23 @@ public class SQLiteDdl extends PlatformDdl {
|
||||
|
||||
public SQLiteDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
|
||||
super(platformTypes, dbIdentity);
|
||||
this.identitySuffix = " autoincrement";
|
||||
this.identitySuffix = "";
|
||||
this.inlineForeignKeys = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableAddForeignKey(String tableName, String fkName, String[] columns, String refTable, String[] refColumns) {
|
||||
// not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-9
@@ -5,7 +5,7 @@ import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
|
||||
/**
|
||||
* Converts a logical column definition into platform specific one.
|
||||
*
|
||||
* <p>
|
||||
* This translates standard sql types into platform specific ones.
|
||||
*/
|
||||
public class PlatformTypeConverter {
|
||||
@@ -44,20 +44,21 @@ public class PlatformTypeConverter {
|
||||
return columnDefinition;
|
||||
}
|
||||
|
||||
String type = columnDefinition.substring(0,open);
|
||||
String suffix = close + 1 < columnDefinition.length() ? columnDefinition.substring(close + 1) : "";
|
||||
String type = columnDefinition.substring(0, open);
|
||||
try {
|
||||
DbType dbType = platformTypes.lookup(type);
|
||||
int comma = columnDefinition.indexOf(',',open);
|
||||
int comma = columnDefinition.indexOf(',', open);
|
||||
if (comma > -1) {
|
||||
// scale and precision - decimal(10,4)
|
||||
int scale = Integer.parseInt(columnDefinition.substring(open+1, comma));
|
||||
int precision = Integer.parseInt(columnDefinition.substring(comma+1, close));
|
||||
return dbType.renderType(scale,precision);
|
||||
int scale = Integer.parseInt(columnDefinition.substring(open + 1, comma));
|
||||
int precision = Integer.parseInt(columnDefinition.substring(comma + 1, close));
|
||||
return dbType.renderType(scale, precision) + suffix;
|
||||
|
||||
} else {
|
||||
// scale - varchar(10)
|
||||
int scale = Integer.parseInt(columnDefinition.substring(open+1, close));
|
||||
return dbType.renderType(scale,0);
|
||||
int scale = Integer.parseInt(columnDefinition.substring(open + 1, close));
|
||||
return dbType.renderType(scale, 0) + suffix;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -73,7 +74,7 @@ public class PlatformTypeConverter {
|
||||
|
||||
try {
|
||||
DbType dbType = platformTypes.lookup(columnDefinition);
|
||||
return dbType.renderType(0,0);
|
||||
return dbType.renderType(0, 0);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
// assume already platform specific, leave as is
|
||||
|
||||
@@ -27,6 +27,8 @@ import javax.xml.bind.annotation.XmlType;
|
||||
* </choice>
|
||||
* </sequence>
|
||||
* <attribute name="type" use="required" type="{http://ebean-orm.github.io/xml/ns/dbmigration}changeSetType" />
|
||||
* <attribute name="dropsFor" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <attribute name="suppressDropsForever" type="{http://www.w3.org/2001/XMLSchema}boolean" />
|
||||
* <attribute name="generated" type="{http://www.w3.org/2001/XMLSchema}boolean" />
|
||||
* <attribute name="author" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <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;
|
||||
* <simpleType name="changeSetType">
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}string">
|
||||
* <enumeration value="apply"/>
|
||||
* <enumeration value="drop"/>
|
||||
* <enumeration value="pendingDrops"/>
|
||||
* <enumeration value="baseline"/>
|
||||
* </restriction>
|
||||
* </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;
|
||||
* <attGroup ref="{http://ebean-orm.github.io/xml/ns/dbmigration}tablespaceAttributes"/>
|
||||
* <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <attribute name="withHistory" type="{http://www.w3.org/2001/XMLSchema}boolean" />
|
||||
* <attribute name="draft" type="{http://www.w3.org/2001/XMLSchema}boolean" />
|
||||
* <attribute name="identityType" type="{http://ebean-orm.github.io/xml/ns/dbmigration}identityType" />
|
||||
* <attribute name="sequenceName" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <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.
|
||||
*
|
||||
|
||||
@@ -6,6 +6,9 @@ import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
@@ -15,19 +18,6 @@ public class MigrationXmlReader {
|
||||
|
||||
private static final MigrationXmlReader INSTANCE = new MigrationXmlReader();
|
||||
|
||||
/**
|
||||
* Read and return a Migration from an xml document at the given resource path.
|
||||
*/
|
||||
public static Migration readMaybe(String resourcePath) {
|
||||
|
||||
InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath);
|
||||
if (is == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return INSTANCE.read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Migration from an xml document at the given resource path.
|
||||
*/
|
||||
@@ -41,10 +31,27 @@ public class MigrationXmlReader {
|
||||
return INSTANCE.read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Migration from a migration xml file.
|
||||
*/
|
||||
public static Migration read(File migrationFile) {
|
||||
|
||||
try {
|
||||
FileInputStream is = new FileInputStream(migrationFile);
|
||||
try {
|
||||
return read(is);
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Migration from an xml document.
|
||||
*/
|
||||
public Migration read(InputStream is) {
|
||||
public static Migration read(InputStream is) {
|
||||
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(Migration.class);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -77,6 +77,9 @@ public class CurrentModel {
|
||||
ModelBuildBeanVisitor visitor = new ModelBuildBeanVisitor(context);
|
||||
VisitAllUsing visit = new VisitAllUsing(visitor, server);
|
||||
visit.visitAllBeans();
|
||||
|
||||
// adjust the foreign keys on the 'draft' tables
|
||||
context.adjustDraftReferences();
|
||||
}
|
||||
return model;
|
||||
}
|
||||
@@ -114,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();
|
||||
}
|
||||
@@ -156,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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public class MColumn {
|
||||
private String references;
|
||||
private String foreignKeyName;
|
||||
private String foreignKeyIndex;
|
||||
private String comment;
|
||||
|
||||
private boolean historyExclude;
|
||||
private boolean notnull;
|
||||
@@ -35,12 +36,15 @@ public class MColumn {
|
||||
*/
|
||||
private AlterColumn alterColumn;
|
||||
|
||||
private boolean draftOnly;
|
||||
|
||||
public MColumn(Column column) {
|
||||
this.name = column.getName();
|
||||
this.type = column.getType();
|
||||
this.checkConstraint = column.getCheckConstraint();
|
||||
this.checkConstraintName = column.getCheckConstraintName();
|
||||
this.defaultValue = column.getDefaultValue();
|
||||
this.comment = column.getComment();
|
||||
this.references = column.getReferences();
|
||||
this.foreignKeyName = column.getForeignKeyName();
|
||||
this.foreignKeyIndex = column.getForeignKeyIndex();
|
||||
@@ -63,6 +67,29 @@ public class MColumn {
|
||||
this.notnull = notnull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this column used for creating the associated draft table.
|
||||
*/
|
||||
public MColumn copyForDraft() {
|
||||
|
||||
MColumn copy = new MColumn(name, type);
|
||||
copy.draftOnly = draftOnly;
|
||||
copy.checkConstraint = checkConstraint;
|
||||
copy.checkConstraintName = checkConstraintName;
|
||||
copy.defaultValue = defaultValue;
|
||||
copy.references = references;
|
||||
copy.comment = comment;
|
||||
copy.foreignKeyName = foreignKeyName;
|
||||
copy.foreignKeyIndex = foreignKeyIndex;
|
||||
copy.historyExclude = historyExclude;
|
||||
copy.notnull = notnull;
|
||||
copy.primaryKey = primaryKey;
|
||||
copy.identity = identity;
|
||||
copy.unique = unique;
|
||||
copy.uniqueOneToOne = uniqueOneToOne;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -174,6 +201,41 @@ public class MColumn {
|
||||
return uniqueOneToOne;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column comment.
|
||||
*/
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column comment.
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the draftOnly status for this column.
|
||||
*/
|
||||
public void setDraftOnly(boolean draftOnly) {
|
||||
this.draftOnly = draftOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the draftOnly status for this column.
|
||||
*/
|
||||
public boolean isDraftOnly() {
|
||||
return draftOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this column should be included in History DB triggers etc.
|
||||
*/
|
||||
public boolean isIncludeInHistory() {
|
||||
return !draftOnly && !historyExclude;
|
||||
}
|
||||
|
||||
public Column createColumn() {
|
||||
|
||||
Column c = new Column();
|
||||
@@ -191,6 +253,7 @@ public class MColumn {
|
||||
c.setForeignKeyName(foreignKeyName);
|
||||
c.setForeignKeyIndex(foreignKeyIndex);
|
||||
c.setDefaultValue(defaultValue);
|
||||
c.setComment(comment);
|
||||
c.setUnique(unique);
|
||||
c.setUniqueOneToOne(uniqueOneToOne);
|
||||
|
||||
@@ -220,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,11 +39,24 @@ import java.util.Set;
|
||||
*/
|
||||
public class MTable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MTable.class);
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The associated draft table.
|
||||
*/
|
||||
private MTable draftTable;
|
||||
|
||||
/**
|
||||
* Marked true for draft tables. These need to have their FK references adjusted
|
||||
* after all the draft tables have been identified.
|
||||
*/
|
||||
private boolean draft;
|
||||
|
||||
/**
|
||||
* Primary key name.
|
||||
*/
|
||||
@@ -107,6 +121,29 @@ public class MTable {
|
||||
*/
|
||||
private AddColumn addColumn;
|
||||
|
||||
private List<String> droppedColumns = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* Create a copy of this table structure as a 'draft' table.
|
||||
*
|
||||
* Note that both tables contain @DraftOnly MColumns and these are filtered out
|
||||
* later when creating the CreateTable object.
|
||||
*/
|
||||
public MTable createDraftTable() {
|
||||
|
||||
draftTable = new MTable(name + "_draft");
|
||||
draftTable.draft = true;
|
||||
draftTable.whenCreatedColumn = whenCreatedColumn;
|
||||
// compoundKeys
|
||||
// compoundUniqueConstraints
|
||||
draftTable.identityType = identityType;
|
||||
|
||||
for (MColumn col : allColumns()) {
|
||||
draftTable.addColumn(col.copyForDraft());
|
||||
}
|
||||
|
||||
return draftTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for migration.
|
||||
@@ -118,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());
|
||||
@@ -162,9 +200,15 @@ public class MTable {
|
||||
if (withHistory) {
|
||||
createTable.setWithHistory(Boolean.TRUE);
|
||||
}
|
||||
if (draft) {
|
||||
createTable.setDraft(Boolean.TRUE);
|
||||
}
|
||||
|
||||
for (MColumn column : this.columns.values()) {
|
||||
createTable.getColumn().add(column.createColumn());
|
||||
for (MColumn column : allColumns()) {
|
||||
// filter out draftOnly columns from the base table
|
||||
if (draft || !column.isDraftOnly()) {
|
||||
createTable.getColumn().add(column.createColumn());
|
||||
}
|
||||
}
|
||||
|
||||
for (MCompoundForeignKey compoundKey : compoundKeys) {
|
||||
@@ -209,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) {
|
||||
@@ -252,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?");
|
||||
}
|
||||
@@ -264,15 +315,21 @@ 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() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getPkName() {
|
||||
return pkName;
|
||||
/**
|
||||
* Return true if this table is a 'Draft' table.
|
||||
*/
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public void setPkName(String pkName) {
|
||||
@@ -283,6 +340,10 @@ public class MTable {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getTablespace() {
|
||||
return tablespace;
|
||||
}
|
||||
@@ -295,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;
|
||||
}
|
||||
|
||||
@@ -311,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;
|
||||
}
|
||||
@@ -353,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);
|
||||
}
|
||||
@@ -427,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);
|
||||
@@ -461,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);
|
||||
@@ -475,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();
|
||||
}
|
||||
@@ -486,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() {
|
||||
@@ -511,4 +592,42 @@ public class MTable {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the references (FK) if it should relate to a draft table.
|
||||
*/
|
||||
public void adjustReferences(ModelContainer modelContainer) {
|
||||
|
||||
Collection<MColumn> cols = allColumns();
|
||||
for (MColumn col : cols) {
|
||||
String references = col.getReferences();
|
||||
if (references != null) {
|
||||
String baseTable = extractBaseTable(references);
|
||||
MTable refBaseTable = modelContainer.getTable(baseTable);
|
||||
if (refBaseTable.draftTable != null) {
|
||||
// change references to another associated 'draft' table
|
||||
String newReferences = deriveReferences(references, refBaseTable.draftTable.getName());
|
||||
col.setReferences(newReferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base table name from references (table.column).
|
||||
*/
|
||||
private String extractBaseTable(String references) {
|
||||
int lastDot = references.lastIndexOf('.');
|
||||
return references.substring(0, lastDot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new references using the given draftTableName.
|
||||
* (The referenced column is the same as before).
|
||||
*/
|
||||
private String deriveReferences(String references, String draftTableName) {
|
||||
int lastDot = references.lastIndexOf('.');
|
||||
return draftTableName + "." + references.substring(lastDot + 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Build the model from the series of migrations.
|
||||
@@ -17,26 +18,15 @@ public class MigrationModel {
|
||||
|
||||
private final ModelContainer model = new ModelContainer();
|
||||
|
||||
private final Set<String> readVersions = new LinkedHashSet<String>();
|
||||
private final File modelDirectory;
|
||||
|
||||
private final String resourcePath;
|
||||
private final String modelSuffix;
|
||||
|
||||
int nextMajorVersion;
|
||||
private MigrationVersion lastVersion;
|
||||
|
||||
public MigrationModel(String resourcePath) {
|
||||
this.resourcePath = normaliseResourcePath(resourcePath);
|
||||
}
|
||||
|
||||
private String normaliseResourcePath(String resourcePath) {
|
||||
if (resourcePath.endsWith("/")) {
|
||||
// trim trailing slash
|
||||
resourcePath = resourcePath.substring(0, resourcePath.length()-1);
|
||||
}
|
||||
if (resourcePath.startsWith("/")) {
|
||||
// trim leading slash
|
||||
resourcePath = resourcePath.substring(1);
|
||||
}
|
||||
return resourcePath;
|
||||
public MigrationModel(File modelDirectory, String modelSuffix) {
|
||||
this.modelDirectory = modelDirectory;
|
||||
this.modelSuffix = modelSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,57 +36,47 @@ public class MigrationModel {
|
||||
public ModelContainer read() {
|
||||
|
||||
readMigrations();
|
||||
logger.info("read versions {}", readVersions);
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of versions that were read.
|
||||
*/
|
||||
public Set<String> getReadVersions() {
|
||||
return readVersions;
|
||||
}
|
||||
|
||||
public int getNextMajorVersion() {
|
||||
return nextMajorVersion;
|
||||
}
|
||||
|
||||
private void readMigrations() {
|
||||
|
||||
for (int majorVersion = 1; majorVersion < 100; majorVersion++) {
|
||||
if (!readMinorVersions(majorVersion)){
|
||||
// no major.0 version so stopping
|
||||
nextMajorVersion = majorVersion;
|
||||
return;
|
||||
// find all the migration xml files
|
||||
File[] xmlFiles = modelDirectory.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
return pathname.getName().toLowerCase().endsWith(modelSuffix);
|
||||
}
|
||||
});
|
||||
|
||||
List<MigrationResource> resources = new ArrayList<MigrationResource>();
|
||||
|
||||
for (File xmlFile: xmlFiles) {
|
||||
resources.add(new MigrationResource(xmlFile, createVersion(xmlFile)));
|
||||
}
|
||||
|
||||
// sort into version order before applying
|
||||
Collections.sort(resources);
|
||||
|
||||
for (MigrationResource migrationResource: resources) {
|
||||
logger.debug("read {}", migrationResource);
|
||||
model.apply(migrationResource.read(), migrationResource.getVersion());
|
||||
}
|
||||
|
||||
// remember the last version
|
||||
if (!resources.isEmpty()) {
|
||||
lastVersion = resources.get(resources.size() - 1).getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readMinorVersions(int majorVersion) {
|
||||
|
||||
for (int minorVersion = 0; minorVersion < 100; minorVersion++) {
|
||||
if (!readMigration(majorVersion, minorVersion)) {
|
||||
// continue reading next major if minorVersion 0 was read
|
||||
return (minorVersion > 0);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
private MigrationVersion createVersion(File xmlFile) {
|
||||
String fileName = xmlFile.getName();
|
||||
String versionName = fileName.substring(0, fileName.length() - modelSuffix.length());
|
||||
return MigrationVersion.parse(versionName);
|
||||
}
|
||||
|
||||
private boolean readMigration(int majorVersion, int minorVersion) {
|
||||
public String getNextVersion(String initialVersion) {
|
||||
|
||||
String version = majorVersion+"."+minorVersion;
|
||||
String path = "/"+resourcePath+"/v"+version+".xml";
|
||||
|
||||
Migration migration = MigrationXmlReader.readMaybe(path);
|
||||
if (migration == null) {
|
||||
logger.debug("... no migration at path:{}", path);
|
||||
return false;
|
||||
}
|
||||
readVersions.add(version);
|
||||
logger.trace("... read migration v{}", version);
|
||||
model.apply(migration);
|
||||
return true;
|
||||
return lastVersion == null ? initialVersion : lastVersion.nextVersion();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import com.avaje.ebean.dbmigration.migration.Migration;
|
||||
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Migration XML resource that holds the changes to be applied.
|
||||
*/
|
||||
public class MigrationResource implements Comparable<MigrationResource> {
|
||||
|
||||
private final File migrationFile;
|
||||
|
||||
private final MigrationVersion version;
|
||||
|
||||
/**
|
||||
* Construct with a migration xml file.
|
||||
*/
|
||||
public MigrationResource(File migrationFile, MigrationVersion version) {
|
||||
this.migrationFile = migrationFile;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return migrationFile.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version associated with this resource.
|
||||
*/
|
||||
public MigrationVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the migration from the resource.
|
||||
*/
|
||||
public Migration read() {
|
||||
|
||||
return MigrationXmlReader.read(migrationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare by underlying version.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(MigrationResource other) {
|
||||
return version.compareTo(other.version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.avaje.ebean.dbmigration.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The version of a migration used so that migrations are processed in order.
|
||||
*/
|
||||
public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
|
||||
/**
|
||||
* The raw version text.
|
||||
*/
|
||||
private final String raw;
|
||||
|
||||
/**
|
||||
* The ordering parts.
|
||||
*/
|
||||
private final int[] ordering;
|
||||
|
||||
private 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]);
|
||||
if (normalised) {
|
||||
sb.append('.');
|
||||
} else {
|
||||
sb.append(underscores[i] ? '_' : '.');
|
||||
}
|
||||
} else {
|
||||
sb.append((nextVersion) ? ordering[i] + 1 : ordering[i]);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MigrationVersion other) {
|
||||
|
||||
int otherLength = other.ordering.length;
|
||||
for (int i = 0; i < ordering.length; i++) {
|
||||
if (i >= otherLength) {
|
||||
// considered greater
|
||||
return 1;
|
||||
}
|
||||
if (ordering[i] != other.ordering[i]) {
|
||||
return (ordering[i] > other.ordering[i]) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
// considered the same
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the raw version string 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 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 = Arrays.copyOf(ordering, stopIndex);
|
||||
boolean[] actualUnderscores = Arrays.copyOf(underscores, stopIndex);
|
||||
|
||||
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;
|
||||
@@ -27,15 +28,27 @@ 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() {
|
||||
for (MTable table : this.tables.values()) {
|
||||
if (table.isDraft()) {
|
||||
table.adjustReferences(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,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.
|
||||
*/
|
||||
@@ -232,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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
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;
|
||||
@@ -24,105 +27,97 @@ public class PlatformDdlWriter {
|
||||
|
||||
private final String platformPrefix;
|
||||
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig) {
|
||||
this(platform, serverConfig, "");
|
||||
}
|
||||
private final DbMigrationConfig config;
|
||||
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix) {
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix, DbMigrationConfig config) {
|
||||
this.platform = platform;
|
||||
this.serverConfig = serverConfig;
|
||||
this.platformPrefix = platformPrefix;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration as platform specific ddl.
|
||||
*/
|
||||
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, int nextMajorVersion) throws IOException {
|
||||
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, String fullVersion) throws IOException {
|
||||
|
||||
DdlHandler handler = handler();
|
||||
|
||||
List<ChangeSet> changeSets = dbMigration.getChangeSet();
|
||||
for (ChangeSet changeSet : changeSets) {
|
||||
if (!changeSet.getChangeSetChildren().isEmpty()) {
|
||||
if (isApply(changeSet)) {
|
||||
handler.generate(write, changeSet);
|
||||
}
|
||||
}
|
||||
handler.generateExtra(write);
|
||||
|
||||
writePlatformDdl(write, writePath, nextMajorVersion);
|
||||
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.
|
||||
*/
|
||||
protected void writePlatformDdl(DdlWrite write, File resourcePath, int migrationVersion) throws IOException {
|
||||
protected void writePlatformDdl(DdlWrite write, File resourcePath, String fullVersion) throws IOException {
|
||||
|
||||
if (!write.isApplyEmpty()) {
|
||||
FileWriter applyWriter = createWriter(resourcePath, migrationVersion, "apply.sql");
|
||||
FileWriter applyWriter = createWriter(resourcePath, fullVersion, "", config.getApplySuffix());
|
||||
try {
|
||||
writeApplyDdl(applyWriter, write);
|
||||
applyWriter.flush();
|
||||
} finally {
|
||||
applyWriter.close();
|
||||
}
|
||||
|
||||
if (!write.isApplyRollbackEmpty()) {
|
||||
FileWriter applyRollbackWriter = createWriter(resourcePath, migrationVersion, "applyRollback.sql");
|
||||
try {
|
||||
writeApplyRollbackDdl(applyRollbackWriter, write);
|
||||
applyRollbackWriter.flush();
|
||||
} finally {
|
||||
applyRollbackWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!write.isDropEmpty()) {
|
||||
FileWriter dropWriter = createWriter(resourcePath, migrationVersion, "drop.sql");
|
||||
try {
|
||||
writeDropDdl(dropWriter, write);
|
||||
dropWriter.flush();
|
||||
} finally {
|
||||
dropWriter.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected FileWriter createWriter(File resourcePath, int migrationVersion, String suffix) throws IOException {
|
||||
protected FileWriter createWriter(File path, String fullVersion, String subPath, String suffix) throws IOException {
|
||||
|
||||
File applyFile = new File(resourcePath, "v" + migrationVersion + ".0-" + platformPrefix + suffix);
|
||||
String fileName = fullVersion;
|
||||
if (!platformPrefix.isEmpty()) {
|
||||
fileName += "-"+platformPrefix;
|
||||
}
|
||||
if (subPath != null && !subPath.isEmpty()) {
|
||||
path = subPath(path, subPath);
|
||||
}
|
||||
fileName += suffix;
|
||||
File applyFile = new File(path, fileName);
|
||||
return new FileWriter(applyFile);
|
||||
}
|
||||
|
||||
protected File subPath(File path, String suffix) {
|
||||
File subPath = new File(path, suffix);
|
||||
if (!subPath.exists()) {
|
||||
subPath.mkdirs();
|
||||
}
|
||||
return subPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the 'Apply' DDL buffers to the writer.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebean.dbmigration.migration.IdentityType;
|
||||
import com.avaje.ebean.dbmigration.model.MColumn;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.dbmigration.model.visitor.BeanPropertyVisitor;
|
||||
import com.avaje.ebean.dbmigration.model.visitor.BeanVisitor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
@@ -29,16 +28,17 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
|
||||
* This creates an MTable and adds it to the model.
|
||||
* </p>
|
||||
*/
|
||||
public BeanPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
|
||||
public ModelBuildPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
|
||||
|
||||
if (!descriptor.isInheritanceRoot()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MTable table = new MTable(descriptor.getBaseTable());
|
||||
table.setComment(descriptor.getDbComment());
|
||||
if (descriptor.isHistorySupport()) {
|
||||
table.setWithHistory(true);
|
||||
BeanProperty whenCreated = descriptor.findWhenCreatedProperty();
|
||||
BeanProperty whenCreated = descriptor.getWhenCreatedProperty();
|
||||
if (whenCreated != null) {
|
||||
table.setWhenCreatedColumn(whenCreated.getDbColumn());
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
|
||||
table.addColumn(new MColumn(discColumn, discDbType, true));
|
||||
}
|
||||
|
||||
return new ModelBuildPropertyVisitor(ctx, table, descriptor.getCompoundUniqueConstraints());
|
||||
return new ModelBuildPropertyVisitor(ctx, table, descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,11 +3,15 @@ package com.avaje.ebean.dbmigration.model.build;
|
||||
import com.avaje.ebean.config.DbConstraintNaming;
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebean.config.dbplatform.DbTypeMap;
|
||||
import com.avaje.ebean.dbmigration.model.MColumn;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
import com.avaje.ebean.dbmigration.model.ModelContainer;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The context used during DDL generation.
|
||||
*/
|
||||
@@ -31,6 +35,18 @@ public class ModelBuildContext {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the foreign key references on any draft tables (that reference other draft tables).
|
||||
* This is called as a 'second pass' after all the draft tables have been identified.
|
||||
*/
|
||||
public void adjustDraftReferences() {
|
||||
model.adjustDraftReferences();
|
||||
}
|
||||
|
||||
public String normaliseTable(String baseTable) {
|
||||
return constraintNaming.normaliseTable(baseTable);
|
||||
}
|
||||
|
||||
public String primaryKeyName(String tableName) {
|
||||
return maxLength(constraintNaming.primaryKeyName(tableName), 0);
|
||||
}
|
||||
@@ -120,4 +136,49 @@ public class ModelBuildContext {
|
||||
return dbTypeMap.get(dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the draft table for a given table.
|
||||
*/
|
||||
public void createDraft(MTable table, boolean draftable) {
|
||||
|
||||
MTable draftTable = table.createDraftTable();
|
||||
draftTable.setPkName(primaryKeyName(draftTable.getName()));
|
||||
|
||||
if (draftable) {
|
||||
// Add a FK from @Draftable live table back to it's draft table)
|
||||
List<MColumn> pkCols = table.primaryKeyColumns();
|
||||
if (pkCols.size() == 1) {
|
||||
// only doing this for single column PK at this stage
|
||||
MColumn pk = pkCols.get(0);
|
||||
pk.setReferences(draftTable.getName() + "." + pk.getName());
|
||||
pk.setForeignKeyName(foreignKeyConstraintName(table.getName(), pk.getName(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
int fkCount = 0;
|
||||
int ixCount = 0;
|
||||
int uqCount = 0;
|
||||
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
|
||||
// after we know all the tables that are 'draftable'
|
||||
//col.setReferences(refTable + "." + refColumn);
|
||||
col.setForeignKeyName(foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount));
|
||||
|
||||
String[] indexCols = {col.getName()};
|
||||
col.setForeignKeyIndex(foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
|
||||
}
|
||||
// adjust the unique constraint names
|
||||
if (col.getUnique() != null){
|
||||
col.setUnique(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
|
||||
}
|
||||
if (col.getUniqueOneToOne() != null){
|
||||
col.setUniqueOneToOne(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
|
||||
}
|
||||
}
|
||||
|
||||
addTable(draftTable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user