mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
371
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcaf11f262 | ||
|
|
05df47c0d7 | ||
|
|
a8892292fb | ||
|
|
e876ef8d87 | ||
|
|
315b39813b | ||
|
|
6d5e0c4766 | ||
|
|
a2ce17aec0 | ||
|
|
fcffa92c55 | ||
|
|
7fda8079c2 | ||
|
|
cca9bb4427 | ||
|
|
f45089240a | ||
|
|
8252a51545 | ||
|
|
c440c768cf | ||
|
|
c6ae501354 | ||
|
|
70e2b1c2ae | ||
|
|
211f28eea3 | ||
|
|
174fe46485 | ||
|
|
890cc6f66d | ||
|
|
2f0e5e6e46 | ||
|
|
54ba78218b | ||
|
|
ba16cea405 | ||
|
|
e81a61378c | ||
|
|
b4154031cc | ||
|
|
ecf5b56652 | ||
|
|
426c6c8111 | ||
|
|
9dbb69f4f4 | ||
|
|
83776a1eae | ||
|
|
6bc72ae6bb | ||
|
|
4fe2016891 | ||
|
|
7be4675d3e | ||
|
|
06b71781f5 | ||
|
|
55e4c7bc26 | ||
|
|
b3584a81b4 | ||
|
|
68da8944ed | ||
|
|
903ee35d63 | ||
|
|
658ee3d39f | ||
|
|
c074b7c457 | ||
|
|
528ad7c7fb | ||
|
|
c3f41f4157 | ||
|
|
33d104e6aa | ||
|
|
4bd3a0f293 | ||
|
|
54a4fd75f1 | ||
|
|
105f08cca1 | ||
|
|
01d9dd3fe8 | ||
|
|
f09b38c58c | ||
|
|
cb6d2e7ec0 | ||
|
|
89c0324e42 | ||
|
|
7b4215f4f8 | ||
|
|
e4cb5f0443 | ||
|
|
ec24374d72 | ||
|
|
cfba4d2deb | ||
|
|
0ab019ca2c | ||
|
|
8c9dae0f88 | ||
|
|
88a0c34d37 | ||
|
|
ce403e72a7 | ||
|
|
3c6a52da31 | ||
|
|
2806972032 | ||
|
|
acd6ba0a42 | ||
|
|
b386c9a5a8 | ||
|
|
c5536aaeec | ||
|
|
1b2bfc5df8 | ||
|
|
808004165c | ||
|
|
61eedb4e9a | ||
|
|
662ffdd070 | ||
|
|
13a5511318 | ||
|
|
0f9c98964e | ||
|
|
b87e6d90f6 | ||
|
|
166906a814 | ||
|
|
1733d2382c | ||
|
|
a6f9120905 | ||
|
|
32b599512b | ||
|
|
34fd24d620 | ||
|
|
f928923b5b | ||
|
|
1f928e65a4 | ||
|
|
d920d8eb76 | ||
|
|
23d2c64046 | ||
|
|
19d29cd836 | ||
|
|
f14c3e8cb4 | ||
|
|
b9ca8be95a | ||
|
|
d627bb47e2 | ||
|
|
d395c666fc | ||
|
|
939397acfc | ||
|
|
fecb5fa731 | ||
|
|
bdc9917811 | ||
|
|
afcd6859e0 | ||
|
|
fef87323b3 | ||
|
|
6909b20a9e | ||
|
|
c1a479e1f4 | ||
|
|
e06967ff4a | ||
|
|
156469ae0f | ||
|
|
1d1d479dda | ||
|
|
d185ea5962 | ||
|
|
2dbb5ea80d | ||
|
|
158ee9a081 | ||
|
|
b1410cc660 | ||
|
|
4c2e9ccacd | ||
|
|
29aa395cd4 | ||
|
|
8160d1f01c | ||
|
|
6fd76ff697 | ||
|
|
249cc5df49 | ||
|
|
2646e8c27f | ||
|
|
3eaf47fd70 | ||
|
|
9d64352771 | ||
|
|
aa7325b249 | ||
|
|
2839434773 | ||
|
|
de9823b69b | ||
|
|
15cab24848 | ||
|
|
b87e360ac7 | ||
|
|
82e68d7756 | ||
|
|
0371043914 | ||
|
|
54b5684628 | ||
|
|
da95ec0c05 | ||
|
|
b5eec93995 | ||
|
|
fc26765a7e | ||
|
|
9a6d339449 | ||
|
|
5b09443cdf | ||
|
|
31093babe3 | ||
|
|
f8b9ca2034 | ||
|
|
c27749ae2d | ||
|
|
2c04430185 | ||
|
|
dcde47daf6 | ||
|
|
57056d7abd | ||
|
|
3409f264ec | ||
|
|
6a61614430 | ||
|
|
9734ae5787 | ||
|
|
b817d5ee4d | ||
|
|
0036473971 | ||
|
|
a020bb15a1 | ||
|
|
efa86043bf | ||
|
|
eb1ea5ddd2 | ||
|
|
a83a4fa1c4 | ||
|
|
fe71c7cc4e | ||
|
|
5522d56e8d | ||
|
|
7e995efb62 | ||
|
|
ef46bc86cc | ||
|
|
0e75c50be5 | ||
|
|
cd1cdb027f | ||
|
|
66703a3a29 | ||
|
|
56e65d7463 | ||
|
|
eb7277aa13 | ||
|
|
42f9e74d91 | ||
|
|
be24e11b91 | ||
|
|
42d3e9eb91 | ||
|
|
bb164a0763 | ||
|
|
3a50c264b1 | ||
|
|
0e7dc4209c | ||
|
|
72845acbad | ||
|
|
29d88d7de8 | ||
|
|
c81d329100 | ||
|
|
7c1c144af5 | ||
|
|
7809858634 | ||
|
|
bd3cb3c547 | ||
|
|
db25cb7bfb | ||
|
|
55a9ba7170 | ||
|
|
2df2224286 | ||
|
|
e1e8092144 | ||
|
|
4d4886d808 | ||
|
|
d11bb2c4e5 | ||
|
|
663b1bb4aa | ||
|
|
f3d26e32ec | ||
|
|
5f83584315 | ||
|
|
d28e3ee456 | ||
|
|
e1b7161b22 | ||
|
|
f63a3cb926 | ||
|
|
29c5a17202 | ||
|
|
340b100a1a | ||
|
|
80975acfbf | ||
|
|
95dab97b1b | ||
|
|
ce10dd79f9 | ||
|
|
3f12739e71 | ||
|
|
cd1ae20015 | ||
|
|
6d1e3a08e1 | ||
|
|
94e6fe90e5 | ||
|
|
9bbe99657a | ||
|
|
0faf9f54ea | ||
|
|
046a9b30b0 | ||
|
|
afb7679302 | ||
|
|
db35f0f5ed | ||
|
|
19756421ad | ||
|
|
1d678576f7 | ||
|
|
ded7d44974 | ||
|
|
c9497fd654 | ||
|
|
23a9cf9bb3 | ||
|
|
75395a8481 | ||
|
|
a161d0c01b | ||
|
|
1dd7720605 | ||
|
|
3d2f885e87 | ||
|
|
2b3e485beb | ||
|
|
d4be0718a8 | ||
|
|
6d0556a120 | ||
|
|
ec7e4e4935 | ||
|
|
471605619a | ||
|
|
02fe956056 | ||
|
|
db13034fec | ||
|
|
c2489beb82 | ||
|
|
9a424d7d6d | ||
|
|
9b74602617 | ||
|
|
5c99986476 | ||
|
|
7d38933e9d | ||
|
|
7668fb11a9 | ||
|
|
eac68166d9 | ||
|
|
cb0023345f | ||
|
|
f0f7feb125 | ||
|
|
c8e32253a5 | ||
|
|
01541bb208 | ||
|
|
2d44d06f36 | ||
|
|
fda930a162 | ||
|
|
5a6cdb9a86 | ||
|
|
5ef7fe8504 | ||
|
|
11586fb635 | ||
|
|
d555122067 | ||
|
|
b6d3dc3516 | ||
|
|
bbf54d6196 | ||
|
|
74c88ce511 | ||
|
|
309bf6a11a | ||
|
|
02b6ab0f74 | ||
|
|
0c41993ce7 | ||
|
|
d949c48245 | ||
|
|
93e603ca25 | ||
|
|
117d22fd67 | ||
|
|
f13de2c8a6 | ||
|
|
5b8ef8154a | ||
|
|
df892f4509 | ||
|
|
2755e6d1ec | ||
|
|
2024193da9 | ||
|
|
72788c7428 | ||
|
|
8789709fb8 | ||
|
|
588aaf9361 | ||
|
|
68649d0e98 | ||
|
|
f6047d3b73 | ||
|
|
629e096e28 | ||
|
|
c981944c8e | ||
|
|
1ba9f4e9de | ||
|
|
ce02e7ce70 | ||
|
|
1d9a2ed43a | ||
|
|
ae81ce5f47 | ||
|
|
9e42f98902 | ||
|
|
ada21729ec | ||
|
|
8b7d882b8a | ||
|
|
cf2d9df256 | ||
|
|
48b05eb358 | ||
|
|
0796fc0d88 | ||
|
|
df585644e9 | ||
|
|
ff3c5b3d82 | ||
|
|
1a03e84f0e | ||
|
|
49ab17cd34 | ||
|
|
9cd189034c | ||
|
|
ed566ca153 | ||
|
|
9159cec36c | ||
|
|
b58fc2df04 | ||
|
|
dd8607b072 | ||
|
|
a5a8765bd0 | ||
|
|
ae55b58438 | ||
|
|
671d082ad3 | ||
|
|
43e95d81f6 | ||
|
|
3a64cf6952 | ||
|
|
8d976f83a9 | ||
|
|
117737c790 | ||
|
|
085843339f | ||
|
|
bf33b3f96f | ||
|
|
e1d4159453 | ||
|
|
4f1f3cadf9 | ||
|
|
01e1a4642b | ||
|
|
3a75e1f66c | ||
|
|
47ac4c5c5b | ||
|
|
9c73309dfd | ||
|
|
bd3c99e67f | ||
|
|
5adbebaf6e | ||
|
|
6a9ab49ef0 | ||
|
|
4308a98a0a | ||
|
|
f86f1b2647 | ||
|
|
96cc8d7605 | ||
|
|
59537227b6 | ||
|
|
81c6d4601d | ||
|
|
e21839de91 | ||
|
|
a61db2ab35 | ||
|
|
bddfa3bc5f | ||
|
|
9ad460ecd8 | ||
|
|
50593a7f9d | ||
|
|
c088cfa339 | ||
|
|
e683e25a95 | ||
|
|
c7052c5bfa | ||
|
|
698ee2198d | ||
|
|
91c4eca528 | ||
|
|
81897bd54d | ||
|
|
2427630c07 | ||
|
|
3238c0def4 | ||
|
|
93f75151f0 | ||
|
|
32e9b48359 | ||
|
|
258e29d2f0 | ||
|
|
ec2f1f7243 | ||
|
|
5775985468 | ||
|
|
4788dd4ec9 | ||
|
|
ca7e5b79b8 | ||
|
|
90dfbcd7b3 | ||
|
|
6b39bad4da | ||
|
|
25dea0b5b0 | ||
|
|
0069d2fabc | ||
|
|
98964638ba | ||
|
|
4573a995f1 | ||
|
|
4bc80cc724 | ||
|
|
a2972abe3e | ||
|
|
54eaf561f4 | ||
|
|
3134dd3165 | ||
|
|
8fc6abd480 | ||
|
|
f031bb72b6 | ||
|
|
42f0b419e8 | ||
|
|
7cbae758fc | ||
|
|
fb32cfa170 | ||
|
|
ffa8268125 | ||
|
|
eba1003c19 | ||
|
|
eca6383dba | ||
|
|
624d08f46c | ||
|
|
14749daf9b | ||
|
|
2817875706 | ||
|
|
3f8d705439 | ||
|
|
e9faef2692 | ||
|
|
d459807af7 | ||
|
|
10ad9ef096 | ||
|
|
11e3649b85 | ||
|
|
ddc00fedb6 | ||
|
|
0aa9208dcc | ||
|
|
e3b1996dc9 | ||
|
|
24ca744e06 | ||
|
|
7f9ce5bdd4 | ||
|
|
23e5de0819 | ||
|
|
c23508819f | ||
|
|
af030e3da4 | ||
|
|
d8e67d09a9 | ||
|
|
66c08e663e | ||
|
|
9a096e272e | ||
|
|
21e87e12cb | ||
|
|
7c6d7dd4d0 | ||
|
|
c5329e9db0 | ||
|
|
bccb9f8ddb | ||
|
|
c57af29fca | ||
|
|
37b6a321ab | ||
|
|
896f7d9ea8 | ||
|
|
141a0d9358 | ||
|
|
5a7f24da5d | ||
|
|
8ea86d6c6d | ||
|
|
fb5eeb60b1 | ||
|
|
33ec8abb9d | ||
|
|
bbd7baf1be | ||
|
|
2e36402a76 | ||
|
|
fa4af4ef98 | ||
|
|
ed6a9e2dbb | ||
|
|
42d83705b7 | ||
|
|
ae58d9223a | ||
|
|
a9a89938f9 | ||
|
|
4b91f5fe0b | ||
|
|
85d3caf079 | ||
|
|
f6d8c8b618 | ||
|
|
d0fa3f0cb1 | ||
|
|
41a31c1aa0 | ||
|
|
ab301f970c | ||
|
|
d1b7b082a2 | ||
|
|
5fb3a1e019 | ||
|
|
cb0dee998b | ||
|
|
53b606289d | ||
|
|
574d5e215b | ||
|
|
508f2129f5 | ||
|
|
cdfcbeec43 | ||
|
|
0e9782e6b3 | ||
|
|
927fa5f7d3 | ||
|
|
53a80c7dd1 | ||
|
|
89789f3347 | ||
|
|
9dc5ca602c | ||
|
|
95737cd3c5 | ||
|
|
0a49fc9b62 | ||
|
|
f8c80c7fd2 |
@@ -151,3 +151,18 @@ In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
|
||||
TODO: not yet tested
|
||||
|
||||
|
||||
|
||||
'hana' platform
|
||||
--------------------
|
||||
|
||||
Requires an installed SAP HANA server - e.g. https://store.docker.com/images/sap-hana-express-edition
|
||||
|
||||
- Create a user `EBEAN_TEST` with password `Eb3an_test` in the tenant database `HXE`
|
||||
- Adjust the connection string and/or username in `ebean.properties`
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=hana`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 2486, Failures: 0, Errors: 0, Skipped: 84
|
||||
|
||||
|
||||
|
||||
@@ -9,15 +9,20 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.15.6</version>
|
||||
<version>11.28.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<jackson-core.version>2.9.5</jackson-core.version>
|
||||
<snakeyaml.version>1.21</snakeyaml.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.15.6</tag>
|
||||
<tag>ebean-11.28.2</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -89,7 +94,7 @@
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.19</version>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
@@ -112,13 +117,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>3.11</version>
|
||||
<version>4.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-datasource</artifactId>
|
||||
<version>4.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -130,7 +135,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.5.4</version>
|
||||
<version>11.11.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -143,14 +148,14 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -187,7 +192,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.4.1212</version>
|
||||
<version>42.2.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -224,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.11.1</version>
|
||||
<version>11.25.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -233,7 +238,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.196</version>
|
||||
<version>1.4.197</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -266,6 +271,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sap.cloud.db.jdbc</groupId>
|
||||
<artifactId>ngdbc</artifactId>
|
||||
<version>2.3.48</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje.composite</groupId>
|
||||
<artifactId>avaje-composite-testing</artifactId>
|
||||
@@ -297,7 +309,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.11.1</version>
|
||||
<version>11.25.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
@@ -355,7 +367,7 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<doctitle>Ebean 10</doctitle>
|
||||
<doctitle>Ebean 11</doctitle>
|
||||
<overview>src/main/java/io/ebean/overview.html</overview>
|
||||
<source>1.8</source>
|
||||
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
@@ -115,6 +117,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
db().save(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans in the collection.
|
||||
*/
|
||||
public int saveAll(Collection<T> bean) {
|
||||
return db().saveAll(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this entity.
|
||||
*
|
||||
@@ -154,6 +163,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
return db().delete(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection.
|
||||
*/
|
||||
public int deleteAll(Collection<T> beans) {
|
||||
return db().deleteAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete.
|
||||
* <p>
|
||||
@@ -167,6 +183,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
return db().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the default merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object)
|
||||
*/
|
||||
public void merge(T bean) {
|
||||
db().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the specified merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object, MergeOptions)
|
||||
*/
|
||||
public void merge(T bean, MergeOptions options) {
|
||||
db().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this entity from the database.
|
||||
*
|
||||
|
||||
@@ -2,7 +2,9 @@ package io.ebean;
|
||||
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.BeanNotEnhancedException;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.datasource.DataSourceConfigurationException;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
@@ -160,6 +162,15 @@ public final class Ebean {
|
||||
defaultServer = getWithCreate(defaultName.trim());
|
||||
}
|
||||
}
|
||||
} catch (BeanNotEnhancedException e) {
|
||||
throw e;
|
||||
|
||||
} catch (DataSourceConfigurationException e) {
|
||||
String msg = "Configuration error creating DataSource for the default EbeanServer." +
|
||||
" This typically means a missing application-test.yaml or missing ebean-test-config dependency." +
|
||||
" See https://ebean.io/docs/trouble-shooting#datasource";
|
||||
throw new DataSourceConfigurationException(msg, e);
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error trying to create the default EbeanServer", e);
|
||||
throw new RuntimeException(e);
|
||||
@@ -638,6 +649,15 @@ public final class Ebean {
|
||||
serverMgr.getDefaultServer().updateAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
public static void merge(Object bean) {
|
||||
serverMgr.getDefaultServer().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
|
||||
@@ -11,17 +11,13 @@ import io.ebean.text.json.JsonContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Provides the API for fetching and saving beans to a particular DataSource.
|
||||
@@ -808,411 +804,18 @@ public interface EbeanServer {
|
||||
<T> T getReference(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* Return the extended API for EbeanServer.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* The extended API has the options for executing queries that take an explicit
|
||||
* transaction as an argument.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* Typically we only need to use the extended API when we do NOT want to use the
|
||||
* usual ThreadLocal based mechanism to obtain the current transaction but instead
|
||||
* supply the transaction explicitly.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* <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()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} 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 the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* 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#findOne()} 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 the query to execute.
|
||||
* @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#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link #update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} 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 query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} 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 query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
ExtendedServer extended();
|
||||
|
||||
/**
|
||||
* Either Insert or Update the bean depending on its state.
|
||||
@@ -1628,6 +1231,13 @@ public interface EbeanServer {
|
||||
*/
|
||||
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options (no paths specified, default delete).
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
void merge(Object bean);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
@@ -1832,6 +1442,13 @@ public interface EbeanServer {
|
||||
*/
|
||||
JsonContext json();
|
||||
|
||||
/**
|
||||
* Return a ScriptRunner for running SQL or DDL scripts.
|
||||
* <p/>
|
||||
* Intended to use mostly in testing to run seed SQL scripts or truncate table scripts etc.
|
||||
*/
|
||||
ScriptRunner script();
|
||||
|
||||
/**
|
||||
* Return the Document store.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Class to determine the ebean version. (
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
@@ -13,31 +13,32 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class EbeanVersion {
|
||||
private EbeanVersion() {
|
||||
|
||||
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
|
||||
|
||||
|
||||
private static String version = "unknown";
|
||||
static {
|
||||
try {
|
||||
Properties prop = new Properties();
|
||||
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
}
|
||||
}
|
||||
logger.info("ebean version: {}", version);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not determine ebean version: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
|
||||
*/
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -133,11 +133,22 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression ieq(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Not Equal To - property not equal to the given value (typically
|
||||
* using a lower() function to make it case insensitive).
|
||||
*/
|
||||
Expression ine(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Equal To that allows for named parameter use.
|
||||
*/
|
||||
Expression ieqObject(String propertyName, Object value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Not Equal To that allows for named parameter use.
|
||||
*/
|
||||
Expression ineObject(String propertyName, Object value);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
|
||||
@@ -120,6 +120,14 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
@@ -158,6 +166,18 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
int delete();
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of rows that were deleted.
|
||||
*/
|
||||
int delete(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a update query.
|
||||
*
|
||||
@@ -166,6 +186,14 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
int update();
|
||||
|
||||
/**
|
||||
* Execute as a update query with the given transaction.
|
||||
*
|
||||
* @return the number of rows that were updated.
|
||||
* @see UpdateQuery
|
||||
*/
|
||||
int update(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query iterating over the results.
|
||||
*
|
||||
@@ -404,6 +432,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> select(String properties);
|
||||
|
||||
/**
|
||||
* Apply the fetchGroup which defines what part of the object graph to load.
|
||||
*/
|
||||
Query<T> select(FetchGroup<T> fetchGroup);
|
||||
|
||||
/**
|
||||
* Set whether this query uses DISTINCT.
|
||||
* <p>
|
||||
@@ -716,6 +749,12 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> ieq(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Not Equal To - property not equal to the given value (typically
|
||||
* using a lower() function to make it case insensitive).
|
||||
*/
|
||||
ExpressionList<T> ine(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
@@ -1023,6 +1062,20 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for the given bit flags to be NOT set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseNot(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The extended API for EbeanServer.
|
||||
* <p>
|
||||
* This provides the finder methods that take an explicit transaction rather than obtaining
|
||||
* the transaction from the usual mechanism (which is ThreadLocal based).
|
||||
* </p>
|
||||
* <p>
|
||||
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
|
||||
* the use of the mechanism to get the current transaction and instead explicitly supply
|
||||
* the transaction to use.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that in all cases the transaction supplied can be null and in this case the EbeanServer
|
||||
* will use the normal mechanism to obtain the transaction to use.
|
||||
* </p>
|
||||
*/
|
||||
public interface ExtendedServer {
|
||||
|
||||
/**
|
||||
* Return the NOW time from the Clock.
|
||||
*/
|
||||
long clockNow();
|
||||
|
||||
/**
|
||||
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
|
||||
* <p>
|
||||
* Note that we only expect to change the Clock for testing purposes.
|
||||
* </p>
|
||||
*/
|
||||
void setClock(Clock clock);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* <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()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} 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 the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* 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#findOne()} 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 the query to execute.
|
||||
* @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#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link EbeanServer#update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} 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 query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} 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 query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Defines what part of the object graph to load (select and fetch clauses).
|
||||
* <p>
|
||||
* Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative
|
||||
* to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load"
|
||||
* that can be defined separately from the query and combined with other FetchGroups.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Select example</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Select and fetch example</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Combining FetchGroups</h3>
|
||||
* <p>
|
||||
* FetchGroups can be combined together to form another FetchGroup.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Address> FG_ADDRESS = FetchGroup.of(Address.class)
|
||||
* .select("line1, line2, city")
|
||||
* .fetch("country", "name")
|
||||
* .build();
|
||||
*
|
||||
* FetchGroup<Customer> FG_CUSTOMER = FetchGroup.of(Customer.class)
|
||||
* .select("name, version")
|
||||
* .fetch("billingAddress", FG_ADDRESS)
|
||||
* .build();
|
||||
*
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(FG_CUSTOMER)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The bean type the Fetch group can be applied to
|
||||
*/
|
||||
public interface FetchGroup<T> {
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
* <p>
|
||||
* We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param select The select clause of the FetchGroup
|
||||
*
|
||||
* @return The FetchGroup with the given select clause
|
||||
*/
|
||||
@Nonnull
|
||||
static <T> FetchGroup<T> of(Class<T> cls, String select) {
|
||||
return XServiceProvider.fetchGroupOf(cls, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to.
|
||||
* <p>
|
||||
* We chain select() with one or more fetch() clauses to define the object graph to load.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to
|
||||
*/
|
||||
@Nonnull
|
||||
static <T> FetchGroupBuilder<T> of(Class<T> cls) {
|
||||
return XServiceProvider.fetchGroupOf(cls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Builds a FetchGroup by adding fetch clauses.
|
||||
* <p>
|
||||
* We add select() and fetch() clauses to define the object graph we want to load.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup fetchGroup = FetchGroup
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .where()
|
||||
* ...
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FetchGroupBuilder<T> {
|
||||
|
||||
/**
|
||||
* Specify specific properties to select (top level properties).
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> select(String select);
|
||||
|
||||
/**
|
||||
* Fetch all the properties at the given path.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path using a query join with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path lazily with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path, String properties);
|
||||
|
||||
/**
|
||||
* Fetch the path including all its properties using a query join.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties using a query join.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path, String properties);
|
||||
|
||||
/**
|
||||
* Fetch the path including all its properties lazily.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties lazily.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path, String properties);
|
||||
|
||||
/**
|
||||
* Build and return the FetchGroup.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroup<T> build();
|
||||
}
|
||||
@@ -8,11 +8,20 @@ import java.util.Set;
|
||||
*/
|
||||
public class MergeOptionsBuilder {
|
||||
|
||||
private static final MOptions DEFAULT_OPTIONS = new MOptions();
|
||||
|
||||
private Set<String> paths = new LinkedHashSet<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
private boolean deletePermanent = true;
|
||||
private boolean deletePermanent;
|
||||
|
||||
/**
|
||||
* Return the default options.
|
||||
*/
|
||||
public static MergeOptions defaultOptions() {
|
||||
return DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path that will included in the merge.
|
||||
@@ -47,7 +56,7 @@ public class MergeOptionsBuilder {
|
||||
/**
|
||||
* Build and return the MergeOptions instance.
|
||||
*/
|
||||
public MOptions build() {
|
||||
public MergeOptions build() {
|
||||
return new MOptions(paths, clientGeneratedIds, deletePermanent);
|
||||
}
|
||||
|
||||
@@ -57,12 +66,19 @@ public class MergeOptionsBuilder {
|
||||
private final boolean deletePermanent;
|
||||
private final Set<String> paths;
|
||||
|
||||
private MOptions(){
|
||||
this.clientGeneratedIds = false;
|
||||
this.paths = new LinkedHashSet<>();
|
||||
this.deletePermanent = false;
|
||||
}
|
||||
|
||||
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
|
||||
this.paths = paths;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
this.deletePermanent = deletePermanent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> paths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
/**
|
||||
* A MappedSuperclass base class that provides convenience methods for inserting, updating and
|
||||
@@ -74,7 +73,6 @@ import javax.persistence.MappedSuperclass;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class Model {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an Order By for a Query.
|
||||
@@ -74,6 +77,15 @@ public final class OrderBy<T> implements Serializable {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with ascending order to this OrderBy.
|
||||
*/
|
||||
public Query<T> asc(String propertyName, String collation) {
|
||||
|
||||
list.add(new Property(propertyName, true, collation));
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with descending order to this OrderBy.
|
||||
*/
|
||||
@@ -83,6 +95,16 @@ public final class OrderBy<T> implements Serializable {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with descending order to this OrderBy.
|
||||
*/
|
||||
public Query<T> desc(String propertyName, String collation) {
|
||||
|
||||
list.add(new Property(propertyName, false, collation));
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the property is known to be contained in the order by clause.
|
||||
*/
|
||||
@@ -231,6 +253,8 @@ public final class OrderBy<T> implements Serializable {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
private String collation;
|
||||
|
||||
private String nulls;
|
||||
|
||||
private String highLow;
|
||||
@@ -247,17 +271,32 @@ public final class OrderBy<T> implements Serializable {
|
||||
this.highLow = highLow;
|
||||
}
|
||||
|
||||
public Property(String property, boolean ascending, String collation) {
|
||||
this.property = property;
|
||||
this.ascending = ascending;
|
||||
this.collation = collation;
|
||||
}
|
||||
|
||||
public Property(String property, boolean ascending, String collation, String nulls, String highLow) {
|
||||
this.property = property;
|
||||
this.ascending = ascending;
|
||||
this.collation = collation;
|
||||
this.nulls = nulls;
|
||||
this.highLow = highLow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this Property with the path trimmed.
|
||||
*/
|
||||
public Property copyWithTrim(String path) {
|
||||
return new Property(property.substring(path.length() + 1), ascending, nulls, highLow);
|
||||
return new Property(property.substring(path.length() + 1), ascending, collation, nulls, highLow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = property.hashCode();
|
||||
hc = hc * 92821 + (ascending ? 0 : 1);
|
||||
hc = hc * 92821 + (collation == null ? 0 : collation.hashCode());
|
||||
hc = hc * 92821 + (nulls == null ? 0 : nulls.hashCode());
|
||||
hc = hc * 92821 + (highLow == null ? 0 : highLow.hashCode());
|
||||
return hc;
|
||||
@@ -274,8 +313,9 @@ public final class OrderBy<T> implements Serializable {
|
||||
Property e = (Property) obj;
|
||||
if (ascending != e.ascending) return false;
|
||||
if (!property.equals(e.property)) return false;
|
||||
if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false;
|
||||
return highLow != null ? highLow.equals(e.highLow) : e.highLow == null;
|
||||
if (!Objects.equals(collation, e.collation)) return false;
|
||||
if (!Objects.equals(nulls, e.nulls)) return false;
|
||||
return Objects.equals(highLow, e.highLow);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,7 +324,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
}
|
||||
|
||||
public String toStringFormat() {
|
||||
if (nulls == null) {
|
||||
if (nulls == null && collation == null) {
|
||||
if (ascending) {
|
||||
return property;
|
||||
} else {
|
||||
@@ -292,11 +332,23 @@ public final class OrderBy<T> implements Serializable {
|
||||
}
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(property);
|
||||
if (collation != null) {
|
||||
if (collation.contains("${}")) {
|
||||
// this is a complex collation, e.g. DB2 - we must replace the property
|
||||
sb.append(StringHelper.replaceString(collation, "${}", property));
|
||||
} else {
|
||||
sb.append(property);
|
||||
sb.append(" collate ").append(collation);
|
||||
}
|
||||
} else {
|
||||
sb.append(property);
|
||||
}
|
||||
if (!ascending) {
|
||||
sb.append(" ").append("desc");
|
||||
}
|
||||
sb.append(" ").append(nulls).append(" ").append(highLow);
|
||||
if (nulls != null) {
|
||||
sb.append(" ").append(nulls).append(" ").append(highLow);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -319,7 +371,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
* Return a copy of this property.
|
||||
*/
|
||||
public Property copy() {
|
||||
return new Property(property, ascending, nulls, highLow);
|
||||
return new Property(property, ascending, collation, nulls, highLow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -125,7 +126,7 @@ public class Pairs {
|
||||
* Return all the value pairs.
|
||||
*/
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
return Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,6 @@ class PrimaryServer {
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = "db";
|
||||
}
|
||||
System.setProperty("ebean_db", defaultServerName);
|
||||
return defaultServerName;
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +397,11 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> select(String fetchProperties);
|
||||
|
||||
/**
|
||||
* Apply the fetchGroup which defines what part of the object graph to load.
|
||||
*/
|
||||
Query<T> select(FetchGroup<T> fetchGroup);
|
||||
|
||||
/**
|
||||
* Specify a path to fetch eagerly including specific properties.
|
||||
* <p>
|
||||
@@ -602,8 +607,6 @@ public interface Query<T> {
|
||||
* <p>
|
||||
* This query will execute against the EbeanServer that was used to create it.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#findIds(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findIds();
|
||||
@@ -740,8 +743,6 @@ public interface Query<T> {
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findList(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
@@ -759,8 +760,6 @@ public interface Query<T> {
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findSet(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
Set<T> findSet();
|
||||
@@ -782,8 +781,6 @@ public interface Query<T> {
|
||||
* .findMap();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findMap(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<K> Map<K, T> findMap();
|
||||
@@ -927,11 +924,31 @@ public interface Query<T> {
|
||||
*/
|
||||
int delete();
|
||||
|
||||
/**
|
||||
* Execute as a delete query returning the number of rows deleted using the given transaction.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of beans/rows that were deleted.
|
||||
*/
|
||||
int delete(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the UpdateQuery returning the number of rows updated.
|
||||
*
|
||||
* @return the number of beans/rows updated.
|
||||
*/
|
||||
int update();
|
||||
|
||||
/**
|
||||
* Execute the UpdateQuery returning the number of rows updated using the given transaction.
|
||||
*
|
||||
* @return the number of beans/rows updated.
|
||||
*/
|
||||
int update(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the count of entities this query should return.
|
||||
* <p>
|
||||
@@ -1488,15 +1505,6 @@ public interface Query<T> {
|
||||
*/
|
||||
String getGeneratedSql();
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to forUpdate().
|
||||
*
|
||||
* executed the select with "for update" which should lock the record
|
||||
* "on read"
|
||||
*/
|
||||
@Deprecated
|
||||
Query<T> setForUpdate(boolean forUpdate);
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowConsumer {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and process it.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
void accept(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to map raw JDBC ResultSet to objects.
|
||||
* <p>
|
||||
* This provides a low level mapping option with direct use of JDBC ResultSet
|
||||
* with the option of having logic in the mapping. For example, only map some
|
||||
* columns depending on the values read from other columns.
|
||||
* </p>
|
||||
* <p>
|
||||
* For straight mapping into beans then DtoQuery would be the first choice as
|
||||
* it can automatically map the ResultSet into beans.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* //
|
||||
* // A mapper from ResultSet into our CustomerDto bean
|
||||
* //
|
||||
* class CustomerMapper implements RowMapper<CustomerDto> {
|
||||
*
|
||||
* @Override
|
||||
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
|
||||
*
|
||||
* long id = rset.getLong(1);
|
||||
* String name = rset.getString(2);
|
||||
* String status = rset.getString(3);
|
||||
*
|
||||
* return new CustomerDto(id, name, status);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* //
|
||||
* // Then use the mapper
|
||||
* //
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer where name = ?";
|
||||
*
|
||||
* CustomerDto rob = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, "Rob")
|
||||
* .findOne(CUSTOMER_MAPPER);
|
||||
*
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The type the row data is mapped into.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and map to the return type.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
T map(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Runs DDL and SQL scripts.
|
||||
* <p/>
|
||||
* Typically these are scripts used for testing such as seed SQL scripts or truncate SQL scripts.
|
||||
* <p/>
|
||||
* Scripts are executed in their own transaction and committed on successful completion.
|
||||
*
|
||||
* <h3>Example of simple use</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* EbeanServer server = Ebean.getDefaultServer();
|
||||
* server.script().run("/scripts/test-script.sql");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*
|
||||
* <h3>Example using place holders in the script</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Map<String,String> placeholders = new HashMap<>();
|
||||
* placeholders.put("tableName", "e_basic");
|
||||
*
|
||||
* EbeanServer server = Ebean.getDefaultServer();
|
||||
* server.script().run("/scripts/test-script.sql");
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ScriptRunner {
|
||||
|
||||
/**
|
||||
* Run a script given the resource path (that should start with "/").
|
||||
*/
|
||||
void run(String path);
|
||||
|
||||
/**
|
||||
* Run a script given the resource path (that should start with "/") and place holders.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* Map<String,String> placeholders = new HashMap<>();
|
||||
* placeholders.put("tableName", "e_basic");
|
||||
*
|
||||
* EbeanServer server = Ebean.getDefaultServer();
|
||||
* server.script().run("/scripts/test-script.sql");
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void run(String path, Map<String, String> placeholderMap);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the resource.
|
||||
*/
|
||||
void run(URL resource);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the resource and place holders.
|
||||
*/
|
||||
void run(URL resource, Map<String, String> placeholderMap);
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package io.ebean;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -77,12 +78,118 @@ public interface SqlQuery extends Serializable {
|
||||
@Nullable
|
||||
SqlRow findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> T findOne(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> List<T> findList(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query reading each row from ResultSet using the RowConsumer.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet
|
||||
* and is good for processing very large results where (unlike findList) we don't
|
||||
* hold all the results in memory but instead can process row by row.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer Used to read and process each ResultSet row.
|
||||
*/
|
||||
void findEachRow(RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional row.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single scalar attribute.
|
||||
* <pre>@{code
|
||||
*
|
||||
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
|
||||
*
|
||||
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 2)
|
||||
* .findSingleAttribute(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> T findSingleAttribute(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single BigDecimal value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
BigDecimal findSingleDecimal();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single Long value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(Long.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
Long findSingleLong();
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of scalar attribute values.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql =
|
||||
* " select (unit_price * order_qty) " +
|
||||
* " from o_order_detail " +
|
||||
* " where unit_price > ? " +
|
||||
* " order by (unit_price * order_qty) desc";
|
||||
*
|
||||
* //
|
||||
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 3)
|
||||
* .findSingleAttributeList(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
|
||||
@@ -19,15 +19,17 @@ package io.ebean;
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // example that uses 'named' parameters
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
* // example that uses 'named' parameters
|
||||
*
|
||||
* int modifiedCount = Ebean.execute(update);
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
*
|
||||
* int modifiedCount = update.execute();
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
@@ -57,6 +59,33 @@ package io.ebean;
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* int[] rows = insert.executeBatch();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -69,6 +98,11 @@ public interface SqlUpdate {
|
||||
/**
|
||||
* Execute the update returning the number of rows modified.
|
||||
* <p>
|
||||
* Note that if the transaction has batch mode on then this update will use JDBC batch and may not execute until
|
||||
* later - at commit time or a transaction flush. In this case this method returns -1 indicating that the
|
||||
* update has been batched for later execution.
|
||||
* </p>
|
||||
* <p>
|
||||
* After you have executed the SqlUpdate you can bind new variables using
|
||||
* {@link #setParameter(String, Object)} etc and then execute the SqlUpdate
|
||||
* again.
|
||||
@@ -83,6 +117,23 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Execute the statement now regardless of the JDBC batch mode of the transaction.
|
||||
*/
|
||||
int executeNow();
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
* @return The row counts for each of the batched statements.
|
||||
*/
|
||||
int[] executeBatch();
|
||||
|
||||
/**
|
||||
* Add the statement to batch processing to then later execute via executeBatch().
|
||||
*/
|
||||
void addBatch();
|
||||
|
||||
/**
|
||||
* Return the generated key value.
|
||||
*/
|
||||
|
||||
@@ -84,7 +84,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* This is similar to commit() but leaves the transaction "Active".
|
||||
* </p>
|
||||
* <h3>Functions/h3>
|
||||
* <h3>Functions</h3>
|
||||
* <ul>
|
||||
* <li>Flush the JDBC batch buffer</li>
|
||||
* <li>Call commit on the underlying JDBC connection</li>
|
||||
@@ -100,7 +100,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* This performs commit and completes the transaction closing underlying resources and
|
||||
* marking the transaction as "In active".
|
||||
* </p>
|
||||
* <h3>Functions/h3>
|
||||
* <h3>Functions</h3>
|
||||
* <ul>
|
||||
* <li>Flush the JDBC batch buffer</li>
|
||||
* <li>Call commit on the underlying JDBC connection</li>
|
||||
@@ -117,7 +117,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* This performs rollback, closes underlying resources and marks the transaction as "In active".
|
||||
* </p>
|
||||
* <h3>Functions/h3>
|
||||
* <h3>Functions</h3>
|
||||
* <ul>
|
||||
* <li>Call rollback on the underlying JDBC connection</li>
|
||||
* <li>Trigger any registered TransactionCallbacks</li>
|
||||
@@ -358,25 +358,13 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void setBatchMode(boolean useBatch);
|
||||
|
||||
/**
|
||||
* The JDBC batch mode to use for this transaction.
|
||||
* <p>
|
||||
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
|
||||
* and this would be useful if the request cascades to detail beans.
|
||||
* </p>
|
||||
*
|
||||
* @param persistBatchMode the batch mode to use for this transaction
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
|
||||
*/
|
||||
void setBatch(PersistBatch persistBatchMode);
|
||||
|
||||
/**
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
PersistBatch getBatch();
|
||||
boolean isBatchMode();
|
||||
|
||||
/**
|
||||
* Set the JDBC batch mode to use for a save() or delete() request.
|
||||
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
|
||||
* <p>
|
||||
* This only takes effect when batch mode on the transaction has not already meant that
|
||||
* JDBC batch mode is being used.
|
||||
@@ -385,16 +373,19 @@ public interface Transaction extends AutoCloseable {
|
||||
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
|
||||
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is effectively already turned on for all platforms apart from older Sql Server.
|
||||
* </p>
|
||||
*
|
||||
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
|
||||
*/
|
||||
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
|
||||
void setBatchOnCascade(boolean batchMode);
|
||||
|
||||
/**
|
||||
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
|
||||
* Return the batch mode at the request level.
|
||||
*/
|
||||
PersistBatch getBatchOnCascade();
|
||||
boolean isBatchOnCascade();
|
||||
|
||||
/**
|
||||
* Specify the number of statements before a batch is flushed automatically.
|
||||
|
||||
@@ -432,4 +432,11 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isBatchMode() {
|
||||
return PersistBatch.ALL.equals(batch);
|
||||
}
|
||||
|
||||
public boolean isBatchOnCascade() {
|
||||
return PersistBatch.ALL.equals(batchOnCascade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.service.SpiFetchGroupService;
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebean.service.SpiRawSqlService;
|
||||
|
||||
@@ -15,6 +16,16 @@ class XServiceProvider {
|
||||
|
||||
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
|
||||
|
||||
private static SpiFetchGroupService fetchGroupService = initSpiFetchGroupService();
|
||||
|
||||
private static SpiFetchGroupService initSpiFetchGroupService() {
|
||||
Iterator<SpiFetchGroupService> loader = ServiceLoader.load(SpiFetchGroupService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiFetchGroupService?");
|
||||
}
|
||||
|
||||
private static SpiRawSqlService initRawSql() {
|
||||
|
||||
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
|
||||
@@ -47,4 +58,17 @@ class XServiceProvider {
|
||||
return profileLocationFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
*/
|
||||
static <T> FetchGroup<T> fetchGroupOf(Class<T> cls, String select) {
|
||||
return fetchGroupService.of(cls, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroupBuilder with the given select clause.
|
||||
*/
|
||||
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
|
||||
return fetchGroupService.of(cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,21 +71,30 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
/**
|
||||
* Used when a bean is partially filled.
|
||||
*/
|
||||
private final boolean[] loadedProps;
|
||||
|
||||
private boolean fullyLoadedBean;
|
||||
private static final byte FLAG_LOADED_PROP = 1;
|
||||
|
||||
/**
|
||||
* Set of changed properties.
|
||||
*/
|
||||
private boolean[] changedProps;
|
||||
private static final byte FLAG_CHANGED_PROP = 2;
|
||||
|
||||
/**
|
||||
* Flags indicating if a property is a dirty embedded bean. Used to distingush
|
||||
* between an embedded bean being completely overwritten and one of its
|
||||
* embedded properties being made dirty.
|
||||
*/
|
||||
private boolean[] embeddedDirty;
|
||||
private static final byte FLAG_EMBEDDED_DIRTY = 4;
|
||||
|
||||
/**
|
||||
* Flags indicating if a property is a dirty embedded bean. Used to distingush
|
||||
* between an embedded bean being completely overwritten and one of its
|
||||
* embedded properties being made dirty.
|
||||
*/
|
||||
private static final byte FLAG_ORIG_VALUE_SET = 8;
|
||||
|
||||
private final byte[] flags;
|
||||
|
||||
private boolean fullyLoadedBean;
|
||||
|
||||
private Object[] origValues;
|
||||
|
||||
@@ -102,7 +111,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
public EntityBeanIntercept(Object ownerBean) {
|
||||
this.owner = (EntityBean) ownerBean;
|
||||
this.loadedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
this.flags = new byte[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,8 +222,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Check each property to see if the bean is partially loaded.
|
||||
*/
|
||||
public boolean isPartial() {
|
||||
for (boolean loadedProp : loadedProps) {
|
||||
if (!loadedProp) {
|
||||
for (byte flag : flags) {
|
||||
if ((flag & FLAG_LOADED_PROP) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -259,10 +268,10 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Return true if only the Id property has been loaded.
|
||||
*/
|
||||
public boolean hasIdOnly(int idIndex) {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if (i == idIndex) {
|
||||
if (!loadedProps[i]) return false;
|
||||
} else if (loadedProps[i]) {
|
||||
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
|
||||
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -284,9 +293,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
if (idPos > -1) {
|
||||
// For cases where properties are set on constructor
|
||||
// set every non Id property to unloaded (for lazy loading)
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if (i != idPos) {
|
||||
loadedProps[i] = false;
|
||||
flags[i] &= ~FLAG_LOADED_PROP;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +361,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.owner._ebean_setEmbeddedLoaded();
|
||||
this.lazyLoadProperty = -1;
|
||||
this.origValues = null;
|
||||
this.changedProps = null;
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
|
||||
}
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
@@ -475,7 +486,11 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
if (position == -1) {
|
||||
throw new IllegalArgumentException("Property " + propertyName + " not found");
|
||||
}
|
||||
loadedProps[position] = loaded;
|
||||
if (loaded) {
|
||||
flags[position] |= FLAG_LOADED_PROP;
|
||||
} else {
|
||||
flags[position] &= ~FLAG_LOADED_PROP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -483,22 +498,22 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* constructor.
|
||||
*/
|
||||
public void setPropertyUnloaded(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = false;
|
||||
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property to be loaded.
|
||||
*/
|
||||
public void setLoadedProperty(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all properties to be loaded (post insert).
|
||||
*/
|
||||
public void setLoadedPropertyAll() {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
loadedProps[i] = true;
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
flags[i] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,14 +521,14 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Return true if the property is loaded.
|
||||
*/
|
||||
public boolean isLoadedProperty(int propertyIndex) {
|
||||
return loadedProps[propertyIndex];
|
||||
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is considered changed.
|
||||
*/
|
||||
public boolean isChangedProperty(int propertyIndex) {
|
||||
return (changedProps != null && changedProps[propertyIndex]);
|
||||
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,8 +536,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* embedded properties is dirty.
|
||||
*/
|
||||
public boolean isDirtyProperty(int propertyIndex) {
|
||||
return (changedProps != null && changedProps[propertyIndex]
|
||||
|| embeddedDirty != null && embeddedDirty[propertyIndex]);
|
||||
return (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,27 +548,22 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
|
||||
public void setChangedProperty(int propertyIndex) {
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
changedProps[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_CHANGED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that an embedded bean has had one of its properties changed.
|
||||
*/
|
||||
private void setEmbeddedPropertyDirty(int propertyIndex) {
|
||||
if (embeddedDirty == null) {
|
||||
embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
embeddedDirty[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
|
||||
}
|
||||
|
||||
private void setOriginalValue(int propertyIndex, Object value) {
|
||||
if (origValues == null) {
|
||||
origValues = new Object[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
if (origValues[propertyIndex] == null) {
|
||||
if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
|
||||
flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
|
||||
origValues[propertyIndex] = value;
|
||||
}
|
||||
}
|
||||
@@ -574,13 +583,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
public void setNewBeanForUpdate() {
|
||||
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
if (loadedProps[i]) {
|
||||
changedProps[i] = true;
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
||||
flags[i] |= FLAG_CHANGED_PROP;
|
||||
}
|
||||
}
|
||||
setDirty(true);
|
||||
@@ -594,8 +599,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return null;
|
||||
}
|
||||
Set<String> props = new LinkedHashSet<>();
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
if (loadedProps[i]) {
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
||||
props.add(getProperty(i));
|
||||
}
|
||||
}
|
||||
@@ -609,12 +614,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
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;
|
||||
}
|
||||
// this, or an embedded property has been changed - recurse
|
||||
dirties[i] = (flags[i] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
|
||||
}
|
||||
return dirties;
|
||||
}
|
||||
@@ -634,11 +635,11 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void addDirtyPropertyNames(Set<String> props, String prefix) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
|
||||
props.add(propName);
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i) + ".");
|
||||
@@ -654,12 +655,12 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
String[] names = owner._ebean_getPropertyNames();
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
if (propertyNames.contains(names[i])) {
|
||||
return true;
|
||||
}
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
if (propertyNames.contains(names[i])) {
|
||||
return true;
|
||||
}
|
||||
@@ -683,15 +684,16 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void addDirtyPropertyValues(Map<String, ValuePair> dirtyValues, String prefix) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
|
||||
Object newVal = owner._ebean_getField(i);
|
||||
Object oldVal = getOrigValue(i);
|
||||
if (!areEqual(oldVal, newVal)) {
|
||||
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
|
||||
}
|
||||
|
||||
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
|
||||
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
|
||||
@@ -705,13 +707,15 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
Object newVal = owner._ebean_getField(i);
|
||||
Object oldVal = getOrigValue(i);
|
||||
visitor.visit(i, newVal, oldVal);
|
||||
if (!areEqual(oldVal, newVal)) {
|
||||
visitor.visit(i, newVal, oldVal);
|
||||
}
|
||||
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
visitor.visitPush(i);
|
||||
@@ -739,9 +743,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
sb.append(i).append(',');
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
sb.append(i).append('[');
|
||||
@@ -765,15 +769,12 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of property names for changed properties.
|
||||
*/
|
||||
public boolean[] getChanged() {
|
||||
return changedProps;
|
||||
}
|
||||
|
||||
public boolean[] getLoaded() {
|
||||
return loadedProps;
|
||||
boolean[] ret= new boolean[flags.length];
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -821,14 +822,14 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
|
||||
|
||||
if (loadedProps == null || loadedProps[loadProperty]) {
|
||||
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
|
||||
// race condition where multiple threads calling preGetter concurrently
|
||||
return;
|
||||
}
|
||||
|
||||
if (lazyLoadFailure) {
|
||||
// failed when batch lazy loaded by another bean in the batch
|
||||
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
|
||||
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
|
||||
}
|
||||
|
||||
if (lazyLoadProperty == -1) {
|
||||
@@ -887,7 +888,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Called when a BeanCollection is initialised automatically.
|
||||
*/
|
||||
public void initialisedMany(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
private void preGetterCallback(int propertyIndex) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
|
||||
* <p>
|
||||
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
|
||||
* modified since the query cache entry was cached. If any dependent tables have since been modified
|
||||
* the query cache entry is treated as invalid.
|
||||
* </p>
|
||||
*/
|
||||
public class QueryCacheEntry {
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* Create with dependent tables and timestamp.
|
||||
*
|
||||
* @param value The query result being cached
|
||||
* @param dependentTables The extra tables the query is dependent on (joins to)
|
||||
* @param timestamp The timestamp that the query uses to check for modifications
|
||||
*/
|
||||
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
|
||||
this.value = value;
|
||||
this.dependentTables = dependentTables;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual query result.
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tables the query result is dependent on.
|
||||
*/
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp used to check for modifications on the dependent tables.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
/**
|
||||
* Used to validate that a query cache entry is still valid based on dependent tables.
|
||||
*/
|
||||
public interface QueryCacheEntryValidate {
|
||||
|
||||
/**
|
||||
* Return true if the entry is still valid based on dependent tables.
|
||||
*/
|
||||
boolean isValid(QueryCacheEntry queryCacheEntry);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
/**
|
||||
* Configuration used to create ServerCache instances.
|
||||
*/
|
||||
public class ServerCacheConfig {
|
||||
|
||||
private final ServerCacheType type;
|
||||
private final String cacheKey;
|
||||
private final ServerCacheOptions cacheOptions;
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
private final QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
|
||||
public ServerCacheConfig(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
|
||||
this.type = type;
|
||||
this.cacheKey = cacheKey;
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.tenantProvider = tenantProvider;
|
||||
this.queryCacheEntryValidate = queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache type.
|
||||
*/
|
||||
public ServerCacheType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
public String getCacheKey() {
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tuning options.
|
||||
*/
|
||||
public ServerCacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current tenant provider.
|
||||
*/
|
||||
public CurrentTenantProvider getTenantProvider() {
|
||||
return tenantProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service that provides validation for query cache entries.
|
||||
*/
|
||||
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
|
||||
return queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the cache is a query cache.
|
||||
*/
|
||||
public boolean isQueryCache() {
|
||||
return type == ServerCacheType.QUERY;
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -1,7 +1,5 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
/**
|
||||
* Defines method for constructing caches for beans and queries.
|
||||
*/
|
||||
@@ -10,6 +8,15 @@ public interface ServerCacheFactory {
|
||||
/**
|
||||
* Create the cache for the given type with options.
|
||||
*/
|
||||
ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions);
|
||||
ServerCache createCache(ServerCacheConfig config);
|
||||
|
||||
/**
|
||||
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
|
||||
* <p>
|
||||
* This is used if a ServerCacheNotifyPlugin is not supplied.
|
||||
* </p>
|
||||
*
|
||||
* @param listener The listener that should be used to process the notification events.
|
||||
*/
|
||||
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Notification event that dependent tables have been modified.
|
||||
* <p>
|
||||
* This is sent to other interested servers (in the cluster).
|
||||
* </p>
|
||||
*/
|
||||
public class ServerCacheNotification {
|
||||
|
||||
private final long modifyTimestamp;
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
public ServerCacheNotification(long modifyTimestamp, Set<String> dependentTables) {
|
||||
this.modifyTimestamp = modifyTimestamp;
|
||||
this.dependentTables = dependentTables;
|
||||
}
|
||||
|
||||
public long getModifyTimestamp() {
|
||||
return modifyTimestamp;
|
||||
}
|
||||
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
/**
|
||||
* Interface for both listening to notification changes and sending them to other members of the cluster.
|
||||
*/
|
||||
public interface ServerCacheNotify {
|
||||
|
||||
/**
|
||||
* Notify other server cache members of the table modifications or process the notifications.
|
||||
*/
|
||||
void notify(ServerCacheNotification notification);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
|
||||
/**
|
||||
* Plugin that provides a ServerCacheNotify implementation.
|
||||
* <p>
|
||||
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
|
||||
* </p>
|
||||
*/
|
||||
public interface ServerCacheNotifyPlugin {
|
||||
|
||||
/**
|
||||
* Create a ServerCacheNotify implementation given the server configuration.
|
||||
*/
|
||||
ServerCacheNotify create(ServerConfig serverConfig);
|
||||
}
|
||||
@@ -123,7 +123,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
private void initClear() {
|
||||
synchronized (this) {
|
||||
if (map == null) {
|
||||
if (modifyListening) {
|
||||
if (!disableLazyLoad && modifyListening) {
|
||||
lazyLoadCollection(true);
|
||||
} else {
|
||||
map = new LinkedHashMap<>();
|
||||
@@ -135,7 +135,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
private void init() {
|
||||
synchronized (this) {
|
||||
if (map == null) {
|
||||
lazyLoadCollection(false);
|
||||
if (disableLazyLoad) {
|
||||
map = new LinkedHashMap<>();
|
||||
} else {
|
||||
lazyLoadCollection(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,14 +277,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyListening) {
|
||||
Object oldBean = map.put(key, value);
|
||||
E oldBean = map.put(key, value);
|
||||
if (value != oldBean) {
|
||||
// register the add of the new and the removal of the old
|
||||
modifyAddition(value);
|
||||
modifyRemoval(oldBean);
|
||||
}
|
||||
return oldBean;
|
||||
} else {
|
||||
return map.put(key, value);
|
||||
}
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,8 +301,9 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
modifyRemoval(oldBean);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
map.putAll(puts);
|
||||
}
|
||||
map.putAll(puts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -116,7 +116,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
private void initClear() {
|
||||
synchronized (this) {
|
||||
if (set == null) {
|
||||
if (modifyListening) {
|
||||
if (!disableLazyLoad && modifyListening) {
|
||||
lazyLoadCollection(true);
|
||||
} else {
|
||||
set = new LinkedHashSet<>();
|
||||
@@ -128,7 +128,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
private void init() {
|
||||
synchronized (this) {
|
||||
if (set == null) {
|
||||
lazyLoadCollection(true);
|
||||
if (disableLazyLoad) {
|
||||
set = new LinkedHashSet<>();
|
||||
} else {
|
||||
lazyLoadCollection(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +381,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
throw new IllegalStateException("This collection is in ReadOnly mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Throw when an processing an entity bean that is not bytecode enhanced.
|
||||
*
|
||||
* Refer: https://ebean.io/docs/trouble-shooting#not-enhanced
|
||||
*/
|
||||
public class BeanNotEnhancedException extends IllegalStateException {
|
||||
|
||||
private static final long serialVersionUID = 3008101919425876799L;
|
||||
|
||||
public BeanNotEnhancedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Throw when an processing thinks a bean is not registered.
|
||||
*
|
||||
* Refer: https://ebean.io/docs/trouble-shooting#not-registered
|
||||
*/
|
||||
public class BeanNotRegisteredException extends IllegalStateException {
|
||||
|
||||
private static final long serialVersionUID = -1411298126011136552L;
|
||||
|
||||
public BeanNotRegisteredException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -28,34 +28,6 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected Platform 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.
|
||||
*/
|
||||
@@ -75,13 +47,6 @@ public class DbMigrationConfig {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* For running migration the DB table that holds migration execution status.
|
||||
*/
|
||||
@@ -102,6 +67,16 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected Map<String, String> runPlaceholderMap;
|
||||
|
||||
/**
|
||||
* DB schema used for the migration (and testing).
|
||||
*/
|
||||
protected String dbSchema;
|
||||
|
||||
/**
|
||||
* Set to true if we consider this the 'default schema' (Postgres schema that matches DB username)
|
||||
*/
|
||||
protected boolean defaultDbSchema;
|
||||
|
||||
/**
|
||||
* DB user used to run the DB migration.
|
||||
*/
|
||||
@@ -218,52 +193,6 @@ public class DbMigrationConfig {
|
||||
this.applyPrefix = applyPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table name that holds the migration run details
|
||||
* (used by DB Migration runner only).
|
||||
@@ -373,6 +302,39 @@ public class DbMigrationConfig {
|
||||
this.dbPassword = dbPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB schema to use (for migration, testing etc).
|
||||
*/
|
||||
public String getDbSchema() {
|
||||
String schema = readEnvironment("ddl.migration.schema");
|
||||
if (schema != null) {
|
||||
return schema;
|
||||
}
|
||||
return dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db schema to use.
|
||||
*/
|
||||
public void setDbSchema(String dbSchema) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db schema if it hasn't already been defined.
|
||||
*/
|
||||
public void setDefaultDbSchema(String dbSchema) {
|
||||
this.defaultDbSchema = true;
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is considered the default DB schema (Postgres schema matching DB username).
|
||||
*/
|
||||
public boolean isDefaultDbSchema() {
|
||||
return defaultDbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return migration versions that should be added to history without running.
|
||||
*/
|
||||
@@ -402,9 +364,20 @@ public class DbMigrationConfig {
|
||||
* You can use placeholders like ${version} or ${timestamp} in properties file.
|
||||
*/
|
||||
public String getDdlHeader() {
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
return ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the header prepended to the DDL.
|
||||
*/
|
||||
public void setDdlHeader(String ddlHeader) {
|
||||
this.ddlHeader = ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should have their checksum reset and not run.
|
||||
* <p>
|
||||
@@ -450,20 +423,15 @@ public class DbMigrationConfig {
|
||||
applyPrefix = properties.get("migration.applyPrefix", applyPrefix);
|
||||
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(Platform.class, "migration.platform", platform);
|
||||
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
|
||||
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
|
||||
|
||||
runMigration = properties.getBoolean("migration.run", runMigration);
|
||||
metaTable = properties.get("migration.metaTable", metaTable);
|
||||
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
|
||||
dbSchema = properties.get("migration.dbSchema", dbSchema);
|
||||
|
||||
//Do not set user and pass from "datasource.db.username"
|
||||
//There is a null test in MigrationRunner::getConnection to handle this
|
||||
@@ -475,48 +443,6 @@ public class DbMigrationConfig {
|
||||
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
|
||||
dbPassword = properties.get("migration.dbpassword", adminPwd);
|
||||
ddlHeader = properties.get("ddl.header", ddlHeader);
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,6 +476,10 @@ public class DbMigrationConfig {
|
||||
runnerConfig.setRunPlaceholders(runPlaceholders);
|
||||
runnerConfig.setDbUsername(getDbUsername());
|
||||
runnerConfig.setDbPassword(getDbPassword());
|
||||
runnerConfig.setDbSchema(getDbSchema());
|
||||
if (defaultDbSchema) {
|
||||
runnerConfig.setSetCurrentSchema(false);
|
||||
}
|
||||
runnerConfig.setClassLoader(classLoader);
|
||||
if (patchInsertOn != null) {
|
||||
runnerConfig.setPatchInsertOn(patchInsertOn);
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.properties.PropertiesLoader;
|
||||
import io.ebean.datasource.DataSourceConfig;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
@@ -30,9 +31,9 @@ import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -57,22 +58,24 @@ import java.util.ServiceLoader;
|
||||
* <pre>{@code
|
||||
*
|
||||
* ServerConfig c = new ServerConfig();
|
||||
* c.setName("db");
|
||||
*
|
||||
* // read the ebean.properties and load
|
||||
* // those settings into this serverConfig object
|
||||
* c.loadFromProperties();
|
||||
*
|
||||
* // add any classes found in the app.data package
|
||||
* c.addPackage("com.myapp.domain");
|
||||
*
|
||||
* // register as the 'Default' server
|
||||
* c.setDefaultServer(true);
|
||||
* // explicitly register the entity beans to avoid classpath scanning
|
||||
* c.addClass(Customer.class);
|
||||
* c.addClass(User.class);
|
||||
*
|
||||
* EbeanServer server = EbeanServerFactory.create(c);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can
|
||||
* be used to apply configuration to the ServerConfig.
|
||||
* </p>
|
||||
*
|
||||
* @author emcgreal
|
||||
* @author rbygrave
|
||||
* @see EbeanServerFactory
|
||||
@@ -226,6 +229,8 @@ public class ServerConfig {
|
||||
|
||||
private int persistBatchSize = 20;
|
||||
|
||||
private boolean disableLazyLoading;
|
||||
|
||||
/**
|
||||
* The default batch size for lazy loading
|
||||
*/
|
||||
@@ -293,6 +298,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* Optional - the database schema that should be used to own the tables etc.
|
||||
*/
|
||||
private String dbSchema;
|
||||
|
||||
/**
|
||||
* The db migration config (migration resource path etc).
|
||||
*/
|
||||
@@ -360,6 +370,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private String uuidStateFile = "ebean-uuid.state";
|
||||
|
||||
/**
|
||||
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
private List<IdGenerator> idGenerators = new ArrayList<>();
|
||||
private List<BeanFindController> findControllers = new ArrayList<>();
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<>();
|
||||
@@ -493,6 +508,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private List<String> mappingLocations = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* When true we do not need explicit GeneratedValue mapping.
|
||||
*/
|
||||
private boolean idGeneratorAutomatic = true;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -500,6 +520,20 @@ public class ServerConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
public Clock getClock() {
|
||||
return clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
public void setClock(final Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slow query time in millis.
|
||||
*/
|
||||
@@ -895,6 +929,22 @@ public class ServerConfig {
|
||||
this.queryBatchSize = queryBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if lazy loading is disabled on queries by default.
|
||||
*/
|
||||
public boolean isDisableLazyLoading() {
|
||||
return disableLazyLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to disable lazy loading by default.
|
||||
* <p>
|
||||
* It can be turned on per query via {@link Query#setDisableLazyLoading(boolean)}.
|
||||
*/
|
||||
public void setDisableLazyLoading(boolean disableLazyLoading) {
|
||||
this.disableLazyLoading = disableLazyLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default batch size for lazy loading of beans and collections.
|
||||
*/
|
||||
@@ -1093,6 +1143,25 @@ public class ServerConfig {
|
||||
this.profilingConfig = profilingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB schema to use.
|
||||
*/
|
||||
public String getDbSchema() {
|
||||
return dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB schema to use. This specifies to use this schema for:
|
||||
* <ul>
|
||||
* <li>Running Database migrations - Create and use the DB schema</li>
|
||||
* <li>Testing DDL - Create-all.sql DDL execution creates and uses schema</li>
|
||||
* <li>Testing Docker - Set default schema on connection URL</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void setDbSchema(String dbSchema) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB migration configuration.
|
||||
*/
|
||||
@@ -1914,6 +1983,15 @@ public class ServerConfig {
|
||||
* Return the UUID state file.
|
||||
*/
|
||||
public String getUuidStateFile() {
|
||||
if (uuidStateFile == null || uuidStateFile.isEmpty()) {
|
||||
// by default, add servername...
|
||||
uuidStateFile = name + "-uuid.state";
|
||||
// and store it in the user's home directory
|
||||
String homeDir = System.getProperty("user.home");
|
||||
if (homeDir != null && homeDir.isEmpty()) {
|
||||
uuidStateFile = homeDir + "/.ebean/" + uuidStateFile;
|
||||
}
|
||||
}
|
||||
return uuidStateFile;
|
||||
}
|
||||
|
||||
@@ -1967,7 +2045,7 @@ public class ServerConfig {
|
||||
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
|
||||
* as it is often the only thing we need to configure for migrations.
|
||||
*/
|
||||
public void setRunMigration(boolean runMigration){
|
||||
public void setRunMigration(boolean runMigration) {
|
||||
migrationConfig.setRunMigration(runMigration);
|
||||
}
|
||||
|
||||
@@ -2685,20 +2763,6 @@ public class ServerConfig {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated - this does nothing now, we always try to read test configuration.
|
||||
* <p>
|
||||
* Load settings from test-ebean.properties and do nothing if the properties is not found.
|
||||
* <p>
|
||||
* This is typically used when test-ebean.properties is put into the test class path and used
|
||||
* to configure Ebean for running tests.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
public void loadTestProperties() {
|
||||
// do nothing now ... as we always try to read test configuration and that should only
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
|
||||
*/
|
||||
@@ -2737,6 +2801,10 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
dbSchema = p.get("dbSchema", dbSchema);
|
||||
if (dbSchema != null) {
|
||||
migrationConfig.setDefaultDbSchema(dbSchema);
|
||||
}
|
||||
profilingConfig.loadSettings(p, name);
|
||||
migrationConfig.loadSettings(p, name);
|
||||
platformConfig.loadSettings(p);
|
||||
@@ -2772,6 +2840,7 @@ public class ServerConfig {
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
|
||||
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
|
||||
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
|
||||
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
|
||||
@@ -2831,6 +2900,7 @@ public class ServerConfig {
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
|
||||
|
||||
disableLazyLoading = p.getBoolean("disableLazyLoading", disableLazyLoading);
|
||||
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
|
||||
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
|
||||
|
||||
@@ -3074,9 +3144,10 @@ public class ServerConfig {
|
||||
* @return A copy of the PlatformConfig with overridden properties
|
||||
*/
|
||||
public PlatformConfig newPlatformConfig(String propertiesPath, String platformPrefix) {
|
||||
|
||||
if (properties == null) {
|
||||
properties = new Properties();
|
||||
}
|
||||
PropertiesWrapper p = new PropertiesWrapper(propertiesPath, platformPrefix, properties, classLoadConfig);
|
||||
|
||||
PlatformConfig config = new PlatformConfig(platformConfig);
|
||||
config.loadSettings(p);
|
||||
return config;
|
||||
@@ -3109,6 +3180,23 @@ public class ServerConfig {
|
||||
this.mappingLocations = mappingLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
|
||||
* Identity or Sequence generated values. When true Id properties are automatically
|
||||
* assigned Identity or Sequence without the GeneratedValue mapping.
|
||||
*/
|
||||
public boolean isIdGeneratorAutomatic() {
|
||||
return idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
|
||||
* mapping before they are assigned Identity or Sequence generation based on platform.
|
||||
*/
|
||||
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
|
||||
this.idGeneratorAutomatic = idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
|
||||
* <p>
|
||||
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
|
||||
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
|
||||
* spring specific configuration. When we are not using a DI container we may use this mechanism to
|
||||
* explicitly register the entity beans and avoid classpath scanning.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class EbeanConfigProvider implements ServerConfigProvider {
|
||||
*
|
||||
* @Override
|
||||
* public void apply(ServerConfig config) {
|
||||
*
|
||||
* // register the entity bean classes explicitly
|
||||
* config.addClass(Customer.class);
|
||||
* config.addClass(User.class);
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ServerConfigProvider {
|
||||
|
||||
/**
|
||||
* Apply the configuration to the ServerConfig.
|
||||
* <p>
|
||||
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
|
||||
* </p>
|
||||
*/
|
||||
void apply(ServerConfig config);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.PartitionMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.CustomDbTypeMapping;
|
||||
@@ -16,6 +17,7 @@ import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -50,7 +52,7 @@ public class DatabasePlatform {
|
||||
/**
|
||||
* The behaviour used when ending a read only transaction at read committed isolation level.
|
||||
*/
|
||||
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
|
||||
protected OnQueryOnly onQueryOnly = OnQueryOnly.COMMIT;
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
@@ -62,8 +64,6 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
protected String concatOperator = "||";
|
||||
|
||||
/**
|
||||
* When set to true all db column names and table names use quoted identifiers.
|
||||
*/
|
||||
@@ -176,9 +176,17 @@ public class DatabasePlatform {
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
|
||||
/**
|
||||
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
|
||||
*
|
||||
* This is {@code false} for HANA
|
||||
*/
|
||||
protected boolean supportsResultSetConcurrencyModeUpdatable = true;
|
||||
|
||||
|
||||
/**
|
||||
* By default we use JDBC batch when cascading (except for SQL Server).
|
||||
* By default we use JDBC batch when cascading (except for SQL Server and HANA).
|
||||
*/
|
||||
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
|
||||
|
||||
@@ -453,13 +461,6 @@ public class DatabasePlatform {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB concat operator.
|
||||
*/
|
||||
public String getConcatOperator() {
|
||||
return concatOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*/
|
||||
@@ -523,6 +524,24 @@ public class DatabasePlatform {
|
||||
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet CONCUR_UPDATABLE Hint should be used on
|
||||
* createNativeSqlTree() PreparedStatements.
|
||||
* <p>
|
||||
* This specifically is required for Hana which doesn't support CONCUR_UPDATABLE
|
||||
* </p>
|
||||
*/
|
||||
public boolean isSupportsResultSetConcurrencyModeUpdatable() {
|
||||
return supportsResultSetConcurrencyModeUpdatable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the ResultSet CONCUR_UPDATABLE Hint should be used by default on createNativeSqlTree() PreparedStatements.
|
||||
*/
|
||||
public void setSupportsResultSetConcurrencyModeUpdatable(boolean supportsResultSetConcurrencyModeUpdatable) {
|
||||
this.supportsResultSetConcurrencyModeUpdatable = supportsResultSetConcurrencyModeUpdatable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally not needed - overridden in CockroachPlatform.
|
||||
@@ -658,6 +677,39 @@ public class DatabasePlatform {
|
||||
return persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DB schema if it does not exist.
|
||||
*/
|
||||
public void createSchemaIfNotExists(String dbSchema, Connection connection) throws SQLException {
|
||||
if (!schemaExists(dbSchema, connection)) {
|
||||
Statement query = connection.createStatement();
|
||||
try {
|
||||
logger.info("create schema:{}", dbSchema);
|
||||
query.executeUpdate("create schema " + dbSchema);
|
||||
} finally {
|
||||
JdbcClose.close(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the schema exists.
|
||||
*/
|
||||
public boolean schemaExists(String dbSchema, Connection connection) throws SQLException {
|
||||
ResultSet schemas = connection.getMetaData().getSchemas();
|
||||
try {
|
||||
while (schemas.next()) {
|
||||
String schema = schemas.getString(1);
|
||||
if (schema.equalsIgnoreCase(dbSchema)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
JdbcClose.close(schemas);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table exists.
|
||||
*/
|
||||
@@ -672,6 +724,20 @@ public class DatabasePlatform {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if partitions exist for the given table.
|
||||
*/
|
||||
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL to create an initial partition for the given table.
|
||||
*/
|
||||
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String singlePrimaryKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the like string for this DB-Platform
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,9 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class DbIdentity {
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
|
||||
private static final String TABLE_PLACEHOLDER = "{table}";
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile(TABLE_PLACEHOLDER, Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* Set if this DB supports sequences. Note some DB's support both Sequences
|
||||
@@ -53,7 +55,9 @@ public class DbIdentity {
|
||||
if (selectLastInsertedIdTemplate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectLastInsertedIdTemplate.contains(TABLE_PLACEHOLDER)) {
|
||||
return selectLastInsertedIdTemplate;
|
||||
}
|
||||
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.config.dbplatform.BasicSqlLimiter;
|
||||
|
||||
public class HanaBasicSqlLimiter implements BasicSqlLimiter {
|
||||
@Override
|
||||
public String limit(String dbSql, int firstRow, int maxRows) {
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
|
||||
sb.append(dbSql);
|
||||
|
||||
if (maxRows > 0) {
|
||||
sb.append(" ").append("limit");
|
||||
sb.append(" ").append(maxRows);
|
||||
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append("offset").append(" ");
|
||||
sb.append(firstRow);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.config.dbplatform.DbStandardHistorySupport;
|
||||
|
||||
public class HanaHistorySupport extends DbStandardHistorySupport {
|
||||
|
||||
@Override
|
||||
public String getAsOfViewSuffix(String asOfViewSuffix) {
|
||||
return " for system_time as of ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
|
||||
return " for system_time between ? and ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_start";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_end";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.Query.ForUpdate;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
public class HanaPlatform extends DatabasePlatform {
|
||||
|
||||
public HanaPlatform() {
|
||||
this.platform = Platform.HANA;
|
||||
this.sqlLimiter = new HanaSqlLimiter();
|
||||
this.persistBatchOnCascade = PersistBatch.NONE;
|
||||
this.supportsResultSetConcurrencyModeUpdatable = false;
|
||||
this.columnAliasPrefix = null;
|
||||
|
||||
this.historySupport = new HanaHistorySupport();
|
||||
this.basicSqlLimiter = new HanaBasicSqlLimiter();
|
||||
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.maxConstraintNameLength = 127;
|
||||
this.maxTableNameLength = 127;
|
||||
|
||||
this.dbDefaultValue.setNow("current_timestamp");
|
||||
|
||||
this.exceptionTranslator = new SqlErrorCodes().addAcquireLock("131", "133", "146")
|
||||
.addDataIntegrity("130", "429", "461", "462").addDuplicateKey("144", "301", "349").build();
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSelectLastInsertedIdTemplate("select current_identity_value() from sys.dummy");
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
this.dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
|
||||
this.dbTypeMap.put(DbType.BINARY, new DbPlatformType("varbinary", 255));
|
||||
this.dbTypeMap.put(DbType.BIT, new DbPlatformType("smallint", false));
|
||||
this.dbTypeMap.put(DbType.BLOB, new DbPlatformType("blob", false));
|
||||
this.dbTypeMap.put(DbType.CHAR, new DbPlatformType("nvarchar", 255));
|
||||
this.dbTypeMap.put(DbType.CLOB, new DbPlatformType("nclob", false));
|
||||
this.dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
|
||||
this.dbTypeMap.put(DbType.JSONVARCHAR, new DbPlatformType("nvarchar", 255));
|
||||
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("blob", false));
|
||||
this.dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nclob", false));
|
||||
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point"));
|
||||
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("smallint", false));
|
||||
this.dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint", false));
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
|
||||
this.dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
|
||||
this.dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addGeoTypes(int srid) {
|
||||
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql, ForUpdate forUpdateMode) {
|
||||
switch (forUpdateMode) {
|
||||
case BASE:
|
||||
return sql + " for update";
|
||||
case NOWAIT:
|
||||
return sql + " for update nowait";
|
||||
case SKIPLOCKED:
|
||||
return sql + " for update ignore locked";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown update mode: " + forUpdateMode.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
|
||||
super.configure(config, allQuotedIdentifiers);
|
||||
if (config.getDbUuid().useBinary()) {
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varbinary", 16));
|
||||
} else {
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.config.dbplatform.SqlLimitRequest;
|
||||
import io.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import io.ebean.config.dbplatform.SqlLimiter;
|
||||
|
||||
public class HanaSqlLimiter implements SqlLimiter {
|
||||
@Override
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
sb.append("select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
|
||||
sb.append(dbSql);
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
int maxRows = request.getMaxRows();
|
||||
|
||||
if (maxRows > 0) {
|
||||
sb.append(" ").append("limit ").append(maxRows);
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append("offset ");
|
||||
sb.append(firstRow);
|
||||
}
|
||||
}
|
||||
|
||||
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
|
||||
|
||||
return new SqlLimitResponse(sql, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* SAP HANA specific support.
|
||||
*/
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform.postgres;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.PartitionMode;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
@@ -11,6 +12,10 @@ import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -116,4 +121,32 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement("select count(*) from pg_inherits i WHERE i.inhparent = ?::regclass")) {
|
||||
statement.setString(1, table);
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
return resultSet.next() && resultSet.getInt(1) > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SQL using built in partition helper functions to create some initial partitions.
|
||||
*
|
||||
* Only use this if extra-ddl doesn't have some initial partitions defined (which it should).
|
||||
*/
|
||||
@Override
|
||||
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String pkey) {
|
||||
if (property == null) {
|
||||
property = "";
|
||||
}
|
||||
if (pkey == null) {
|
||||
pkey = "";
|
||||
}
|
||||
return "select partition('" + mode.name().toLowerCase() + "','" + tableName + "','" + pkey + "','" + property + "',1);";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,26 +18,26 @@ import java.util.Set;
|
||||
*/
|
||||
class LoadContext {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
|
||||
/**
|
||||
* Map we are loading the properties into.
|
||||
*/
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Names of resources/files that were loaded.
|
||||
*/
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Return the input stream (maybe null) for the given source.
|
||||
*/
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
|
||||
InputStream is = null;
|
||||
if (source == Loader.Source.RESOURCE) {
|
||||
is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
is = resourceStream(resourcePath);
|
||||
if (is != null) {
|
||||
loadedResources.add(resourcePath);
|
||||
}
|
||||
@@ -46,43 +46,52 @@ class LoadContext {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
is = new FileInputStream(file);
|
||||
loadedResources.add("file:"+resourcePath);
|
||||
loadedResources.add("file:" + resourcePath);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is;
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
private InputStream resourceStream(String resourcePath) {
|
||||
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
if (is == null) {
|
||||
// search the module path for top level resource
|
||||
is = ClassLoader.getSystemResourceAsStream(resourcePath);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property entry.
|
||||
*/
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate all the expressions and return as a Properties object.
|
||||
*/
|
||||
Properties eval() {
|
||||
Properties eval() {
|
||||
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
|
||||
Properties properties = new Properties();
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the special properties that can point to an external properties source.
|
||||
|
||||
@@ -16,10 +16,10 @@ import java.util.Properties;
|
||||
*/
|
||||
class Loader {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
FILE
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class Loader {
|
||||
|
||||
private YamlLoader yamlLoader;
|
||||
|
||||
Loader() {
|
||||
Loader() {
|
||||
String skipYaml = System.getProperty("ebeanSkipYaml");
|
||||
if (!"true".equals(skipYaml)) {
|
||||
try {
|
||||
@@ -44,25 +44,26 @@ class Loader {
|
||||
/**
|
||||
* Load the configuration with the expected ordering.
|
||||
*/
|
||||
void load() {
|
||||
void load() {
|
||||
|
||||
loadMain(Source.RESOURCE);
|
||||
// external file configuration overrides the resources configuration
|
||||
loadMain(Source.FILE);
|
||||
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
|
||||
// test configuration (if found) overrides main configuration
|
||||
// test configuration (if found) overrides main configuration
|
||||
// we should only find these resources when running tests
|
||||
loadTest();
|
||||
}
|
||||
loadTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load test configuration.
|
||||
*/
|
||||
private void loadTest() {
|
||||
loadProperties("application-test.properties", Source.RESOURCE);
|
||||
loadYaml("application-test.yaml", Source.RESOURCE);
|
||||
loadYaml("application-test.yml", Source.RESOURCE);
|
||||
loadProperties("test-ebean.properties", Source.RESOURCE);
|
||||
}
|
||||
@@ -83,13 +84,14 @@ class Loader {
|
||||
* Load the main configuration for the given source.
|
||||
*/
|
||||
private void loadMain(Source source) {
|
||||
loadYaml("application.yaml", source);
|
||||
loadYaml("application.yml", source);
|
||||
loadProperties("application.properties", source);
|
||||
loadProperties("ebean.properties", source);
|
||||
}
|
||||
|
||||
private void loadViaSystemProperty() {
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName != null) {
|
||||
@@ -99,7 +101,7 @@ class Loader {
|
||||
}
|
||||
|
||||
void loadWithExtensionCheck(String fileName) {
|
||||
if (fileName.endsWith("yml")) {
|
||||
if (fileName.endsWith("yaml") || fileName.endsWith("yml")) {
|
||||
loadYaml(fileName, Source.FILE);
|
||||
} else if (fileName.endsWith("properties")) {
|
||||
loadProperties(fileName, Source.FILE);
|
||||
@@ -112,59 +114,66 @@ class Loader {
|
||||
* Evaluate all the configuration entries and return as properties.
|
||||
*/
|
||||
Properties eval() {
|
||||
return loadContext.eval();
|
||||
}
|
||||
return loadContext.eval();
|
||||
}
|
||||
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
try {
|
||||
yamlLoader.load(resource(resourcePath, source));
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
try {
|
||||
try (InputStream is = resource(resourcePath, source)) {
|
||||
yamlLoader.load(is);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read yml from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
InputStream is = resource(resourcePath, source);
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
try {
|
||||
try (InputStream is = resource(resourcePath, source)) {
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read properties from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
|
||||
private void loadProperties(InputStream is) {
|
||||
private void loadProperties(InputStream is) {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@ public class PropertiesLoader {
|
||||
* <p>Firstly loads from main resources</p>
|
||||
* <pre>
|
||||
* - application.properties
|
||||
* - application.yml
|
||||
* - application.yaml
|
||||
* - ebean.properties
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>Then loads from local files</p>
|
||||
* <pre>
|
||||
* - application.properties
|
||||
* - application.yml
|
||||
* - application.yaml
|
||||
* - ebean.properties
|
||||
* </pre>
|
||||
* <p>
|
||||
@@ -41,7 +41,7 @@ public class PropertiesLoader {
|
||||
* <p>Loads from test resources</p>
|
||||
* <pre>
|
||||
* - application-test.properties
|
||||
* - application-test.yml
|
||||
* - application-test.yaml
|
||||
* - test-ebean.properties
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
@@ -93,6 +94,43 @@ public interface DbMigration {
|
||||
*/
|
||||
void setStrictMode(boolean strictMode);
|
||||
|
||||
/**
|
||||
* Set to true to include a generated header comment in the DDL script.
|
||||
*/
|
||||
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
|
||||
|
||||
/**
|
||||
* Set this to false to exclude the builtin support for table partitioning (with @DbPartition).
|
||||
*/
|
||||
void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning);
|
||||
|
||||
/**
|
||||
* Set the header that is included in the generated DDL script.
|
||||
*/
|
||||
void setHeader(String header);
|
||||
|
||||
/**
|
||||
* Set the prefix for the version. Set this to "V" for use with Flyway.
|
||||
*/
|
||||
void setApplyPrefix(String applyPrefix);
|
||||
|
||||
/**
|
||||
* Set the version of the migration to be generated.
|
||||
*/
|
||||
void setVersion(String version);
|
||||
|
||||
/**
|
||||
* Set the name of the migration to be generated.
|
||||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Generate a migration for the version specified that contains pending drops.
|
||||
*
|
||||
* @param generatePendingDrop The version of a prior migration that holds pending drops.
|
||||
*/
|
||||
void setGeneratePendingDrop(String generatePendingDrop);
|
||||
|
||||
/**
|
||||
* Add an additional platform to write the migration DDL.
|
||||
* <p>
|
||||
@@ -110,6 +148,11 @@ public interface DbMigration {
|
||||
*/
|
||||
void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix);
|
||||
|
||||
/**
|
||||
* Return the list of versions that contain pending drops.
|
||||
*/
|
||||
List<String> getPendingDrops();
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
* <p>
|
||||
|
||||
+9
-3
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
package io.ebean.event;
|
||||
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
|
||||
@@ -7,8 +7,14 @@ import javax.servlet.ServletContextEvent;
|
||||
/**
|
||||
* Listens for webserver server starting and stopping events.
|
||||
* <p>
|
||||
* Register this listener in the web.xml configuration file. This will listen
|
||||
* for startup and shutdown events.
|
||||
* This should be used when the deployment is into a servlet container where the webapp
|
||||
* can be shutdown or redeployed without the JVM stopping.
|
||||
* </p>
|
||||
* <p>
|
||||
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
|
||||
* runnable war or when using a servlet container that only contains the single webapp and
|
||||
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
|
||||
* hook that Ebean registers.
|
||||
* </p>
|
||||
*/
|
||||
public class ServletContextListener implements javax.servlet.ServletContextListener {
|
||||
@@ -9,6 +9,10 @@ import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Information and methods on BeanDescriptors made available to plugins.
|
||||
@@ -18,6 +22,7 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Return the short name of the bean type.
|
||||
*/
|
||||
@Nonnull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
@@ -28,11 +33,13 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Return the full name of the bean type.
|
||||
*/
|
||||
@Nonnull
|
||||
String getFullName();
|
||||
|
||||
/**
|
||||
* Return the class type this BeanDescriptor describes.
|
||||
*/
|
||||
@Nonnull
|
||||
Class<T> getBeanType();
|
||||
|
||||
/**
|
||||
@@ -43,6 +50,7 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Return all the properties for this bean type.
|
||||
*/
|
||||
@Nonnull
|
||||
Collection<? extends Property> allProperties();
|
||||
|
||||
/**
|
||||
@@ -197,6 +205,28 @@ public interface BeanType<T> {
|
||||
*/
|
||||
boolean hasInheritance();
|
||||
|
||||
/**
|
||||
* Return true if this object is the root level object in its entity
|
||||
* inheritance.
|
||||
*/
|
||||
boolean isInheritanceRoot();
|
||||
|
||||
/**
|
||||
* Returns all direct children of this beantype
|
||||
*/
|
||||
List<BeanType<?>> getInheritanceChildren();
|
||||
|
||||
/**
|
||||
* Returns the parent in inheritance hiearchy
|
||||
*/
|
||||
BeanType<?> getInheritanceParent();
|
||||
|
||||
/**
|
||||
* Visit all children recursively
|
||||
* @param visitor
|
||||
*/
|
||||
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
|
||||
|
||||
/**
|
||||
* Return the discriminator column.
|
||||
*/
|
||||
|
||||
@@ -73,4 +73,14 @@ public interface ExpressionPath {
|
||||
* Return the Id values for the given bean value.
|
||||
*/
|
||||
Object[] getAssocIdValues(EntityBean bean);
|
||||
|
||||
/**
|
||||
* Return the underlying bean property.
|
||||
*/
|
||||
public Property getProperty();
|
||||
|
||||
/**
|
||||
* The ElPrefix plus name.
|
||||
*/
|
||||
String getElName();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebean.plugin;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Property of a entity bean that can be read.
|
||||
*/
|
||||
@@ -8,8 +10,15 @@ public interface Property {
|
||||
/**
|
||||
* Return the name of the property.
|
||||
*/
|
||||
@Nonnull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the type of the property.
|
||||
*/
|
||||
@Nonnull
|
||||
Class<?> getPropertyType();
|
||||
|
||||
/**
|
||||
* Return the value of the property on the given bean.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchGroupBuilder;
|
||||
|
||||
/**
|
||||
* Service that parses FetchGroup expressions.
|
||||
*/
|
||||
public interface SpiFetchGroupService {
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
*
|
||||
* @param beanType The type of entity bean the fetch group is for
|
||||
* @param select The properties to select (top level properties)
|
||||
*/
|
||||
<T> FetchGroup<T> of(Class<T> beanType, String select);
|
||||
|
||||
/**
|
||||
* Create and return a FetchGroupBuilder starting with a select() clause.
|
||||
*
|
||||
* @param beanType The type of entity bean the fetch group is for
|
||||
* @return The FetchGroupBuilder to add additional select and fetch clauses
|
||||
*/
|
||||
<T> FetchGroupBuilder<T> of(Class<T> beanType);
|
||||
}
|
||||
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
|
||||
}
|
||||
|
||||
Props getProps(String path) {
|
||||
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
|
||||
return props;
|
||||
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
|
||||
}
|
||||
|
||||
public Collection<Props> getPathProps() {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Context used to read binary format messages.
|
||||
*/
|
||||
public class BinaryReadContext {
|
||||
|
||||
private final DataInputStream in;
|
||||
|
||||
/**
|
||||
* Create with protocol 0 and byte data.
|
||||
*/
|
||||
public BinaryReadContext(byte[] byteData) {
|
||||
this(new DataInputStream(new ByteArrayInputStream(byteData)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with protocol version and DataInputStream data.
|
||||
*/
|
||||
public BinaryReadContext(DataInputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public DataInputStream in() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public boolean readBoolean() throws IOException {
|
||||
return in.readBoolean();
|
||||
}
|
||||
|
||||
public int readInt() throws IOException {
|
||||
return in.readInt();
|
||||
}
|
||||
|
||||
public String readUTF() throws IOException {
|
||||
return in.readUTF();
|
||||
}
|
||||
|
||||
public long readLong() throws IOException {
|
||||
return in.readLong();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Messages that can be sent in binary form.
|
||||
* <p>
|
||||
* Mainly RemoteTransactionEvent which is sent to cluster members.
|
||||
* </p>
|
||||
*/
|
||||
public interface BinaryWritable {
|
||||
|
||||
int TYPE_BEANIUD = 1;
|
||||
int TYPE_TABLEIUD = 2;
|
||||
int TYPE_CACHE = 3;
|
||||
int TYPE_TABLEMOD = 4;
|
||||
|
||||
/**
|
||||
* Write message in binary format.
|
||||
*/
|
||||
void writeBinary(BinaryWriteContext out) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Context used to write binary message (like RemoteTransactionEvent).
|
||||
*/
|
||||
public class BinaryWriteContext {
|
||||
|
||||
private final DataOutputStream out;
|
||||
|
||||
private long counter;
|
||||
|
||||
public BinaryWriteContext(DataOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of message parts that have been written.
|
||||
*/
|
||||
public long counter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the output stream to write to.
|
||||
*/
|
||||
public DataOutputStream os() {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a message part with a given type code.
|
||||
*/
|
||||
public DataOutputStream start(int type) throws IOException {
|
||||
counter++;
|
||||
out.writeBoolean(true);
|
||||
out.writeInt(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of message parts.
|
||||
*/
|
||||
public void end() throws IOException {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.persist.MultiValueWrapper;
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -154,6 +155,10 @@ public class BindParams implements Serializable {
|
||||
public void setParameter(int position, Object value) {
|
||||
|
||||
Param p = getParam(position);
|
||||
if (value instanceof Collection) {
|
||||
// use of postgres ANY with positioned parameter
|
||||
value = new MultiValueWrapper((Collection)value);
|
||||
}
|
||||
p.setInValue(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -168,13 +168,8 @@ public class LoadBeanRequest extends LoadRequest {
|
||||
// necessary but allow processing to continue until it is accessed by client code
|
||||
Object id = desc.getId(ebi.getOwner());
|
||||
if (!loadedIds.contains(id)) {
|
||||
if (desc.isSoftDelete()) {
|
||||
// assume this is logically deleted (hence not found)
|
||||
desc.setSoftDeleteValue(ebi.getOwner());
|
||||
} else {
|
||||
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
|
||||
ebi.setLazyLoadFailure(id);
|
||||
}
|
||||
// assume this is logically deleted (hence not found)
|
||||
desc.markAsDeleted(ebi.getOwner());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class NaturalKeyQueryData<T> {
|
||||
private List<Pairs.Entry> inPairs;
|
||||
|
||||
// IN clause - only one allowed
|
||||
private Collection<?> inValues;
|
||||
private List<Object> inValues;
|
||||
private String inProperty;
|
||||
|
||||
// normal EQ expressions
|
||||
@@ -52,36 +52,36 @@ public class NaturalKeyQueryData<T> {
|
||||
/**
|
||||
* Match for In Pairs expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchInPairs(Pairs pairs) {
|
||||
public List<Pairs.Entry> matchInPairs(String property0, String property1, List<Pairs.Entry> inPairs) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
if (matchProperty(pairs.getProperty0()) && matchProperty(pairs.getProperty1())) {
|
||||
if (matchProperty(property0) && matchProperty(property1)) {
|
||||
this.hasIn = true;
|
||||
this.inProperty0 = pairs.getProperty0();
|
||||
this.inProperty1 = pairs.getProperty1();
|
||||
this.inPairs = pairs.getEntries();
|
||||
return true;
|
||||
this.inProperty0 = property0;
|
||||
this.inProperty1 = property1;
|
||||
this.inPairs = new ArrayList<>(inPairs); // will be modified
|
||||
return this.inPairs;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for IN expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchIn(String propName, Collection<?> sourceValues) {
|
||||
public List<Object> matchIn(String propName, List<Object> sourceValues) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
if (matchProperty(propName)) {
|
||||
this.hasIn = true;
|
||||
this.inProperty = propName;
|
||||
this.inValues = sourceValues;
|
||||
return true;
|
||||
this.inValues = new ArrayList<>(sourceValues);
|
||||
return this.inValues;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -38,9 +37,9 @@ public class ScopeTrans {
|
||||
*/
|
||||
private final ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
|
||||
private PersistBatch restoreBatch;
|
||||
private Boolean restoreBatch;
|
||||
|
||||
private PersistBatch restoreBatchOnCascade;
|
||||
private Boolean restoreBatchOnCascade;
|
||||
|
||||
private int restoreBatchSize;
|
||||
|
||||
@@ -53,6 +52,10 @@ public class ScopeTrans {
|
||||
*/
|
||||
private boolean rolledBack;
|
||||
|
||||
/**
|
||||
* Flag set when nested commit has occurred.
|
||||
*/
|
||||
private boolean nestedCommit;
|
||||
|
||||
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
|
||||
|
||||
@@ -64,20 +67,20 @@ public class ScopeTrans {
|
||||
|
||||
if (transaction != null) {
|
||||
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
|
||||
restoreBatch = transaction.getBatch();
|
||||
restoreBatchOnCascade = transaction.getBatchOnCascade();
|
||||
restoreBatch = transaction.isBatchMode();
|
||||
restoreBatchOnCascade = transaction.isBatchOnCascade();
|
||||
restoreBatchSize = transaction.getBatchSize();
|
||||
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
|
||||
restoreBatchFlushOnQuery = transaction.isBatchFlushOnQuery();
|
||||
}
|
||||
if (txScope.isBatchSet()) {
|
||||
transaction.setBatch(txScope.getBatch());
|
||||
transaction.setBatchMode(txScope.isBatchMode());
|
||||
}
|
||||
if (!txScope.isFlushOnQuery()) {
|
||||
transaction.setBatchFlushOnQuery(false);
|
||||
}
|
||||
if (txScope.isBatchOnCascadeSet()) {
|
||||
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
|
||||
transaction.setBatchOnCascade(txScope.isBatchOnCascade());
|
||||
}
|
||||
if (txScope.isBatchSizeSet()) {
|
||||
transaction.setBatchSize(txScope.getBatchSize());
|
||||
@@ -113,7 +116,6 @@ public class ScopeTrans {
|
||||
complete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete the transaction programmatically. Try to commit.
|
||||
*/
|
||||
@@ -124,7 +126,7 @@ public class ScopeTrans {
|
||||
}
|
||||
|
||||
public void end() {
|
||||
if (created) {
|
||||
if (created || !nestedCommit) {
|
||||
transaction.end();
|
||||
}
|
||||
}
|
||||
@@ -133,9 +135,10 @@ public class ScopeTrans {
|
||||
if (created) {
|
||||
transaction.commit();
|
||||
} else {
|
||||
nestedCommit = true;
|
||||
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
|
||||
if (restoreBatch != null) {
|
||||
transaction.setBatch(restoreBatch);
|
||||
transaction.setBatchMode(restoreBatch);
|
||||
}
|
||||
if (restoreBatchOnCascade != null) {
|
||||
transaction.setBatchOnCascade(restoreBatchOnCascade);
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Plugin API available to invoke something prior to container bootup.
|
||||
* <p>
|
||||
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
|
||||
* we have a test DB docker container running and setup ready to go.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiContainerBootup {
|
||||
|
||||
/**
|
||||
* Run something at bootup prior to the container starting.
|
||||
*
|
||||
* For example, start DB docker container(s).
|
||||
*/
|
||||
void bootup();
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
/**
|
||||
* Return the label for the query.
|
||||
*/
|
||||
@Override
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,11 @@ package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.ExtendedServer;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
@@ -29,7 +32,12 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Service Provider extension to EbeanServer.
|
||||
*/
|
||||
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
|
||||
/**
|
||||
* Return the log manager.
|
||||
*/
|
||||
SpiLogManager log();
|
||||
|
||||
/**
|
||||
* Return the server extended Json context.
|
||||
@@ -229,6 +237,31 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
void scopedTransactionExit(Object returnOrThrowable, int opCode);
|
||||
|
||||
/**
|
||||
* SqlQuery find single attribute.
|
||||
*/
|
||||
<T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls);
|
||||
|
||||
/**
|
||||
* SqlQuery find single attribute list.
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
|
||||
|
||||
/**
|
||||
* SqlQuery find one with mapper.
|
||||
*/
|
||||
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* SqlQuery find list with mapper.
|
||||
*/
|
||||
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* SqlQuery find each with consumer.
|
||||
*/
|
||||
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* DTO findList query.
|
||||
*/
|
||||
@@ -268,4 +301,19 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
* Return true if a row for the bean type and id exists.
|
||||
*/
|
||||
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Add to JDBC batch for later execution.
|
||||
*/
|
||||
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the batched statement.
|
||||
*/
|
||||
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql update regardless of transaction batch mode.
|
||||
*/
|
||||
int executeNow(SpiSqlUpdate sqlUpdate);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Log manager for SQL, TXN and Summary logging.
|
||||
* <p>
|
||||
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
|
||||
* the logged SQL during testing such that we can assert against the executed sql if desired.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiLogManager {
|
||||
|
||||
/**
|
||||
* Return the SQL logger.
|
||||
*/
|
||||
SpiLogger sql();
|
||||
|
||||
/**
|
||||
* Return the TXN logger.
|
||||
*/
|
||||
SpiLogger txn();
|
||||
|
||||
/**
|
||||
* Return the Summary logger.
|
||||
*/
|
||||
SpiLogger sum();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Logger for SQL, TXN and Summary logging.
|
||||
* <p>
|
||||
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
|
||||
* the logged SQL during testing such that we can assert against the executed sql if desired.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiLogger {
|
||||
|
||||
/**
|
||||
* Is debug logging enabled.
|
||||
*/
|
||||
boolean isDebug();
|
||||
|
||||
/**
|
||||
* Is trace logging enabled.
|
||||
*/
|
||||
boolean isTrace();
|
||||
|
||||
/**
|
||||
* Log a debug level message.
|
||||
*/
|
||||
void debug(String msg);
|
||||
|
||||
/**
|
||||
* Log a trace level message.
|
||||
*/
|
||||
void trace(String msg);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
|
||||
*/
|
||||
public interface SpiLoggerFactory {
|
||||
|
||||
/**
|
||||
* Create the logger given the name.
|
||||
*/
|
||||
SpiLogger create(String name);
|
||||
}
|
||||
@@ -134,27 +134,40 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
/**
|
||||
* Includes soft deletes rows in the result.
|
||||
*/
|
||||
SOFT_DELETED,
|
||||
SOFT_DELETED(false),
|
||||
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
DRAFT,
|
||||
DRAFT(false),
|
||||
|
||||
/**
|
||||
* Query runs against current data (normal).
|
||||
*/
|
||||
CURRENT,
|
||||
CURRENT(false),
|
||||
|
||||
/**
|
||||
* Query runs potentially returning many versions of the same bean.
|
||||
*/
|
||||
VERSIONS,
|
||||
VERSIONS(true),
|
||||
|
||||
/**
|
||||
* Query runs 'As Of' a given date time.
|
||||
*/
|
||||
AS_OF;
|
||||
AS_OF(true);
|
||||
|
||||
private final boolean history;
|
||||
|
||||
TemporalMode(boolean history) {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a history query.
|
||||
*/
|
||||
public boolean isHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode of the query of if null return CURRENT mode.
|
||||
@@ -644,9 +657,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
boolean isBeanCachePut();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
|
||||
* Return true if we must hit the DB (Cache reload or select for update).
|
||||
*/
|
||||
boolean isBeanCacheReload();
|
||||
boolean isForceHitDatabase();
|
||||
|
||||
/**
|
||||
* Return the cache mode for using the bean cache (Get and Put).
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeaninternal.server.transaction.ProfileStream;
|
||||
@@ -101,6 +100,11 @@ public interface SpiTransaction extends Transaction {
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Return the start timestamp for the transaction (JVM side).
|
||||
*/
|
||||
long getStartMillis();
|
||||
|
||||
/**
|
||||
* Return true if this transaction has updateAllLoadedProperties set.
|
||||
* If null is returned the server default is used (set on ServerConfig).
|
||||
@@ -179,7 +183,7 @@ public interface SpiTransaction extends Transaction {
|
||||
* Return true if this request should be batched. Conversely returns false
|
||||
* if this request should be executed immediately.
|
||||
*/
|
||||
boolean isBatchThisRequest(PersistRequest.Type type);
|
||||
boolean isBatchThisRequest();
|
||||
|
||||
/**
|
||||
* Return the BatchControl used to batch up persist requests.
|
||||
|
||||
@@ -3,12 +3,10 @@ package io.ebeaninternal.api;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeaninternal.server.transaction.ProfileStream;
|
||||
@@ -30,6 +28,11 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.translate(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartMillis() {
|
||||
return transaction.getStartMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLabel(String label) {
|
||||
transaction.setLabel(label);
|
||||
@@ -246,23 +249,18 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatch(PersistBatch persistBatchMode) {
|
||||
transaction.setBatch(persistBatchMode);
|
||||
public boolean isBatchMode() {
|
||||
return transaction.isBatchMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatch() {
|
||||
return transaction.getBatch();
|
||||
public void setBatchOnCascade(boolean batchMode) {
|
||||
transaction.setBatchOnCascade(batchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
|
||||
transaction.setBatchOnCascade(batchOnCascadeMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatchOnCascade() {
|
||||
return transaction.getBatchOnCascade();
|
||||
public boolean isBatchOnCascade() {
|
||||
return transaction.isBatchOnCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -356,8 +354,8 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest(PersistRequest.Type type) {
|
||||
return transaction.isBatchThisRequest(type);
|
||||
public boolean isBatchThisRequest() {
|
||||
return transaction.isBatchThisRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,10 +3,13 @@ package io.ebeaninternal.api;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
import io.ebeaninternal.server.transaction.DeleteByIdMap;
|
||||
import io.ebeaninternal.server.transaction.TransactionManager;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdates;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -27,16 +30,21 @@ public class TransactionEvent implements Serializable {
|
||||
*/
|
||||
private final transient boolean local;
|
||||
|
||||
private final long startMillis;
|
||||
|
||||
private TransactionEventTable eventTables;
|
||||
|
||||
private transient TransactionEventBeans eventBeans;
|
||||
private transient List<PersistRequestBean<?>> listenerNotify;
|
||||
|
||||
private transient DeleteByIdMap deleteByIdMap;
|
||||
|
||||
private transient CacheChangeSet changeSet;
|
||||
|
||||
/**
|
||||
* Create the TransactionEvent, one per Transaction.
|
||||
*/
|
||||
public TransactionEvent() {
|
||||
public TransactionEvent(long startMillis) {
|
||||
this.startMillis = startMillis;
|
||||
this.local = true;
|
||||
}
|
||||
|
||||
@@ -69,8 +77,8 @@ public class TransactionEvent implements Serializable {
|
||||
/**
|
||||
* Return the list of PersistRequestBean's for this transaction.
|
||||
*/
|
||||
public List<PersistRequestBean<?>> getPersistRequestBeans() {
|
||||
return (eventBeans == null) ? null : eventBeans.getRequests();
|
||||
public List<PersistRequestBean<?>> getListenerNotify() {
|
||||
return (listenerNotify == null) ? null : listenerNotify;
|
||||
}
|
||||
|
||||
public TransactionEventTable getEventTables() {
|
||||
@@ -92,26 +100,33 @@ public class TransactionEvent implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a inserted updated or deleted bean to the event.
|
||||
* Add post commit listeners. Watch this for large transactions.
|
||||
*/
|
||||
public void add(PersistRequestBean<?> request) {
|
||||
|
||||
if (request.isNotify()) {
|
||||
// either a BeanListener or Cache is interested
|
||||
if (eventBeans == null) {
|
||||
eventBeans = new TransactionEventBeans();
|
||||
}
|
||||
eventBeans.add(request);
|
||||
public void addListenerNotify(PersistRequestBean<?> request) {
|
||||
if (listenerNotify == null) {
|
||||
listenerNotify = new ArrayList<>();
|
||||
}
|
||||
listenerNotify.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the cache changeSet.
|
||||
*/
|
||||
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
|
||||
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
|
||||
if (eventBeans != null) {
|
||||
eventBeans.notifyCache(changeSet);
|
||||
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
|
||||
|
||||
if (changeSet == null && deleteByIdMap == null && eventTables == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (changeSet == null) {
|
||||
changeSet = new CacheChangeSet(manager.clockNowMillis());
|
||||
}
|
||||
if (eventTables != null && !eventTables.isEmpty()) {
|
||||
// notify cache with table based changes
|
||||
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
|
||||
for (TransactionEventTable.TableIUD tableIUD : eventTables.values()) {
|
||||
dm.cacheNotify(tableIUD, changeSet);
|
||||
}
|
||||
}
|
||||
if (deleteByIdMap != null) {
|
||||
deleteByIdMap.notifyCache(changeSet);
|
||||
@@ -123,12 +138,25 @@ public class TransactionEvent implements Serializable {
|
||||
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
|
||||
*/
|
||||
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
|
||||
|
||||
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
|
||||
if (persistRequestBeans != null) {
|
||||
for (PersistRequestBean<?> persistRequestBean : persistRequestBeans) {
|
||||
List<PersistRequestBean<?>> requests = getListenerNotify();
|
||||
if (requests != null) {
|
||||
for (PersistRequestBean<?> persistRequestBean : requests) {
|
||||
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CacheChangeSet that we add cache notification messages to.
|
||||
*
|
||||
* We want to add to this change set as we process requests allowing the
|
||||
* PersistRequestBean to be garbage collected for large transactions.
|
||||
*/
|
||||
public CacheChangeSet obtainCacheChangeSet() {
|
||||
if (changeSet == null) {
|
||||
changeSet = new CacheChangeSet(startMillis);
|
||||
}
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Lists of inserted updated and deleted beans that have a BeanPersistListener.
|
||||
* <p>
|
||||
* These beans will be sent to the appropriate BeanListeners after a successful
|
||||
* commit of the transaction.
|
||||
* </p>
|
||||
*/
|
||||
public class TransactionEventBeans {
|
||||
|
||||
final ArrayList<PersistRequestBean<?>> requests = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return the list of PersistRequests that BeanListeners are interested in.
|
||||
*/
|
||||
public List<PersistRequestBean<?>> getRequests() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bean for BeanListener notification.
|
||||
*/
|
||||
public void add(PersistRequestBean<?> request) {
|
||||
|
||||
requests.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the cache changes.
|
||||
*/
|
||||
public void notifyCache(CacheChangeSet changeSet) {
|
||||
for (PersistRequestBean<?> request : requests) {
|
||||
request.notifyCache(changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.event.BulkTableEvent;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessage;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessageList;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
@@ -12,7 +9,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TransactionEventTable implements Serializable {
|
||||
public final class TransactionEventTable implements Serializable, BinaryWritable {
|
||||
|
||||
private static final long serialVersionUID = 2236555729767483264L;
|
||||
|
||||
@@ -23,20 +20,13 @@ public final class TransactionEventTable implements Serializable {
|
||||
return "TransactionEventTable " + map.values();
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
@Override
|
||||
public void writeBinary(BinaryWriteContext out) throws IOException {
|
||||
for (TableIUD tableIud : map.values()) {
|
||||
tableIud.writeBinaryMessage(msgList);
|
||||
tableIud.writeBinary(out);
|
||||
}
|
||||
}
|
||||
|
||||
public void readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
|
||||
TableIUD tableIud = TableIUD.readBinaryMessage(dataInput);
|
||||
map.put(tableIud.getTableName(), tableIud);
|
||||
}
|
||||
|
||||
|
||||
public void add(TransactionEventTable table) {
|
||||
|
||||
for (TableIUD iud : table.values()) {
|
||||
@@ -47,7 +37,6 @@ public final class TransactionEventTable implements Serializable {
|
||||
public void add(String table, boolean insert, boolean update, boolean delete) {
|
||||
|
||||
table = table.toUpperCase();
|
||||
|
||||
add(new TableIUD(table, insert, update, delete));
|
||||
}
|
||||
|
||||
@@ -67,7 +56,7 @@ public final class TransactionEventTable implements Serializable {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public static class TableIUD implements Serializable, BulkTableEvent {
|
||||
public static class TableIUD implements Serializable, BulkTableEvent, BinaryWritable {
|
||||
|
||||
private static final long serialVersionUID = -1958317571064162089L;
|
||||
|
||||
@@ -83,7 +72,7 @@ public final class TransactionEventTable implements Serializable {
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
public static TableIUD readBinaryMessage(BinaryReadContext dataInput) throws IOException {
|
||||
|
||||
String table = dataInput.readUTF();
|
||||
boolean insert = dataInput.readBoolean();
|
||||
@@ -93,17 +82,13 @@ public final class TransactionEventTable implements Serializable {
|
||||
return new TableIUD(table, insert, update, delete);
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
BinaryMessage msg = new BinaryMessage(table.length() + 10);
|
||||
DataOutputStream os = msg.getOs();
|
||||
os.writeInt(BinaryMessage.TYPE_TABLEIUD);
|
||||
@Override
|
||||
public void writeBinary(BinaryWriteContext out) throws IOException {
|
||||
DataOutputStream os = out.start(TYPE_TABLEIUD);
|
||||
os.writeUTF(table);
|
||||
os.writeBoolean(insert);
|
||||
os.writeBoolean(update);
|
||||
os.writeBoolean(delete);
|
||||
|
||||
msgList.add(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface TxnProfileEventCodes {
|
||||
String EVT_INSERT = "i";
|
||||
String EVT_UPDATE = "u";
|
||||
String EVT_DELETE = "d";
|
||||
String EVT_SOFT_DELETE = "ds";
|
||||
String EVT_DELETE_SOFT = "ds";
|
||||
String EVT_DELETE_PERMANENT = "dp";
|
||||
String EVT_ORMUPDATE = "uo";
|
||||
String FIND_UPDATE = "uq";
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.config.DbMigrationConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebean.migration.runner.ScriptTransform;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import io.ebeaninternal.server.deploy.PartitionMeta;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -38,6 +43,8 @@ public class DdlGenerator {
|
||||
private final boolean createOnly;
|
||||
private final boolean jaxbPresent;
|
||||
private final boolean ddlCommitOnCreateIndex;
|
||||
private final String dbSchema;
|
||||
private final ScriptTransform scriptTransform;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropAllContent;
|
||||
@@ -48,6 +55,7 @@ public class DdlGenerator {
|
||||
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
this.dbSchema = serverConfig.getDbSchema();
|
||||
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
|
||||
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
|
||||
this.runDdl = false;
|
||||
@@ -56,6 +64,7 @@ public class DdlGenerator {
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
|
||||
}
|
||||
this.scriptTransform = createScriptTransform(serverConfig.getMigrationConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,98 +94,164 @@ public class DdlGenerator {
|
||||
* Run the DDL drop and DDL create scripts if properties have been set.
|
||||
*/
|
||||
protected void runDdl() {
|
||||
|
||||
if (runDdl) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
runInitSql();
|
||||
runDropSql();
|
||||
runCreateSql();
|
||||
runSeedSql();
|
||||
|
||||
} catch (IOException e) {
|
||||
String msg = "Error reading drop/create script from file system";
|
||||
throw new RuntimeException(msg, e);
|
||||
connection = obtainConnection();
|
||||
runDdlWith(connection);
|
||||
} finally {
|
||||
JdbcClose.rollback(connection);
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runDdlWith(Connection connection) {
|
||||
try {
|
||||
if (dbSchema != null) {
|
||||
createSchemaIfRequired(connection);
|
||||
}
|
||||
runInitSql(connection);
|
||||
runDropSql(connection);
|
||||
runCreateSql(connection);
|
||||
runSeedSql(connection);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error reading drop/create script from file system", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Connection obtainConnection() {
|
||||
try {
|
||||
return server.getPluginApi().getDataSource().getConnection();
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to obtain connection to run DDL", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void createSchemaIfRequired(Connection connection) {
|
||||
try {
|
||||
for (String schema : dbSchema.split(",")) {
|
||||
server.getDatabasePlatform().createSchemaIfNotExists(schema, connection);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to create DB Schema", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
public int runScript(Connection connection, boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
|
||||
Transaction transaction = server.createTransaction();
|
||||
Connection connection = transaction.getConnection();
|
||||
try {
|
||||
if (expectErrors) {
|
||||
connection.setAutoCommit(true);
|
||||
} else if (ddlCommitOnCreateIndex) {
|
||||
runner.setCommitOnCreateIndex();
|
||||
}
|
||||
int count = runner.runAll(content, connection);
|
||||
int count = runner.runAll(scriptTransform.transform(content), connection);
|
||||
if (expectErrors) {
|
||||
connection.setAutoCommit(false);
|
||||
}
|
||||
transaction.commit();
|
||||
connection.commit();
|
||||
return count;
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to run script", e);
|
||||
|
||||
} finally {
|
||||
transaction.end();
|
||||
JdbcClose.rollback(connection);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runDropSql() throws IOException {
|
||||
protected void runDropSql(Connection connection) throws IOException {
|
||||
if (!createOnly) {
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
runScript(connection, false, extraApply, "extra-ddl");
|
||||
}
|
||||
}
|
||||
|
||||
if (dropAllContent == null) {
|
||||
dropAllContent = readFile(getDropFileName());
|
||||
}
|
||||
runScript(true, dropAllContent, getDropFileName());
|
||||
runScript(connection, true, dropAllContent, getDropFileName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void runCreateSql() throws IOException {
|
||||
protected void runCreateSql(Connection connection) throws IOException {
|
||||
if (createAllContent == null) {
|
||||
createAllContent = readFile(getCreateFileName());
|
||||
}
|
||||
runScript(false, createAllContent, getCreateFileName());
|
||||
runScript(connection, false, createAllContent, getCreateFileName());
|
||||
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
if (currentModel.isTablePartitioning()) {
|
||||
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
|
||||
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
|
||||
runScript(connection, false, extraPartitioning, "builtin-partitioning-ddl");
|
||||
}
|
||||
}
|
||||
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
runScript(connection, false, extraApply, "extra-ddl");
|
||||
}
|
||||
|
||||
if (currentModel.isTablePartitioning()) {
|
||||
checkInitialTablePartitions(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void runInitSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlInitSql());
|
||||
/**
|
||||
* Check if table partitions exist and if not create some. The expectation is that
|
||||
* extra-ddl.xml should have some partition initialisation but this helps people get going.
|
||||
*/
|
||||
private void checkInitialTablePartitions(Connection connection) {
|
||||
|
||||
DatabasePlatform databasePlatform = server.getDatabasePlatform();
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (MTable table : currentModel.getPartitionedTables()) {
|
||||
String tableName = table.getName();
|
||||
if (!databasePlatform.tablePartitionsExist(connection, tableName)) {
|
||||
log.info("No table partitions for table {}", tableName);
|
||||
PartitionMeta meta = table.getPartitionMeta();
|
||||
String initPart = databasePlatform.tablePartitionInit(tableName, meta.getMode(), meta.getProperty(), table.singlePrimaryKey());
|
||||
sb.append(initPart).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
String initialPartitionSql = sb.toString();
|
||||
if (!initialPartitionSql.isEmpty()) {
|
||||
runScript(connection, false, initialPartitionSql, "initial table partitions");
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
log.error("Error checking initial table partitions", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runSeedSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlSeedSql());
|
||||
protected void runInitSql(Connection connection) throws IOException {
|
||||
runResourceScript(connection, server.getServerConfig().getDdlInitSql());
|
||||
}
|
||||
|
||||
protected void runResourceScript(String sqlScript) throws IOException {
|
||||
protected void runSeedSql(Connection connection) throws IOException {
|
||||
runResourceScript(connection, server.getServerConfig().getDdlSeedSql());
|
||||
}
|
||||
|
||||
protected void runResourceScript(Connection connection, String sqlScript) throws IOException {
|
||||
|
||||
if (sqlScript != null) {
|
||||
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
|
||||
if (is != null) {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runScript(false, content, sqlScript);
|
||||
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
|
||||
if (is != null) {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runScript(connection, false, content, sqlScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,8 +348,15 @@ public class DdlGenerator {
|
||||
buf.append(s).append("\n");
|
||||
}
|
||||
return buf.toString();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ScriptTransform for placeholder key/value replacement.
|
||||
*/
|
||||
private ScriptTransform createScriptTransform(DbMigrationConfig config) {
|
||||
|
||||
return ScriptTransform.build(config.getRunPlaceholders(), config.getRunPlaceholderMap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hana.HanaPlatform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
@@ -57,7 +58,7 @@ import java.util.List;
|
||||
*
|
||||
* DbMigration migration = DbMigration.create();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
* migration.setPlatform(Platform.POSTGRES);
|
||||
*
|
||||
* migration.generateMigration();
|
||||
*
|
||||
@@ -65,7 +66,7 @@ import java.util.List;
|
||||
*/
|
||||
public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DefaultDbMigration.class);
|
||||
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration");
|
||||
|
||||
private static final String initialVersion = "1.0";
|
||||
|
||||
@@ -84,6 +85,8 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
protected DatabasePlatform databasePlatform;
|
||||
|
||||
private boolean vanillaPlatform;
|
||||
|
||||
protected List<Pair> platforms = new ArrayList<>();
|
||||
|
||||
protected ServerConfig serverConfig;
|
||||
@@ -91,6 +94,14 @@ public class DefaultDbMigration implements DbMigration {
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
protected Boolean strictMode;
|
||||
protected Boolean includeGeneratedFileComment;
|
||||
protected String header;
|
||||
protected String applyPrefix;
|
||||
protected String version;
|
||||
protected String name;
|
||||
protected String generatePendingDrop;
|
||||
|
||||
protected boolean includeBuiltInPartitioning = true;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
@@ -148,6 +159,41 @@ public class DefaultDbMigration implements DbMigration {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplyPrefix(String applyPrefix) {
|
||||
this.applyPrefix = applyPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratePendingDrop(String generatePendingDrop) {
|
||||
this.generatePendingDrop = generatePendingDrop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
|
||||
this.includeGeneratedFileComment = includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning) {
|
||||
this.includeBuiltInPartitioning = includeBuiltInPartitioning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
* <p>
|
||||
@@ -156,6 +202,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
*/
|
||||
@Override
|
||||
public void setPlatform(Platform platform) {
|
||||
vanillaPlatform = true;
|
||||
setPlatform(getPlatform(platform));
|
||||
}
|
||||
|
||||
@@ -219,6 +266,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the generated migration or null
|
||||
*/
|
||||
@Override
|
||||
@@ -240,7 +288,13 @@ public class DefaultDbMigration implements DbMigration {
|
||||
try {
|
||||
Request request = createRequest();
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform);
|
||||
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
|
||||
} else {
|
||||
for (Pair pair : platforms) {
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(request.migrationDir, pair.prefix);
|
||||
generateExtraDdl(subPath, pair.platform, request.isTablePartitioning());
|
||||
}
|
||||
}
|
||||
|
||||
String pendingVersion = generatePendingDrop();
|
||||
@@ -257,6 +311,24 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the versions containing pending drops.
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPendingDrops() {
|
||||
if (!online) {
|
||||
DbOffline.setGenerateMigration();
|
||||
}
|
||||
setDefaults();
|
||||
try {
|
||||
return createRequest().getPendingDrops();
|
||||
} finally {
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration for each of the target platforms.
|
||||
*/
|
||||
@@ -270,21 +342,28 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Generate "repeatable" migration scripts.
|
||||
* <p>
|
||||
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
|
||||
* These take scrips from extra-ddl.xml (typically views) and outputs "repeatable"
|
||||
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
|
||||
* migration runner.
|
||||
* </p>
|
||||
*/
|
||||
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
|
||||
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException {
|
||||
|
||||
if (dbPlatform != null) {
|
||||
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
|
||||
if (extraDdl != null) {
|
||||
List<DdlScript> ddlScript = extraDdl.getDdlScript();
|
||||
for (DdlScript script : ddlScript) {
|
||||
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
|
||||
writeExtraDdl(migrationDir, script);
|
||||
}
|
||||
if (tablePartitioning && includeBuiltInPartitioning) {
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning());
|
||||
}
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
|
||||
}
|
||||
}
|
||||
|
||||
private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
|
||||
if (extraDdl != null) {
|
||||
List<DdlScript> ddlScript = extraDdl.getDdlScript();
|
||||
for (DdlScript script : ddlScript) {
|
||||
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
|
||||
writeExtraDdl(migrationDir, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +374,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
*/
|
||||
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
|
||||
|
||||
String fullName = repeatableMigrationName(script.getName());
|
||||
String fullName = repeatableMigrationName(script.isInit(), script.getName());
|
||||
|
||||
logger.info("writing repeatable script {}", fullName);
|
||||
|
||||
@@ -306,8 +385,16 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private String repeatableMigrationName(String scriptName) {
|
||||
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
|
||||
private String repeatableMigrationName(boolean init, String scriptName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (init) {
|
||||
sb.append("I__");
|
||||
} else {
|
||||
sb.append("R__");
|
||||
}
|
||||
sb.append(scriptName.replace(' ', '_'));
|
||||
sb.append(migrationConfig.getApplySuffix());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,6 +455,10 @@ public class DefaultDbMigration implements DbMigration {
|
||||
this.current = currentModel.read();
|
||||
}
|
||||
|
||||
boolean isTablePartitioning() {
|
||||
return current.isTablePartitioning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration for the pending drops for a given version.
|
||||
*/
|
||||
@@ -429,7 +520,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (nextDrop != null) {
|
||||
return nextDrop;
|
||||
}
|
||||
return migrationConfig.getGeneratePendingDrop();
|
||||
return generatePendingDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,14 +530,15 @@ public class DefaultDbMigration implements DbMigration {
|
||||
*/
|
||||
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
|
||||
|
||||
String version = migrationConfig.getVersion();
|
||||
String version = getVersion();
|
||||
if (version == null) {
|
||||
version = migrationModel.getNextVersion(initialVersion);
|
||||
}
|
||||
|
||||
String fullVersion = migrationConfig.getApplyPrefix() + version;
|
||||
if (migrationConfig.getName() != null) {
|
||||
fullVersion += "__" + toUnderScore(migrationConfig.getName());
|
||||
String name = getName();
|
||||
if (name != null) {
|
||||
fullVersion += "__" + toUnderScore(name);
|
||||
|
||||
} else if (dropsFor != null) {
|
||||
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
|
||||
@@ -474,8 +566,6 @@ public class DefaultDbMigration implements DbMigration {
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(writePath, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
|
||||
|
||||
generateExtraDdl(subPath, pair.platform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,7 +583,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (file.exists()) {
|
||||
return false;
|
||||
}
|
||||
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
|
||||
String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null;
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
|
||||
xmlWriter.write(dbMigration, file);
|
||||
return true;
|
||||
@@ -506,17 +596,81 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (server == null) {
|
||||
setServer(Ebean.getDefaultServer());
|
||||
}
|
||||
if (databasePlatform == null && platforms.isEmpty()) {
|
||||
// not explicitly set not set a list of platforms so
|
||||
// default to the platform of the default server
|
||||
if (vanillaPlatform || databasePlatform == null) {
|
||||
// not explicitly set so use the platform of the server
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.debug("set platform to {}", databasePlatform.getName());
|
||||
logger.trace("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
if (strictMode != null && migrationConfig != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
if (migrationConfig != null) {
|
||||
if (strictMode != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
}
|
||||
if (applyPrefix != null) {
|
||||
migrationConfig.setApplyPrefix(applyPrefix);
|
||||
}
|
||||
if (header != null) {
|
||||
migrationConfig.setDdlHeader(header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
private 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>
|
||||
*/
|
||||
private String getName() {
|
||||
String envName = readEnvironment("ddl.migration.name");
|
||||
if (!isEmpty(envName)) {
|
||||
return envName.trim();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the string is null or empty.
|
||||
*/
|
||||
private boolean isEmpty(String val) {
|
||||
return val == null || val.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system or environment property.
|
||||
*/
|
||||
private String readEnvironment(String key) {
|
||||
|
||||
String val = System.getProperty(key);
|
||||
if (val == null) {
|
||||
val = System.getenv(key);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
@@ -578,6 +732,8 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
return new SQLitePlatform();
|
||||
case HANA:
|
||||
return new HanaPlatform();
|
||||
case GENERIC:
|
||||
return new DatabasePlatform();
|
||||
|
||||
|
||||
@@ -28,7 +28,11 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
protected final TableDdl tableDdl;
|
||||
|
||||
public BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.tableDdl = new BaseTableDdl(serverConfig, platformDdl);
|
||||
this(serverConfig, platformDdl, new BaseTableDdl(serverConfig, platformDdl));
|
||||
}
|
||||
|
||||
protected BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl, TableDdl tableDdl) {
|
||||
this.tableDdl = tableDdl;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,7 +18,9 @@ public class DdlWrite {
|
||||
|
||||
private final DdlBuffer applyForeignKeys;
|
||||
|
||||
private final DdlBuffer applyHistory;
|
||||
private final DdlBuffer applyHistoryView;
|
||||
|
||||
private final DdlBuffer applyHistoryTrigger;
|
||||
|
||||
private final DdlBuffer dropAllForeignKeys;
|
||||
|
||||
@@ -39,7 +41,8 @@ public class DdlWrite {
|
||||
this.applyDropDependencies = new BaseDdlBuffer(configuration);
|
||||
this.apply = new BaseDdlBuffer(configuration);
|
||||
this.applyForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.applyHistory = new BaseDdlBuffer(configuration);
|
||||
this.applyHistoryView = new BaseDdlBuffer(configuration);
|
||||
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
|
||||
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.dropAll = new BaseDdlBuffer(configuration);
|
||||
}
|
||||
@@ -61,7 +64,8 @@ public class DdlWrite {
|
||||
public boolean isApplyEmpty() {
|
||||
return apply.getBuffer().isEmpty()
|
||||
&& applyForeignKeys.getBuffer().isEmpty()
|
||||
&& applyHistory.getBuffer().isEmpty()
|
||||
&& applyHistoryView.getBuffer().isEmpty()
|
||||
&& applyHistoryTrigger.getBuffer().isEmpty()
|
||||
&& applyDropDependencies.getBuffer().isEmpty();
|
||||
}
|
||||
|
||||
@@ -90,10 +94,17 @@ public class DdlWrite {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history DDL is written to.
|
||||
* Return the buffer that apply history-view DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistory() {
|
||||
return applyHistory;
|
||||
public DdlBuffer applyHistoryView() {
|
||||
return applyHistoryView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history-trigger DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistoryTrigger() {
|
||||
return applyHistoryTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class AbstractHanaDdl extends PlatformDdl {
|
||||
|
||||
private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\w+)\\s*\\[\\s*\\]\\s*(\\(\\d+\\))?", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public AbstractHanaDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.addColumn = "add (";
|
||||
this.addColumnSuffix = ")";
|
||||
this.alterColumn = "alter (";
|
||||
this.alterColumnSuffix = ")";
|
||||
this.columnDropDefault = " default null";
|
||||
this.columnSetDefault = " default";
|
||||
this.columnSetNotnull = " not null";
|
||||
this.columnSetNull = " null";
|
||||
this.dropColumn = "drop (";
|
||||
this.dropColumnSuffix = ")";
|
||||
this.dropConstraintIfExists = "drop constraint ";
|
||||
this.dropIndexIfExists = "drop index ";
|
||||
this.dropSequenceIfExists = "drop sequence ";
|
||||
this.dropTableCascade = " cascade";
|
||||
this.dropTableIfExists = "drop table ";
|
||||
this.fallbackArrayType = "nvarchar(1000)";
|
||||
this.historyDdl = new HanaHistoryDdl();
|
||||
this.identitySuffix = " generated by default as identity";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnBaseAttributes(AlterColumn alter) {
|
||||
String tableName = alter.getTableName();
|
||||
String columnName = alter.getColumnName();
|
||||
String currentType = alter.getCurrentType();
|
||||
String type = alter.getType() != null ? alter.getType() : currentType;
|
||||
type = convert(type, false);
|
||||
currentType = convert(currentType, false);
|
||||
boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
|
||||
String notnullClause = notnull ? " not null" : "";
|
||||
String defaultValue = DdlHelp.isDropDefault(alter.getDefaultValue()) ? "null"
|
||||
: (alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue());
|
||||
String defaultValueClause = (defaultValue == null || defaultValue.isEmpty()) ? "" : " default " + defaultValue;
|
||||
|
||||
try {
|
||||
DdlBuffer buffer = new BaseDdlBuffer(null);
|
||||
if (!isConvertible(currentType, type)) {
|
||||
// add an intermediate conversion if possible
|
||||
if (isNumberType(currentType)) {
|
||||
// numbers can always be converted to decimal
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
|
||||
.append(" decimal ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
|
||||
.endOfStatement();
|
||||
|
||||
} else if (isStringType(currentType)) {
|
||||
// strings can always be converted to nclob
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
|
||||
.append(" nclob ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
|
||||
.endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
|
||||
.append(" ").append(type).append(defaultValueClause).append(notnullClause).append(alterColumnSuffix);
|
||||
|
||||
return buffer.getBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return new HanaDdlHandler(serverConfig, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnType(String tableName, String columnName, String type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
Matcher matcher = ARRAY_PATTERN.matcher(logicalArrayType);
|
||||
if (matcher.matches()) {
|
||||
return convert(matcher.group(1), false) + " array" + (matcher.group(2) == null ? "" : matcher.group(2));
|
||||
} else {
|
||||
return fallbackArrayType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
if (nullableColumns == null || nullableColumns.length == 0) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
} else {
|
||||
return "-- cannot create unique index \"" + uqName + "\" on table \"" + tableName + "\" with nullable columns";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
|
||||
DdlBuffer buffer = new BaseDdlBuffer(null);
|
||||
try {
|
||||
buffer.append("delimiter $$").newLine();
|
||||
buffer.append("do").newLine();
|
||||
buffer.append("begin").newLine();
|
||||
buffer.append("declare exit handler for sql_error_code 397 begin end").endOfStatement();
|
||||
buffer.append("exec 'alter table ").append(tableName).append(" ").append(dropUniqueConstraint).append(" ")
|
||||
.append(maxConstraintName(uniqueConstraintName)).append("'").endOfStatement();
|
||||
buffer.append("end").endOfStatement();
|
||||
buffer.append("$$");
|
||||
return buffer.getBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableDropConstraint(String tableName, String constraintName) {
|
||||
return alterTableDropUniqueConstraint(tableName, constraintName);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is rather complex to delete a column on HANA as there must not exist any
|
||||
* foreign keys. That's why we call a user stored procedure here
|
||||
*/
|
||||
@Override
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')")
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a data type can be converted to another data type. Data types can't
|
||||
* be converted if the target type has a lower precision than the source type.
|
||||
*
|
||||
* @param sourceType The source data type
|
||||
* @param targetType the target data type
|
||||
* @return {@code true} if the type can be converted, {@code false} otherwise
|
||||
*/
|
||||
private boolean isConvertible(String sourceType, String targetType) {
|
||||
if (Objects.equals(sourceType, targetType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourceType == null || targetType == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("bigint".equals(sourceType)) {
|
||||
if ("integer".equals(targetType) || "smallint".equals(targetType) || "tinyint".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("integer".equals(sourceType)) {
|
||||
if ("smallint".equals(targetType) || "tinyint".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("smallint".equals(sourceType)) {
|
||||
if ("tinyint".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("double".equals(sourceType)) {
|
||||
if ("real".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DbPlatformType dbPlatformSourceType = DbPlatformType.parse(sourceType);
|
||||
|
||||
if ("float".equals(dbPlatformSourceType.getName())) {
|
||||
if ("real".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("varchar".equals(dbPlatformSourceType.getName()) || "nvarchar".equals(dbPlatformSourceType.getName())) {
|
||||
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
|
||||
if ("varchar".equals(dbPlatformTargetType.getName()) || "nvarchar".equals(dbPlatformTargetType.getName())) {
|
||||
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if ("decimal".equals(dbPlatformSourceType.getName())) {
|
||||
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
|
||||
if ("decimal".equals(dbPlatformTargetType.getName())) {
|
||||
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()
|
||||
|| dbPlatformSourceType.getDefaultScale() > dbPlatformTargetType.getDefaultScale()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isNumberType(String type) {
|
||||
return type != null
|
||||
&& ("bigint".equals(type) || "integer".equals(type) || "smallint".equals(type) || "tinyint".equals(type)
|
||||
|| type.startsWith("float") || "real".equals(type) || "double".equals(type) || type.startsWith("decimal"));
|
||||
}
|
||||
|
||||
private boolean isStringType(String type) {
|
||||
return type != null
|
||||
&& (type.startsWith("varchar") || type.startsWith("nvarchar") || "clob".equals(type) || "nclob".equals(type));
|
||||
}
|
||||
}
|
||||
+62
-23
@@ -42,6 +42,12 @@ import java.util.Map;
|
||||
*/
|
||||
public class BaseTableDdl implements TableDdl {
|
||||
|
||||
enum HistorySupport {
|
||||
NONE,
|
||||
SQL2011,
|
||||
TRIGGER_BASED
|
||||
}
|
||||
|
||||
protected final DbConstraintNaming naming;
|
||||
|
||||
protected final NamingConvention namingConvention;
|
||||
@@ -78,22 +84,23 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
private boolean strictMode;
|
||||
|
||||
private final boolean sql2011History;
|
||||
private final HistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* Helper class that is used to execute the migration ddl before and after the migration action.
|
||||
*/
|
||||
private class DdlMigrationHelp {
|
||||
private List<String> before;
|
||||
private List<String> after;
|
||||
private String tableName;
|
||||
private String columnName;
|
||||
private String defaultValue;
|
||||
private final List<String> before;
|
||||
private final List<String> after;
|
||||
private final String tableName;
|
||||
private final String columnName;
|
||||
private final String defaultValue;
|
||||
private final boolean withHistory;
|
||||
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when adding a NEW column.
|
||||
*/
|
||||
DdlMigrationHelp(String tableName, Column column) {
|
||||
DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
|
||||
this.tableName = tableName;
|
||||
this.columnName = column.getName();
|
||||
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
|
||||
@@ -105,6 +112,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
|
||||
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
|
||||
this.withHistory = withHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +127,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
|
||||
// here we add the platform's default update script
|
||||
withHistory = isTrue(alter.isWithHistory());
|
||||
if (alter.getBefore().isEmpty() && alterNotNull) {
|
||||
if (defaultValue == null) {
|
||||
handleStrictError(tableName, columnName);
|
||||
@@ -134,6 +143,10 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (!before.isEmpty()) {
|
||||
buffer.end();
|
||||
}
|
||||
|
||||
if (!before.isEmpty() && withHistory) {
|
||||
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
|
||||
}
|
||||
for (String ddlScript : before) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
|
||||
buffer.endOfStatement();
|
||||
@@ -141,6 +154,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
|
||||
void writeAfter(DdlBuffer buffer) throws IOException {
|
||||
if (!after.isEmpty() && withHistory) {
|
||||
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
|
||||
}
|
||||
// here we run post migration scripts
|
||||
for (String ddlScript : after) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
|
||||
@@ -198,7 +214,11 @@ public class BaseTableDdl implements TableDdl {
|
||||
this.platformDdl.configure(serverConfig);
|
||||
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
|
||||
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
|
||||
this.sql2011History = hist != null && hist.isStandardsBased();
|
||||
if (hist == null) {
|
||||
this.historySupport = HistorySupport.NONE;
|
||||
} else {
|
||||
this.historySupport = hist.isStandardsBased() ? HistorySupport.SQL2011 : HistorySupport.TRIGGER_BASED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,15 +257,19 @@ public class BaseTableDdl implements TableDdl {
|
||||
useSequence = (IdType.SEQUENCE == useDbIdentityType);
|
||||
}
|
||||
|
||||
String partitionMode = createTable.getPartitionMode();
|
||||
|
||||
DdlBuffer apply = writer.apply();
|
||||
apply.append("create table ").append(tableName).append(" (");
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(tableName).append(" (");
|
||||
writeTableColumns(apply, columns, useIdentity);
|
||||
writeCheckConstraints(apply, createTable);
|
||||
writeUniqueConstraints(apply, createTable);
|
||||
writeCompoundUniqueConstraints(apply, createTable);
|
||||
if (!pk.isEmpty()) {
|
||||
// defined on the columns
|
||||
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
|
||||
if (partitionMode == null || !platformDdl.suppressPrimaryKeyOnPartition()) {
|
||||
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
|
||||
}
|
||||
}
|
||||
if (platformDdl.isInlineForeignKeys()) {
|
||||
writeInlineForeignKeys(writer, createTable);
|
||||
@@ -253,6 +277,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
apply.newLine().append(")");
|
||||
addTableCommentInline(apply, createTable);
|
||||
if (partitionMode != null) {
|
||||
platformDdl.addTablePartition(apply, partitionMode, createTable.getPartitionColumn());
|
||||
}
|
||||
apply.endOfStatement();
|
||||
|
||||
addComments(apply, createTable);
|
||||
@@ -700,8 +727,14 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
@Override
|
||||
public void generateEpilog(DdlWrite write) throws IOException {
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
if (!regenerateHistoryTriggers.isEmpty()) {
|
||||
platformDdl.lockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
|
||||
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
}
|
||||
|
||||
platformDdl.unlockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
|
||||
}
|
||||
platformDdl.generateEpilog(write);
|
||||
}
|
||||
@@ -722,15 +755,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = addColumn.getTableName();
|
||||
List<Column> columns = addColumn.getColumn();
|
||||
for (Column column : columns) {
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false);
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
|
||||
}
|
||||
|
||||
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
|
||||
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
for (Column column : columns) {
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true);
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,7 +804,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = dropColumn.getTableName();
|
||||
|
||||
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
|
||||
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
|
||||
|
||||
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// also drop from the history table
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
|
||||
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
|
||||
@@ -813,12 +847,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(alterColumn.getComment())) {
|
||||
alterColumnComment(writer, alterColumn);
|
||||
}
|
||||
if (hasValue(alterColumn.getDropCheckConstraint())) {
|
||||
dropCheckConstraint(writer, alterColumn, alterColumn.getDropCheckConstraint());
|
||||
}
|
||||
|
||||
boolean alterCheckConstraint = hasValue(alterColumn.getCheckConstraint());
|
||||
|
||||
if (alterCheckConstraint) {
|
||||
// drop constraint before altering type etc
|
||||
dropCheckConstraint(writer, alterColumn);
|
||||
dropCheckConstraint(writer, alterColumn, alterColumn.getCheckConstraintName());
|
||||
}
|
||||
boolean alterBaseAttributes = false;
|
||||
if (hasValue(alterColumn.getType())) {
|
||||
@@ -872,8 +909,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// mysql and sql server column type change allowing nulls in the history table column
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
|
||||
alterHistoryColumn.setColumnName(alter.getColumnName());
|
||||
@@ -894,9 +932,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
}
|
||||
|
||||
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) throws IOException {
|
||||
|
||||
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), alter.getCheckConstraintName());
|
||||
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName);
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
@@ -923,7 +961,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
if (isTrue(alter.isWithHistory()) && !sql2011History) {
|
||||
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
// apply same type change to matching column in the history table
|
||||
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
@@ -979,8 +1018,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
|
||||
}
|
||||
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) throws IOException {
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
|
||||
if (!onHistoryTable) {
|
||||
help.writeBefore(buffer);
|
||||
}
|
||||
|
||||
+17
-17
@@ -69,7 +69,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
*/
|
||||
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
|
||||
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
|
||||
|
||||
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
|
||||
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
|
||||
createHistoryTable(apply, table);
|
||||
@@ -175,7 +175,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
|
||||
apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(table.getName()).append(historySuffix).append("(").newLine();
|
||||
|
||||
Collection<MColumn> cols = table.allColumns();
|
||||
for (MColumn column : cols) {
|
||||
@@ -210,19 +210,19 @@ 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();
|
||||
/**
|
||||
* For postgres/h2/mysql 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.
|
||||
*/
|
||||
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
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();
|
||||
|
||||
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
|
||||
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
|
||||
}
|
||||
|
||||
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
|
||||
@@ -238,8 +238,8 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
}
|
||||
|
||||
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodStart).endOfStatement();
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
|
||||
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodStart);
|
||||
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodEnd);
|
||||
}
|
||||
|
||||
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
|
||||
|
||||
+10
-2
@@ -28,10 +28,18 @@ public class DbTriggerUpdate {
|
||||
/**
|
||||
* Return the appropriate buffer for the current mode.
|
||||
*/
|
||||
public DdlBuffer historyBuffer() {
|
||||
return writer.applyHistory();
|
||||
public DdlBuffer historyViewBuffer() {
|
||||
return writer.applyHistoryView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate buffer for the current mode.
|
||||
*/
|
||||
public DdlBuffer historyTriggerBuffer() {
|
||||
return writer.applyHistoryTrigger();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the appropriate drop dependency buffer for the current mode.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,8 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
|
||||
|
||||
public H2HistoryDdl() {
|
||||
this.sysPeriodType = "timestamp";
|
||||
this.currentTimestamp = "now()";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -27,7 +29,7 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryTrigger();
|
||||
|
||||
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName);
|
||||
}
|
||||
@@ -35,7 +37,9 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
@Override
|
||||
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.historyBuffer();
|
||||
recreateHistoryView(update);
|
||||
|
||||
DdlBuffer buffer = update.historyTriggerBuffer();
|
||||
dropTriggers(buffer, update.getBaseTable());
|
||||
addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable());
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
public class HanaColumnStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaColumnStoreDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.createTable = "create column table";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createIndex(String indexName, String tableName, String[] columns) {
|
||||
if (columns == null || columns.length == 0) {
|
||||
return "-- cannot create index: no columns given";
|
||||
}
|
||||
|
||||
if (columns.length == 1) {
|
||||
return "-- explicit index \"" + indexName + "\" for single column \"" + columns[0] + "\" of table \"" + tableName
|
||||
+ "\" is not necessary";
|
||||
}
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("create inverted hash index ").append(maxConstraintName(indexName)).append(" on ").append(tableName);
|
||||
appendColumns(columns, buffer);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dropIndex(String indexName, String tableName) {
|
||||
DdlBuffer buffer = new BaseDdlBuffer(null);
|
||||
try {
|
||||
buffer.append("delimiter $$").newLine();
|
||||
buffer.append("do").newLine();
|
||||
buffer.append("begin").newLine();
|
||||
buffer.append("declare exit handler for sql_error_code 261 begin end").endOfStatement();
|
||||
buffer.append("exec '").append(dropIndexIfExists).append(maxConstraintName(indexName)).append("'")
|
||||
.endOfStatement();
|
||||
buffer.append("end").endOfStatement();
|
||||
buffer.append("$$");
|
||||
return buffer.getBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
|
||||
|
||||
public class HanaDdlHandler extends BaseDdlHandler {
|
||||
|
||||
public HanaDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
super(serverConfig, platformDdl, new HanaTableDdl(serverConfig, platformDdl));
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.model.MColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class HanaHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
private String systemPeriodStart;
|
||||
private String systemPeriodEnd;
|
||||
private PlatformDdl platformDdl;
|
||||
private String historySuffix;
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
private Map<String, String> createdHistoryTables = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "_start";
|
||||
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "_end";
|
||||
this.platformDdl = platformDdl;
|
||||
this.historySuffix = serverConfig.getHistoryTableSuffix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
|
||||
String tableName = table.getName();
|
||||
String historyTableName = tableName + historySuffix;
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
if (apply.isEmpty()) {
|
||||
createdHistoryTables.clear();
|
||||
}
|
||||
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(historyTableName).append(" (").newLine();
|
||||
|
||||
// create history table
|
||||
Collection<MColumn> cols = table.allColumns();
|
||||
for (MColumn column : cols) {
|
||||
if (!column.isDraftOnly()) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType(), column.getDefaultValue(), column.isNotnull(),
|
||||
column.isIdentity() ? platformDdl.identitySuffix : null);
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
}
|
||||
writeColumnDefinition(apply, systemPeriodStart, "TIMESTAMP", null, false, null);
|
||||
apply.append(",").newLine();
|
||||
writeColumnDefinition(apply, systemPeriodEnd, "TIMESTAMP", null, false, null);
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
|
||||
// enable system versioning
|
||||
apply.append("alter table ").append(tableName).append(" add (").newLine();
|
||||
apply.append(" ").append(systemPeriodStart).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW START, ").newLine();
|
||||
apply.append(" ").append(systemPeriodEnd).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW END").newLine();
|
||||
apply.append(")").endOfStatement();
|
||||
|
||||
apply.append("alter table ").append(tableName).append(" add period for system_time(").append(systemPeriodStart)
|
||||
.append(",").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
enableSystemVersioning(apply, tableName, historyTableName, true, false);
|
||||
|
||||
createdHistoryTables.put(tableName, historyTableName);
|
||||
|
||||
dropHistoryTable(writer.dropAll(), tableName, historyTableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
dropHistoryTable(writer.applyDropDependencies(), dropHistoryTable.getBaseTable(),
|
||||
dropHistoryTable.getBaseTable() + historySuffix);
|
||||
}
|
||||
|
||||
protected void dropHistoryTable(DdlBuffer apply, String baseTable, String historyTable) throws IOException {
|
||||
// disable system versioning
|
||||
disableSystemVersioning(apply, baseTable);
|
||||
|
||||
apply.append("alter table ").append(baseTable).append(" drop period for system_time").endOfStatement();
|
||||
|
||||
// drop the period columns
|
||||
apply.append("alter table ").append(baseTable).append(" drop (").append(systemPeriodStart).append(",")
|
||||
.append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
// drop the history table
|
||||
apply.append("drop table ").append(historyTable).append(" cascade").endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
|
||||
MTable table = writer.getTable(addHistoryTable.getBaseTable());
|
||||
if (table == null) {
|
||||
throw new IllegalStateException("MTable " + addHistoryTable.getBaseTable() + " not found in writer? (required for history DDL)");
|
||||
}
|
||||
createWithHistory(writer, table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type, String defaultValue,
|
||||
boolean isNotNull, String generated) throws IOException {
|
||||
|
||||
String platformType = platformDdl.convert(type, false);
|
||||
buffer.append(" ").append(platformDdl.lowerColumnName(columnName));
|
||||
buffer.append(" ").append(platformType);
|
||||
if (defaultValue != null) {
|
||||
buffer.append(" default ").append(defaultValue);
|
||||
}
|
||||
if (isNotNull) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
if (generated != null) {
|
||||
buffer.append(" ").append(generated);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableSystemVersioning(DdlBuffer apply, String tableName) throws IOException {
|
||||
disableSystemVersioning(apply, tableName, false);
|
||||
}
|
||||
|
||||
public void disableSystemVersioning(DdlBuffer apply, String tableName, boolean uniqueStatement) throws IOException {
|
||||
apply.append("alter table ").append(tableName).append(" drop system versioning");
|
||||
if (uniqueStatement) {
|
||||
// needed for the DB migration test to prevent the statement from being filtered
|
||||
// out as a duplicate
|
||||
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
|
||||
}
|
||||
apply.endOfStatement();
|
||||
}
|
||||
|
||||
public void enableSystemVersioning(DdlBuffer apply, String tableName, String historyTableName, boolean validated,
|
||||
boolean uniqueStatement) throws IOException {
|
||||
apply.append("alter table ").append(tableName).append(" add system versioning history table ").append(historyTableName);
|
||||
if (!validated) {
|
||||
apply.append(" not validated");
|
||||
}
|
||||
if (uniqueStatement) {
|
||||
// needed for the DB migration test to prevent the statement from being filtered
|
||||
// out as a duplicate
|
||||
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
|
||||
}
|
||||
apply.endOfStatement();
|
||||
}
|
||||
|
||||
public boolean isSystemVersioningEnabled(String tableName) {
|
||||
return !createdHistoryTables.containsKey(tableName);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
public class HanaRowStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaRowStoreDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.createTable = "create row table";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.config.PropertiesWrapper;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
public class HanaTableDdl extends BaseTableDdl {
|
||||
|
||||
private final HanaHistoryDdl historyDdl;
|
||||
private final boolean generateUniqueDdl;
|
||||
|
||||
public HanaTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
super(serverConfig, platformDdl);
|
||||
this.historyDdl = (HanaHistoryDdl) platformDdl.historyDdl;
|
||||
if (serverConfig.getProperties() != null) {
|
||||
PropertiesWrapper wrapper = new PropertiesWrapper("ebean", "hana", serverConfig.getProperties(), serverConfig.getClassLoadConfig());
|
||||
this.generateUniqueDdl = wrapper.getBoolean("generateUniqueDdl", false);
|
||||
} else {
|
||||
this.generateUniqueDdl = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
String ddl = platformDdl.alterColumnBaseAttributes(alter);
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
|
||||
String tableName = addColumn.getTableName();
|
||||
MTable table = writer.getTable(tableName);
|
||||
if (table == null) {
|
||||
super.generate(writer, addColumn);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
historyDdl.disableSystemVersioning(writer.apply(), table.getName(), this.generateUniqueDdl);
|
||||
}
|
||||
|
||||
super.generate(writer, addColumn);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
List<Column> columns = addColumn.getColumn();
|
||||
for (Column column : columns) {
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
|
||||
}
|
||||
|
||||
historyDdl.enableSystemVersioning(writer.apply(), table.getName(), historyTable, false, this.generateUniqueDdl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
String tableName = alterColumn.getTableName();
|
||||
MTable table = writer.getTable(tableName);
|
||||
if (table == null) {
|
||||
super.generate(writer, alterColumn);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
|
||||
}
|
||||
|
||||
super.generate(writer, alterColumn);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
if (hasValue(alterColumn.getType()) || hasValue(alterColumn.getDefaultValue()) || alterColumn.isNotnull() != null) {
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable);
|
||||
alterHistoryColumn.setColumnName(alterColumn.getColumnName());
|
||||
alterHistoryColumn.setType(alterColumn.getType());
|
||||
alterHistoryColumn.setDefaultValue(alterColumn.getDefaultValue());
|
||||
alterHistoryColumn.setNotnull(alterColumn.isNotnull());
|
||||
alterHistoryColumn.setCurrentType(alterColumn.getCurrentType());
|
||||
alterHistoryColumn.setCurrentDefaultValue(alterColumn.getCurrentDefaultValue());
|
||||
alterHistoryColumn.setCurrentNotnull(alterColumn.isCurrentNotnull());
|
||||
String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
|
||||
|
||||
// write the apply to history table
|
||||
writer.apply().append(histColumnDdl).endOfStatement();
|
||||
}
|
||||
|
||||
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
|
||||
String tableName = dropColumn.getTableName();
|
||||
MTable table = writer.getTable(tableName);
|
||||
if (table == null) {
|
||||
super.generate(writer, dropColumn);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
|
||||
}
|
||||
|
||||
super.generate(writer, dropColumn);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
// also drop from the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
alterTableDropColumn(writer.apply(), historyTable, dropColumn.getColumnName());
|
||||
|
||||
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -17,7 +17,8 @@ public class HistoryTableUpdate {
|
||||
ADD,
|
||||
DROP,
|
||||
INCLUDE,
|
||||
EXCLUDE
|
||||
EXCLUDE,
|
||||
ALTER
|
||||
}
|
||||
|
||||
private static class Column {
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* MySql specific DDL.
|
||||
@@ -37,9 +38,20 @@ public class MySqlDdl extends PlatformDdl {
|
||||
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is rather complex to delete a column on MySql as there must not exist any foreign keys.
|
||||
* That's why we call a user stored procedure here
|
||||
*/
|
||||
@Override
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
|
||||
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableDropConstraint(String tableName, String constraintName) {
|
||||
// drop constraint not supported
|
||||
// drop constraint not supported in MySQL 5.7 and 8.0 but starting with MariaDB 10.2.1 CHECK is evaluated
|
||||
// TODO: Implement for MariaDB >= 10.2.1
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,7 +71,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
@Override
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
|
||||
|
||||
// use alter
|
||||
return "alter table " + tableName + " alter " + columnName + " " + suffix;
|
||||
@@ -113,10 +125,39 @@ public class MySqlDdl extends PlatformDdl {
|
||||
}
|
||||
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
// alter comment currently not supported as it requires to repeat whole column definition
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Locks all tables for triggers that have to be updated.
|
||||
*/
|
||||
@Override
|
||||
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
if (!tables.isEmpty()) {
|
||||
buffer.append("lock tables ");
|
||||
int i = 0;
|
||||
for (String table : tables) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
buffer.append(table).append(" write");
|
||||
i++;
|
||||
}
|
||||
buffer.endOfStatement();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks all tables for triggers that have to be updated.
|
||||
*/
|
||||
@Override
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
buffer.append("unlock tables").endOfStatement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -34,19 +34,19 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
@Override
|
||||
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.historyBuffer();
|
||||
recreateHistoryView(update);
|
||||
|
||||
DdlBuffer buffer = update.historyTriggerBuffer();
|
||||
String baseTable = update.getBaseTable();
|
||||
|
||||
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(String triggerName, DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer apply = update.historyBuffer();
|
||||
DdlBuffer apply = update.historyTriggerBuffer();
|
||||
apply
|
||||
.append("delimiter $$").newLine()
|
||||
.append("create trigger ").append(triggerName).append(" before update on ").append(update.getBaseTable())
|
||||
@@ -59,7 +59,7 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer apply = update.historyBuffer();
|
||||
DdlBuffer apply = update.historyTriggerBuffer();
|
||||
apply
|
||||
.append("delimiter $$").newLine()
|
||||
.append("create trigger ").append(triggerName).append(" before delete on ").append(update.getBaseTable())
|
||||
|
||||
+66
-14
@@ -20,10 +20,12 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.IdentityType;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -76,14 +78,16 @@ public class PlatformDdl {
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
|
||||
protected String alterColumn = "alter column";
|
||||
|
||||
protected String dropConstraint = "drop constraint";
|
||||
|
||||
protected String alterColumnSuffix = "";
|
||||
|
||||
protected String dropUniqueConstraint = "drop constraint";
|
||||
|
||||
protected String addConstraint = "add constraint";
|
||||
|
||||
protected String addColumn = "add column";
|
||||
|
||||
protected String addColumnSuffix = "";
|
||||
|
||||
protected String columnSetType = "";
|
||||
|
||||
@@ -96,6 +100,12 @@ public class PlatformDdl {
|
||||
protected String columnSetNull = "set null";
|
||||
|
||||
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
|
||||
|
||||
protected String createTable = "create table";
|
||||
|
||||
protected String dropColumn = "drop column";
|
||||
|
||||
protected String dropColumnSuffix = "";
|
||||
|
||||
/**
|
||||
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
|
||||
@@ -246,6 +256,9 @@ public class PlatformDdl {
|
||||
* Convert the standard type to the platform specific type.
|
||||
*/
|
||||
public String convert(String type, boolean identity) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
if (type.contains("[]")) {
|
||||
return convertArrayType(type);
|
||||
}
|
||||
@@ -418,7 +431,7 @@ public class PlatformDdl {
|
||||
* Drop a unique constraint from the table.
|
||||
*/
|
||||
public String alterTableDropConstraint(String tableName, String constraintName) {
|
||||
return "alter table " + tableName + " " + dropConstraint + " " + maxConstraintName(constraintName);
|
||||
return "alter table " + tableName + " " + dropConstraintIfExists + " " + maxConstraintName(constraintName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -442,32 +455,38 @@ public class PlatformDdl {
|
||||
.append(" ").append(addColumn).append(" ").append(column.getName())
|
||||
.append(" ").append(convertedType);
|
||||
|
||||
if (!onHistoryTable) {
|
||||
|
||||
if (defaultValue != null) {
|
||||
// Add default value also to history table if it is not excluded
|
||||
if (defaultValue != null) {
|
||||
if (!onHistoryTable || !isTrue(column.isHistoryExclude())) {
|
||||
buffer.append(" default ");
|
||||
buffer.append(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!onHistoryTable) {
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
buffer.append(addColumnSuffix);
|
||||
buffer.endOfStatement();
|
||||
|
||||
// check constraints cannot be added in one statement for h2
|
||||
if (!StringHelper.isNull(column.getCheckConstraint())) {
|
||||
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
|
||||
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(),
|
||||
column.getCheckConstraint());
|
||||
buffer.append(ddl).endOfStatement();
|
||||
}
|
||||
} else {
|
||||
buffer.append(addColumnSuffix);
|
||||
buffer.endOfStatement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
|
||||
.endOfStatement();
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(dropColumn).append(" ").append(columnName)
|
||||
.append(dropColumnSuffix).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,19 +506,19 @@ public class PlatformDdl {
|
||||
*/
|
||||
public String alterColumnType(String tableName, String columnName, String type) {
|
||||
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false);
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false) + alterColumnSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter a column adding or removing the not null constraint.
|
||||
* <p>
|
||||
* Note that that MySql and SQL Server instead use alterColumnBaseAttributes()
|
||||
* Note that that MySql, SQL Server, and HANA instead use alterColumnBaseAttributes()
|
||||
* </p>
|
||||
*/
|
||||
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
|
||||
|
||||
String suffix = notnull ? columnSetNotnull : columnSetNull;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,17 +534,17 @@ public class PlatformDdl {
|
||||
*/
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter column setting both the type and not null constraint.
|
||||
* <p>
|
||||
* Used by MySql and SQL Server as these require both column attributes to be set together.
|
||||
* Used by MySql, SQL Server, and HANA as these require both column attributes to be set together.
|
||||
* </p>
|
||||
*/
|
||||
public String alterColumnBaseAttributes(AlterColumn alter) {
|
||||
// by default do nothing, only used by mysql and sql server as they can only
|
||||
// by default do nothing, only used by mysql, sql server, and HANA as they can only
|
||||
// modify the column with the full column definition
|
||||
return null;
|
||||
}
|
||||
@@ -641,4 +660,37 @@ public class PlatformDdl {
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mysql-specific: Locks all tables for triggers that have to be updated.
|
||||
*/
|
||||
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mysql-specific: Unlocks all tables for triggers that have to be updated.
|
||||
*/
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database-specific "create table" command prefix. For HANA this is
|
||||
* either "create column table" or "create row table", for all other databases
|
||||
* it is "create table".
|
||||
*
|
||||
* @return The "create table" command prefix
|
||||
*/
|
||||
public String getCreateTableCommandPrefix() {
|
||||
return createTable;
|
||||
}
|
||||
|
||||
public boolean suppressPrimaryKeyOnPartition() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addTablePartition(DdlBuffer apply, String partitionMode, String partitionColumn) throws IOException {
|
||||
// only supported by postgres initially
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Postgres specific DDL.
|
||||
@@ -16,6 +19,11 @@ public class PostgresDdl extends PlatformDdl {
|
||||
this.columnSetNull = "drop not null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean suppressPrimaryKeyOnPartition() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
return NativeDbArray.logicalToNative(logicalArrayType);
|
||||
@@ -38,4 +46,9 @@ public class PostgresDdl extends PlatformDdl {
|
||||
}
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTablePartition(DdlBuffer apply, String partitionMode, String partitionColumn) throws IOException {
|
||||
apply.append(" partition by range (").append(partitionColumn).append(")");
|
||||
}
|
||||
}
|
||||
|
||||
+15
-21
@@ -62,7 +62,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
String procedureName = procedureName(baseTableName);
|
||||
String triggerName = triggerName(baseTableName);
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryTrigger();
|
||||
apply
|
||||
.append("create trigger ").append(triggerName).newLine()
|
||||
.append(" before update or delete on ").append(baseTableName).newLine()
|
||||
@@ -79,13 +79,21 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
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();
|
||||
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine();
|
||||
|
||||
apply.append("declare").newLine()
|
||||
.append(" lowerTs timestamptz;").newLine()
|
||||
.append(" upperTs timestamptz;").newLine();
|
||||
|
||||
apply.append("begin").newLine()
|
||||
.append(" lowerTs = lower(OLD.sys_period);").newLine()
|
||||
.append(" upperTs = greatest(lowerTs + '1 microsecond',current_timestamp);").newLine();
|
||||
|
||||
apply
|
||||
.append(" if (TG_OP = 'UPDATE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(upperTs,null);").newLine()
|
||||
.append(" return new;").newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
@@ -107,7 +115,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
String historyTable = historyTableName(table.getName());
|
||||
|
||||
List<String> columnNames = columnNamesForApply(table);
|
||||
createOrReplaceFunction(writer.applyHistory(), procedureName, historyTable, columnNames);
|
||||
createOrReplaceFunction(writer.applyHistoryTrigger(), procedureName, historyTable, columnNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,21 +124,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
String procedureName = procedureName(update.getBaseTable());
|
||||
|
||||
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 {
|
||||
|
||||
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();
|
||||
|
||||
// recreate the view with specific columns specified (the columns generally are not dropped until later)
|
||||
createWithHistoryView(update);
|
||||
createOrReplaceFunction(update.historyTriggerBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,7 +132,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("), ").append(currentTimestamp).append("), ");
|
||||
buffer.append(") values (tstzrange(lowerTs,upperTs), ");
|
||||
appendColumnNames(buffer, columns, "OLD.");
|
||||
buffer.append(");").newLine();
|
||||
}
|
||||
|
||||
+43
-22
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.server.persist.platform.MultiValueBind;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -137,13 +139,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
// a rather complex statement.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (DdlHelp.isDropDefault(defaultValue)) {
|
||||
sb.append("delimiter $$\n");
|
||||
sb.append("DECLARE @Tmp nvarchar(200);");
|
||||
sb.append("select @Tmp = t1.name from sys.default_constraints t1\n");
|
||||
sb.append(" join sys.columns t2 on t1.object_id = t2.default_object_id\n");
|
||||
sb.append(" where t1.parent_object_id = OBJECT_ID('").append(tableName)
|
||||
.append("') and t2.name = '").append(columnName).append("';\n");
|
||||
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
|
||||
sb.append("EXEC usp_ebean_drop_default_constraint ").append(tableName).append(", ").append(columnName);
|
||||
} else {
|
||||
sb.append("alter table ").append(tableName);
|
||||
sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
|
||||
@@ -200,25 +196,50 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
|
||||
/**
|
||||
* It is rather complex to delete a column on SqlServer as there must not exist any references
|
||||
* (constraints, default values, indices and foreign keys). The list is not yet complete, as
|
||||
* indices over multiple columns will not yet deleted.
|
||||
* (This may be changed to delete all refering objects by using the sys.* tables later)
|
||||
* (constraints, default values, indices and foreign keys). That's why we call a user stored procedure here
|
||||
*/
|
||||
@Override
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("-- drop column ").append(tableName).append(".").append(columnName).endOfStatement();
|
||||
|
||||
buffer.append(alterTableDropUniqueConstraint(tableName, naming.uniqueConstraintName(tableName, columnName)));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(alterColumnDefaultValue(tableName, columnName, DdlHelp.DROP_DEFAULT));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(alterTableDropConstraint(tableName, naming.checkConstraintName(tableName, columnName)));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(dropIndex(naming.indexName(tableName, columnName), tableName));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(alterTableDropForeignKey(tableName, naming.foreignKeyConstraintName(tableName, columnName)));
|
||||
buffer.endOfStatement();
|
||||
super.alterTableDropColumn(buffer, tableName, columnName);
|
||||
buffer.append("EXEC usp_ebean_drop_column ").append(tableName).append(", ").append(columnName).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* This writes the multi value datatypes needed for {@link MultiValueBind}
|
||||
*/
|
||||
@Override
|
||||
public void generateProlog(DdlWrite write) throws IOException {
|
||||
super.generateProlog(write);
|
||||
|
||||
generateTVPDefinitions(write, "bigint");
|
||||
generateTVPDefinitions(write, "float");
|
||||
generateTVPDefinitions(write, "bit");
|
||||
generateTVPDefinitions(write, "date");
|
||||
generateTVPDefinitions(write, "time");
|
||||
//generateTVPDefinitions(write, "datetime2");
|
||||
generateTVPDefinitions(write, "uniqueidentifier");
|
||||
generateTVPDefinitions(write, "nvarchar(max)");
|
||||
|
||||
}
|
||||
|
||||
private void generateTVPDefinitions(DdlWrite write, String definition) throws IOException {
|
||||
int pos = definition.indexOf('(');
|
||||
String name = pos == -1 ? definition : definition.substring(0, pos);
|
||||
|
||||
dropTVP(write.dropAll(), name);
|
||||
//TVPs are included in "I__create_procs.sql"
|
||||
//createTVP(write.apply(), name, definition);
|
||||
}
|
||||
|
||||
private void dropTVP(DdlBuffer ddl, String name) throws IOException {
|
||||
ddl.append("if exists (select name from sys.types where name = 'ebean_").append(name)
|
||||
.append("_tvp') drop type ebean_").append(name).append("_tvp").endOfStatement();
|
||||
}
|
||||
|
||||
private void createTVP(DdlBuffer ddl, String name, String definition) throws IOException {
|
||||
ddl.append("if not exists (select name from sys.types where name = 'ebean_").append(name)
|
||||
.append("_tvp') create type ebean_").append(name).append("_tvp as table (c1 ").append(definition).append(")")
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-10
@@ -31,19 +31,24 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
enableSystemVersioning(writer, baseTable);
|
||||
}
|
||||
|
||||
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
apply.append("alter table ").append(baseTable).newLine()
|
||||
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
|
||||
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
|
||||
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
private String getHistoryTable(String baseTable) {
|
||||
String historyTable = baseTable + "_history"; // history must contain schema, otherwise you'll get
|
||||
// Setting SYSTEM_VERSIONING to ON failed because history table 'xxx_history' is not specified in two-part name format.
|
||||
if (historyTable.indexOf('.') == -1) {
|
||||
historyTable = "dbo." +historyTable; // so add the default schema, if none was specified.
|
||||
}
|
||||
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=").append(historyTable).append("))").endOfStatement();
|
||||
return historyTable;
|
||||
}
|
||||
|
||||
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
apply.append("alter table ").append(baseTable).newLine()
|
||||
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
|
||||
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
|
||||
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=")
|
||||
.append(getHistoryTable(baseTable)).append("))").endOfStatement();
|
||||
|
||||
DdlBuffer drop = writer.dropAll();
|
||||
drop.append("IF OBJECT_ID('").append(baseTable).append("', 'U') IS NOT NULL alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
|
||||
@@ -53,7 +58,7 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
@Override
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
String baseTable = dropHistoryTable.getBaseTable();
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
|
||||
// drop default constraints
|
||||
|
||||
@@ -76,7 +81,15 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException {
|
||||
public void updateTriggers(DdlWrite writer, HistoryTableUpdate baseTable) throws IOException {
|
||||
// SQL Server 2016 does not need triggers
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
String baseTableName = baseTable.getBaseTable();
|
||||
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = off (history_table=")
|
||||
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
|
||||
apply.append("-- history migration goes here").newLine();
|
||||
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = on (history_table=")
|
||||
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user