mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
202
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f694eb6ca | ||
|
|
539633c963 | ||
|
|
8e05956f6b | ||
|
|
94951a5476 | ||
|
|
2cd769d00f | ||
|
|
389f2bcd2f | ||
|
|
1ba259d15d | ||
|
|
4899f23773 | ||
|
|
771e9ff069 | ||
|
|
7d27c06483 | ||
|
|
d6a8643fe4 | ||
|
|
97c714d945 | ||
|
|
57f6668b37 | ||
|
|
9d506b4397 | ||
|
|
5a12cc58d1 | ||
|
|
f7c83411c2 | ||
|
|
a02fee7de0 | ||
|
|
170e323ee9 | ||
|
|
4555f3da51 | ||
|
|
376b3a9f13 | ||
|
|
43af5343a4 | ||
|
|
886f539fc4 | ||
|
|
1035d74113 | ||
|
|
26a4feb5ba | ||
|
|
a1c0e21234 | ||
|
|
cbe133dd68 | ||
|
|
4b1fe063b0 | ||
|
|
15f1bd310b | ||
|
|
6e3e9bd4f7 | ||
|
|
fddcc64741 | ||
|
|
ed917c55f1 | ||
|
|
17fed0ebd8 | ||
|
|
2dcd17830a | ||
|
|
a412873da6 | ||
|
|
7f5370c709 | ||
|
|
9470a1376c | ||
|
|
7f5b65cf45 | ||
|
|
6d0f5aea82 | ||
|
|
530a75b591 | ||
|
|
13cfaba0c8 | ||
|
|
1fa92efb63 | ||
|
|
bc477c64cb | ||
|
|
804098149e | ||
|
|
567ad92050 | ||
|
|
3a84cb8623 | ||
|
|
b4ddbe38ee | ||
|
|
01722b334b | ||
|
|
0a780523cf | ||
|
|
f2e04d9fbf | ||
|
|
4b8565e7c7 | ||
|
|
1359d534d5 | ||
|
|
94ae513d85 | ||
|
|
d58c56484e | ||
|
|
75a27fa2d6 | ||
|
|
cc1c08cd34 | ||
|
|
029cde12db | ||
|
|
b33914ab2f | ||
|
|
d571a6c354 | ||
|
|
95b19569e4 | ||
|
|
a9560c6664 | ||
|
|
235822bcb2 | ||
|
|
5a024ea19f | ||
|
|
399f8b92fa | ||
|
|
be93263015 | ||
|
|
b4fe4ec8b9 | ||
|
|
8c9f5f4a22 | ||
|
|
953f8054e6 | ||
|
|
aadb791980 | ||
|
|
7430f97c9c | ||
|
|
ba21eeaadb | ||
|
|
c0e827ffda | ||
|
|
3b30708dfc | ||
|
|
9f9184ac6a | ||
|
|
5845f94303 | ||
|
|
dd824ffe52 | ||
|
|
e5d29afa4c | ||
|
|
5ae5e408e3 | ||
|
|
90f6d0707f | ||
|
|
0310601e31 | ||
|
|
015dd00b8e | ||
|
|
6d978add29 | ||
|
|
e529232f76 | ||
|
|
d982ac73b7 | ||
|
|
ddaff81b9c | ||
|
|
1d11666945 | ||
|
|
223e5160dc | ||
|
|
64605b5090 | ||
|
|
425124d8f7 | ||
|
|
6af8968280 | ||
|
|
a9bee03ba7 | ||
|
|
302cc426bd | ||
|
|
44462e3513 | ||
|
|
b748c598a1 | ||
|
|
dbac184b4c | ||
|
|
29c5916203 | ||
|
|
827315a11d | ||
|
|
14ef8f6e0d | ||
|
|
5b63434f13 | ||
|
|
3e8cc87f01 | ||
|
|
190b4ef588 | ||
|
|
4a20c9eb99 | ||
|
|
d8c2b3d583 | ||
|
|
48c8f5e2ca | ||
|
|
69a753fc4f | ||
|
|
b9ba6b2db5 | ||
|
|
7a6071ac9a | ||
|
|
404b167bcc | ||
|
|
7278db56ea | ||
|
|
1dc5ecc81f | ||
|
|
c39661e2c2 | ||
|
|
a702bbb5ee | ||
|
|
1982027300 | ||
|
|
2e1a6eec04 | ||
|
|
e5f37e638c | ||
|
|
7ffea8e922 | ||
|
|
315640df6b | ||
|
|
78a647a72e | ||
|
|
7ffd2f1886 | ||
|
|
0087eabb4e | ||
|
|
5f0731f975 | ||
|
|
85e31899b9 | ||
|
|
3337541ac4 | ||
|
|
b123b06f4d | ||
|
|
17df5a2c16 | ||
|
|
e13aaefee1 | ||
|
|
09ce693da2 | ||
|
|
17f7be72f4 | ||
|
|
23061473c7 | ||
|
|
0944b87283 | ||
|
|
86768dfe4d | ||
|
|
ffdafc796b | ||
|
|
5466c6a350 | ||
|
|
7e0ac5a73a | ||
|
|
008e1849a2 | ||
|
|
dcf60a3d1c | ||
|
|
774cab327d | ||
|
|
2f824c99ea | ||
|
|
1a54fc12ad | ||
|
|
a1d6b280bc | ||
|
|
fa561e5197 | ||
|
|
4a8231a6cc | ||
|
|
5cb486c745 | ||
|
|
dd26dc342d | ||
|
|
7733e9382c | ||
|
|
bc6d2fb3be | ||
|
|
a9dbc036e9 | ||
|
|
8d29a3a575 | ||
|
|
4158ac4665 | ||
|
|
ddab6e65b7 | ||
|
|
bbede04a56 | ||
|
|
9df96b0294 | ||
|
|
6a6b6b8200 | ||
|
|
e26e093680 | ||
|
|
fc107115c6 | ||
|
|
0139ad891e | ||
|
|
4f70c09690 | ||
|
|
df8ae18bf6 | ||
|
|
8edaabf755 | ||
|
|
25eea42211 | ||
|
|
263f6cdeba | ||
|
|
38f1967e60 | ||
|
|
6eb8eb0dd2 | ||
|
|
1165d857d8 | ||
|
|
874c2f5231 | ||
|
|
88ab852855 | ||
|
|
b577331524 | ||
|
|
ec45fc1c86 | ||
|
|
e65876e8ed | ||
|
|
5453037172 | ||
|
|
9b116c9bf6 | ||
|
|
82862deccc | ||
|
|
1c83bbe0b0 | ||
|
|
f16f34f35f | ||
|
|
38bc5b2fea | ||
|
|
356dc5eb19 | ||
|
|
c5779133bc | ||
|
|
b8b6311348 | ||
|
|
8eed983fbb | ||
|
|
7cfecb83a9 | ||
|
|
e92939a34d | ||
|
|
408b982eb3 | ||
|
|
dd7e7711a8 | ||
|
|
5a3f7853a6 | ||
|
|
01847e44de | ||
|
|
0cf9f0bf58 | ||
|
|
b01b44af10 | ||
|
|
8d85000970 | ||
|
|
e7f9c63502 | ||
|
|
eb21b41fba | ||
|
|
3dc6c567a0 | ||
|
|
18eed47306 | ||
|
|
9ddf1333ed | ||
|
|
73896d375f | ||
|
|
3f645211a6 | ||
|
|
860aad11ce | ||
|
|
f830662cce | ||
|
|
84ac737ceb | ||
|
|
483f0398b2 | ||
|
|
c67e66c0ca | ||
|
|
b60f9201a9 | ||
|
|
3fe521c53a | ||
|
|
091b5fbf26 |
@@ -31,6 +31,10 @@ jobs:
|
||||
path:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: Maven version
|
||||
run: mvn --version
|
||||
# - name: Maven single test
|
||||
# run: mvn --batch-mode clean verify -Dtest="io.ebeaninternal.server.core.DefaultServer_getReferenceTest" -DfailIfNoTests=false
|
||||
- name: Build with Maven
|
||||
run: mvn package
|
||||
run: mvn clean package
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
name: DB2 LUW
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '10 8 * * 2,5'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java_version }}
|
||||
distribution: 'adopt'
|
||||
- name: Maven cache
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: maven-cache
|
||||
with:
|
||||
path:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: db2
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-db2.properties
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
name: MariaDB 10.6
|
||||
name: MariaDB
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
+13
-5
@@ -1,5 +1,5 @@
|
||||
|
||||
name: Multi-database platform test
|
||||
name: Multi-database platform
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
@@ -37,9 +37,17 @@ jobs:
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-postgres.properties
|
||||
- name: mysql
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-mysql.properties
|
||||
- name: sqlserver 2019
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-sqlserver19.properties
|
||||
- name: sqlserver 2017
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-sqlserver17.properties
|
||||
- name: mariadb
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-mariadb.properties
|
||||
- name: mariadb
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-mariadb.properties
|
||||
- name: sqlserver
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-sqlserver17.properties
|
||||
- name: yugabyte
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-yugabyte.properties
|
||||
# - name: sqlserver 2019
|
||||
# run: mvn clean test -Dprops.file=testconfig/ebean-sqlserver19.properties
|
||||
# - name: db2
|
||||
# run: mvn clean test -Dprops.file=testconfig/ebean-db2.properties
|
||||
# - name: oracle
|
||||
# run: mvn clean test -Dprops.file=testconfig/ebean-oracle.properties
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
name: Oracle18
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '10 6 * * 1,4'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java_version }}
|
||||
distribution: 'zulu'
|
||||
- name: Maven cache
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: maven-cache
|
||||
with:
|
||||
path:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: oracle
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-oracle.properties
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
name: SqlServer 2019 latest
|
||||
name: SqlServer 2019
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
name: SqlServer 2017 latest
|
||||
name: SqlServer
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
name: Yugabyte
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '10 3 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java_version }}
|
||||
distribution: 'adopt'
|
||||
- name: Maven cache
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: maven-cache
|
||||
with:
|
||||
path:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: yugabyte
|
||||
run: mvn clean test -Dprops.file=testconfig/ebean-yugabyte.properties
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/postgres.yml)
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/mysql.yml)
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/mariadb-10.6.yml)
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/sqlserver-2017-latest.yml)
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/mariadb.yml)
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/sqlserver.yml)
|
||||
|
||||
# Sponsors
|
||||
<table>
|
||||
@@ -49,6 +49,3 @@ Goto [https://ebean.io/docs/](https://ebean.io/docs/)
|
||||
|
||||
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
|
||||
|
||||
## Other versions
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
@@ -49,7 +49,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>7.3</version>
|
||||
<version>7.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -85,7 +85,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.199</version>
|
||||
<version>${h2database.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -23,10 +24,10 @@ public interface ScriptRunner {
|
||||
/**
|
||||
* Run a script given the resource path (that should start with "/").
|
||||
*/
|
||||
void run(String path);
|
||||
void run(String resourcePath);
|
||||
|
||||
/**
|
||||
* Run a script given the resource path (that should start with "/") and place holders.
|
||||
* Run a script given the resource path (that should start with "/") and place-holders.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -38,7 +39,7 @@ public interface ScriptRunner {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void run(String path, Map<String, String> placeholderMap);
|
||||
void run(String resourcePath, Map<String, String> placeholderMap);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the resource.
|
||||
@@ -46,10 +47,20 @@ public interface ScriptRunner {
|
||||
void run(URL resource);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the resource and place holders.
|
||||
* Run a DDL or SQL script given the resource and place-holders.
|
||||
*/
|
||||
void run(URL resource, Map<String, String> placeholderMap);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the file.
|
||||
*/
|
||||
void run(Path file);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the file and place-holders.
|
||||
*/
|
||||
void run(Path file, Map<String, String> placeholderMap);
|
||||
|
||||
/**
|
||||
* Run the raw provided DDL or SQL script.
|
||||
*
|
||||
|
||||
@@ -371,6 +371,33 @@ public class DatabaseConfig {
|
||||
*/
|
||||
private String uuidStateFile;
|
||||
|
||||
/**
|
||||
* The node id (=mac address) for Version 1 UUIDs. There are several options:
|
||||
* <ul>
|
||||
* <li><code>null</code> (default) The generator tries to get the hardwarwe MAC
|
||||
* address. If this fails, it will fall back to 'generate' mode.</li>
|
||||
* <li><code>"generate"</code> Hardware detection is skipped. It generates a
|
||||
* random identifier and tries to persist this to the state file. This nodeId
|
||||
* will be reused on next start. If persisting to the state file will fail also,
|
||||
* it will fall back to 'random' mode.<br>
|
||||
* This mode is good, if the MAC address is not reliable, e.g. if you run
|
||||
* multiple ebean instances on the same machine.</li>
|
||||
* <li><code>"random"</code> In this mode, a random node id is generated on each
|
||||
* start. No stateFile is used. it will generate a new nodeId on each
|
||||
* application start.<br>
|
||||
* This mode is good, if you have no write access to save the state file.</li>
|
||||
* <li><code>"xx-xx-xx-xx-xx-xx"</code> When an explicit nodeId is specified,
|
||||
* this one is used.</li>
|
||||
* </ul>
|
||||
* Note: It is possible that multiple servers are sharing the same state file as
|
||||
* long as they are in the <b>same</b> JVM/ClassLoader scope. In this case it is
|
||||
* recommended to use the same uuidNodeId configuration.
|
||||
*
|
||||
* If you have multiple servers in different JVMs, do <b>not</b> share the state
|
||||
* files!
|
||||
*/
|
||||
private String uuidNodeId;
|
||||
|
||||
/**
|
||||
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
@@ -2019,6 +2046,20 @@ public class DatabaseConfig {
|
||||
public void setUuidStateFile(String uuidStateFile) {
|
||||
this.uuidStateFile = uuidStateFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the V1-UUID-NodeId
|
||||
*/
|
||||
public String getUuidNodeId() {
|
||||
return uuidNodeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the V1-UUID-NodeId.
|
||||
*/
|
||||
public void setUuidNodeId(String uuidNodeId) {
|
||||
this.uuidNodeId = uuidNodeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if LocalTime should be persisted with nanos precision.
|
||||
@@ -2888,6 +2929,7 @@ public class DatabaseConfig {
|
||||
|
||||
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
|
||||
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
|
||||
uuidNodeId = p.get("uuidNodeId", uuidNodeId);
|
||||
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
|
||||
|
||||
@@ -66,6 +66,8 @@ public class PlatformConfig {
|
||||
|
||||
private boolean caseSensitiveCollation = true;
|
||||
|
||||
private boolean useMigrationStoredProcedures = false;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
@@ -90,6 +92,7 @@ public class PlatformConfig {
|
||||
this.geometrySRID = platformConfig.geometrySRID;
|
||||
this.dbUuid = platformConfig.dbUuid;
|
||||
this.caseSensitiveCollation = platformConfig.caseSensitiveCollation;
|
||||
this.useMigrationStoredProcedures = platformConfig.useMigrationStoredProcedures;
|
||||
this.allQuotedIdentifiers = platformConfig.allQuotedIdentifiers;
|
||||
this.databaseInetAddressVarchar = platformConfig.databaseInetAddressVarchar;
|
||||
this.customDbTypeMappings = platformConfig.customDbTypeMappings;
|
||||
@@ -142,6 +145,20 @@ public class PlatformConfig {
|
||||
this.caseSensitiveCollation = caseSensitiveCollation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if force use of helper stored procedures for migrations.
|
||||
*/
|
||||
public boolean isUseMigrationStoredProcedures() {
|
||||
return useMigrationStoredProcedures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set true to force use of helper stored procedures for migrations.
|
||||
*/
|
||||
public void setUseMigrationStoredProcedures(boolean useMigrationStoredProcedures) {
|
||||
this.useMigrationStoredProcedures = useMigrationStoredProcedures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Postgres FOR UPDATE should use the NO KEY option.
|
||||
*/
|
||||
@@ -317,6 +334,7 @@ public class PlatformConfig {
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
databaseInetAddressVarchar = p.getBoolean("databaseInetAddressVarchar", databaseInetAddressVarchar);
|
||||
caseSensitiveCollation = p.getBoolean("caseSensitiveCollation", caseSensitiveCollation);
|
||||
useMigrationStoredProcedures = p.getBoolean("useMigrationStoredProcedures", useMigrationStoredProcedures);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
|
||||
@@ -7,13 +7,10 @@ public class BasicSqlAnsiLimiter implements BasicSqlLimiter {
|
||||
|
||||
@Override
|
||||
public String limit(String dbSql, int firstRow, int maxRows) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
|
||||
sb.append(dbSql);
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append("offset");
|
||||
sb.append(" ").append(firstRow).append(" rows");
|
||||
sb.append(" offset ").append(firstRow).append(" rows");
|
||||
}
|
||||
if (maxRows > 0) {
|
||||
sb.append(" fetch next ").append(maxRows).append(" rows only");
|
||||
|
||||
@@ -15,6 +15,7 @@ import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
@@ -53,6 +54,17 @@ public class DatabasePlatform {
|
||||
|
||||
protected boolean supportsSavepointId = true;
|
||||
|
||||
protected boolean useMigrationStoredProcedures = false;
|
||||
|
||||
/**
|
||||
* Can we use native java time API objects in
|
||||
* {@link ResultSet#getObject(int, Class)} and
|
||||
* {@link PreparedStatement#setObject(int, Object)}.
|
||||
*
|
||||
* Not all drivers (DB2 e.g.) will support this.
|
||||
*/
|
||||
protected boolean supportsNativeJavaTime = true;
|
||||
|
||||
/**
|
||||
* The behaviour used when ending a read only transaction at read committed isolation level.
|
||||
*/
|
||||
@@ -202,6 +214,8 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
|
||||
|
||||
protected int maxInBinding;
|
||||
|
||||
/**
|
||||
* The maximum length of table names - used specifically when derived
|
||||
* default table names for intersection tables.
|
||||
@@ -237,6 +251,7 @@ public class DatabasePlatform {
|
||||
public void configure(PlatformConfig config) {
|
||||
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
|
||||
this.caseSensitiveCollation = config.isCaseSensitiveCollation();
|
||||
this.useMigrationStoredProcedures = config.isUseMigrationStoredProcedures();
|
||||
configureIdType(config.getIdType());
|
||||
configure(config, config.isAllQuotedIdentifiers());
|
||||
}
|
||||
@@ -343,6 +358,13 @@ public class DatabasePlatform {
|
||||
return supportsSavepointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if migrations should use stored procedures.
|
||||
*/
|
||||
public boolean isUseMigrationStoredProcedures() {
|
||||
return useMigrationStoredProcedures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the platform supports LIMIT with sql update.
|
||||
*/
|
||||
@@ -350,6 +372,13 @@ public class DatabasePlatform {
|
||||
return inlineSqlUpdateLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum number of bind values this database platform allows or zero for no limit.
|
||||
*/
|
||||
public int getMaxInBinding() {
|
||||
return maxInBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum table name length.
|
||||
* <p>
|
||||
@@ -573,6 +602,10 @@ public class DatabasePlatform {
|
||||
this.supportsResultSetConcurrencyModeUpdatable = supportsResultSetConcurrencyModeUpdatable;
|
||||
}
|
||||
|
||||
public void setUseMigrationStoredProcedures(final boolean useMigrationStoredProcedures) {
|
||||
this.useMigrationStoredProcedures = useMigrationStoredProcedures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally not needed - overridden in CockroachPlatform.
|
||||
*/
|
||||
@@ -813,4 +846,8 @@ public class DatabasePlatform {
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append(likeEscapeChar).append(ch);
|
||||
}
|
||||
|
||||
public boolean supportsNativeJavaTime() {
|
||||
return supportsNativeJavaTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,39 @@ public class SqlCodeTranslator implements SqlExceptionTranslator {
|
||||
this.map = Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceException translate(String message, SQLException e) {
|
||||
|
||||
private DataErrorType getErrorType(SQLException e) {
|
||||
DataErrorType errorType = map.get(e.getSQLState());
|
||||
if (errorType == null) {
|
||||
// fall back to error code
|
||||
errorType = map.get(String.valueOf(e.getErrorCode()));
|
||||
}
|
||||
return errorType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceException translate(String message, SQLException e) {
|
||||
|
||||
DataErrorType errorType = getErrorType(e);
|
||||
// for DB2 we must inspect the sql exception chain to determine which
|
||||
// persistence error occurred in a batch execution. We also concatenate
|
||||
// the error messages to improve error analysis.
|
||||
SQLException chain = e.getNextException();
|
||||
if (chain != null) {
|
||||
StringBuilder sb = new StringBuilder(message);
|
||||
int i = 1;
|
||||
while (chain != null && i < 100000) { // prevents from endless loop
|
||||
sb.append("\n\t#").append(i++).append(": ").append(chain.getMessage());
|
||||
if (errorType == null) {
|
||||
errorType = getErrorType(chain);
|
||||
if (errorType != null) {
|
||||
sb.append(" (causing error)"); // mark the line, where we found a matching error code
|
||||
}
|
||||
}
|
||||
chain = chain.getNextException();
|
||||
}
|
||||
message = sb.toString();
|
||||
}
|
||||
|
||||
if (errorType != null) {
|
||||
switch (errorType) {
|
||||
case AcquireLock:
|
||||
|
||||
+5
-8
@@ -15,21 +15,19 @@ import java.sql.Types;
|
||||
/**
|
||||
* DB2 specific platform.
|
||||
*/
|
||||
public class DB2Platform extends DatabasePlatform {
|
||||
public abstract class BaseDB2Platform extends DatabasePlatform {
|
||||
|
||||
public DB2Platform() {
|
||||
public BaseDB2Platform() {
|
||||
super();
|
||||
this.platform = Platform.DB2;
|
||||
// Note: DB2 (at least LUW supports length up to 128)
|
||||
// TOOD: Check if we need to introduce a new platform (DB2_LUW_11 ?)
|
||||
this.maxTableNameLength = 18;
|
||||
this.maxConstraintNameLength = 18;
|
||||
this.supportsNativeJavaTime = false;
|
||||
this.truncateTable = "truncate table %s reuse storage ignore delete triggers immediate";
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.sqlLimiter = new Db2SqlLimiter();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.idInExpandedForm = true; // Db2 does not support (a,b) in ((?,?),(?,?))
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("40001","57033") // key -911/-913
|
||||
@@ -45,7 +43,6 @@ public class DB2Platform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
|
||||
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
|
||||
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("decimal", 16, 3));
|
||||
persistBatchOnCascade = PersistBatch.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* DB2 specific platform for i Series.
|
||||
*
|
||||
* @author Cédric Sougné
|
||||
*/
|
||||
public class DB2ForIPlatform extends BaseDB2Platform {
|
||||
|
||||
public DB2ForIPlatform() {
|
||||
super();
|
||||
this.platform = Platform.DB2FORI;
|
||||
// Note: IBM i from 7.1 allow up to to 128
|
||||
// TODO: Check if we need to introduce older platform (DB2ForI_6 ? but older
|
||||
// documentation is not anymore published on ibm.com),
|
||||
this.maxTableNameLength = 128;
|
||||
this.maxConstraintNameLength = 128;
|
||||
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
this.exceptionTranslator = new SqlErrorCodes().addAcquireLock("57033") // key -913
|
||||
.addDuplicateKey("23505") // -803
|
||||
// .addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667")
|
||||
// we need SQLState, not code:
|
||||
// https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/codes/src/tpc/db2z_n.html
|
||||
.addDataIntegrity("23502", "23503", "23504", "23507", "23511", "23512", "23513", "42917", "23515")
|
||||
.build();
|
||||
|
||||
booleanDbType = Types.SMALLINT;
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("smallint default 0"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
/**
|
||||
* DB2 specific platform for older DB2 versions. This platform is here for
|
||||
* compatibility reasons. It uses a length limit of 18 chars for table and
|
||||
* constraint names. Newer DB2 versions will support up to 128. It is strongly
|
||||
* recommended to migrate to db2luw/DB2ForI or db2zos platform.
|
||||
*/
|
||||
public class DB2LegacyPlatform extends BaseDB2Platform {
|
||||
public DB2LegacyPlatform() {
|
||||
super();
|
||||
this.platform = Platform.DB2;
|
||||
// Note: DB2 (at least LUW supports length up to 128)
|
||||
// TOOD: Check if we need to introduce a new platform (DB2_LUW_11 ?)
|
||||
this.maxTableNameLength = 18;
|
||||
this.maxConstraintNameLength = 18;
|
||||
this.persistBatchOnCascade = PersistBatch.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
/**
|
||||
* DB2 platform for Linux/Unix/Windows.
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public class DB2LuwPlatform extends BaseDB2Platform {
|
||||
public DB2LuwPlatform() {
|
||||
super();
|
||||
this.platform = Platform.DB2LUW;
|
||||
this.maxTableNameLength = 128;
|
||||
this.maxConstraintNameLength = 128;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class DB2SequenceIdGenerator extends SequenceBatchIdGenerator {
|
||||
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
super(be, ds, seqName, batchSize);
|
||||
this.baseSql = "values nextval for " + seqName;
|
||||
this.unionBaseSql = " union " + baseSql;
|
||||
this.unionBaseSql = ", nextval for " + seqName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
/**
|
||||
* DB2 platform for z/OS.
|
||||
* Note: This platform is currently not tested!
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public class DB2ZosPlatform extends BaseDB2Platform {
|
||||
public DB2ZosPlatform() {
|
||||
super();
|
||||
this.platform = Platform.DB2ZOS;
|
||||
this.maxTableNameLength = 128;
|
||||
this.maxConstraintNameLength = 128;
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,13 @@ public class Db2SqlLimiter implements SqlLimiter {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
sb.append(request.getDbSql());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
if (firstRow > 0) {
|
||||
sb.append(" offset ").append(firstRow).append(" rows");
|
||||
}
|
||||
int maxRows = request.getMaxRows();
|
||||
if (maxRows > 0) {
|
||||
sb.append(" ").append(NEW_LINE).append("FETCH FIRST ").append(maxRows).append(" ROWS ONLY");
|
||||
sb.append(" fetch next ").append(maxRows).append(" rows only");
|
||||
}
|
||||
|
||||
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
|
||||
|
||||
@@ -39,6 +39,7 @@ public class H2Platform extends DatabasePlatform {
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
dbTypeMap.put(DbType.UUID, new DbPlatformType("uuid", false));
|
||||
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,13 +18,12 @@ public class H2SequenceIdGenerator extends SequenceBatchIdGenerator {
|
||||
*/
|
||||
public H2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
super(be, ds, seqName, batchSize);
|
||||
this.baseSql = "select " + seqName + ".nextval";
|
||||
this.baseSql = "select nextval('" + seqName + "')";
|
||||
this.unionBaseSql = " union " + baseSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql(int batchSize) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(baseSql);
|
||||
for (int i = 1; i < batchSize; i++) {
|
||||
|
||||
@@ -11,6 +11,7 @@ public class Oracle11Platform extends OraclePlatform {
|
||||
public Oracle11Platform() {
|
||||
super();
|
||||
this.platform = Platform.ORACLE11;
|
||||
this.columnAliasPrefix = "c";
|
||||
this.sqlLimiter = new OracleRownumSqlLimiter();
|
||||
this.basicSqlLimiter = new OracleRownumBasicLimiter();
|
||||
dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ebean.config.dbplatform.oracle;
|
||||
|
||||
/**
|
||||
* Oracle 12 platform using column alias.
|
||||
*/
|
||||
public class Oracle12Platform extends OraclePlatform {
|
||||
|
||||
public Oracle12Platform() {
|
||||
super();
|
||||
//this.platform = Platform.ORACLE12;
|
||||
this.columnAliasPrefix = "c";
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
public OraclePlatform() {
|
||||
super();
|
||||
this.platform = Platform.ORACLE;
|
||||
this.columnAliasPrefix = "c";
|
||||
this.supportsDeleteTableAlias = true;
|
||||
this.maxInBinding = 1000;
|
||||
this.maxTableNameLength = 30;
|
||||
this.maxConstraintNameLength = 30;
|
||||
this.dbEncrypt = new OracleDbEncrypt();
|
||||
@@ -35,11 +35,9 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
dbIdentity.setSupportsSequence(true);
|
||||
dbIdentity.setSupportsIdentity(true);
|
||||
dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
|
||||
this.dbDefaultValue.setFalse("0");
|
||||
this.dbDefaultValue.setTrue("1");
|
||||
this.dbDefaultValue.setNow("current_timestamp");
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
this.likeClauseRaw = "like ?";
|
||||
|
||||
@@ -56,7 +54,6 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("number(1)"));
|
||||
|
||||
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("number", 10));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("number", 19));
|
||||
dbTypeMap.put(DbType.REAL, new DbPlatformType("number", 19, 4));
|
||||
@@ -65,12 +62,10 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("number", 3));
|
||||
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("number", 16, 3));
|
||||
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("varchar2", 255));
|
||||
|
||||
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("blob"));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("clob"));
|
||||
dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("raw", 255));
|
||||
dbTypeMap.put(DbType.BINARY, new DbPlatformType("raw", 255));
|
||||
|
||||
dbTypeMap.put(DbType.TIME, new DbPlatformType("timestamp"));
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -27,6 +27,7 @@ abstract class SqlServerBasePlatform extends DatabasePlatform {
|
||||
// SQL Server unless we are using sequences
|
||||
this.dbEncrypt = new SqlServerDbEncrypt();
|
||||
this.persistBatchOnCascade = PersistBatch.NONE;
|
||||
this.maxInBinding = 2000;
|
||||
this.idInExpandedForm = true;
|
||||
this.selectCountWithAlias = true;
|
||||
this.selectCountWithColumnAlias = true;
|
||||
@@ -119,4 +120,9 @@ abstract class SqlServerBasePlatform extends DatabasePlatform {
|
||||
// for update are hints on from clause of base table
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseMigrationStoredProcedures() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.config.dbplatform.yugabyte;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
|
||||
public class YugabytePlaform extends PostgresPlatform {
|
||||
|
||||
public YugabytePlaform() {
|
||||
super();
|
||||
this.platform = Platform.YUGABYTE;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import java.util.Locale;
|
||||
* try {
|
||||
* File f = new File("src/test/resources/test1.csv");
|
||||
*
|
||||
* FileReader reader = new FileReader(f);
|
||||
* FileReader reader = new FileReader(f, encoding);
|
||||
*
|
||||
* CsvReader<Customer> csvReader = DB.createCsvReader(Customer.class);
|
||||
*
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ebean.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Utilities for IO. It uses UTF-8 as encoding when reading/writing and uses
|
||||
* buffered IO for better performance.
|
||||
*/
|
||||
public class IOUtils {
|
||||
|
||||
/**
|
||||
* Read from stream as UTF-8.
|
||||
*/
|
||||
public static BufferedReader newReader(InputStream is) {
|
||||
return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from file as UTF-8.
|
||||
*/
|
||||
public static BufferedReader newReader(File file) throws FileNotFoundException {
|
||||
return newReader(new FileInputStream(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to stream as UTF-8
|
||||
*/
|
||||
public static BufferedWriter newWriter(OutputStream os) {
|
||||
return new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to file as UTF-8
|
||||
*/
|
||||
public static BufferedWriter newWriter(File file) throws FileNotFoundException {
|
||||
return newWriter(new FileOutputStream(file));
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
<!-- <parent>-->
|
||||
<!-- <groupId>org.avaje</groupId>-->
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.14.0</tag>
|
||||
<tag>ebean-parent-12.15.0</tag>
|
||||
</scm>
|
||||
|
||||
<name>ebean autotune</name>
|
||||
@@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<tiles>
|
||||
<tile>io.ebean.tile:enhancement:12.11.3</tile>
|
||||
<tile>io.ebean.tile:enhancement:12.15.0</tile>
|
||||
<tile>io.avaje.tile:moditech-module:1.0</tile>
|
||||
</tiles>
|
||||
</configuration>
|
||||
|
||||
+15
-15
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -71,88 +71,88 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-xml</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-autotune</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -47,6 +47,8 @@ public interface DataReader {
|
||||
|
||||
Object getObject() throws SQLException;
|
||||
|
||||
<T> T getObject(Class<T> cls) throws SQLException;
|
||||
|
||||
InputStream getBinaryStream() throws SQLException;
|
||||
|
||||
/**
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.14.0</tag>
|
||||
<tag>ebean-parent-12.15.0</tag>
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
@@ -41,19 +41,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -78,7 +78,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.199</version>
|
||||
<version>${h2database.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.2.24</version>
|
||||
<version>42.3.2</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ public final class BindParams implements Serializable {
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void setParameter(int position, Object value) {
|
||||
assert value != null : "use setNullParameter";
|
||||
Param p = getParam(position);
|
||||
if (value instanceof Collection) {
|
||||
// use of postgres ANY with positioned parameter
|
||||
@@ -218,6 +219,7 @@ public final class BindParams implements Serializable {
|
||||
* Set a named In parameter that is not null.
|
||||
*/
|
||||
public Param setParameter(String name, Object value) {
|
||||
assert value != null : "use setNullParameter";
|
||||
Param p = getParam(name);
|
||||
p.setInValue(value);
|
||||
return p;
|
||||
|
||||
@@ -3,20 +3,20 @@ package io.ebeaninternal.server.core;
|
||||
import io.ebean.ScriptRunner;
|
||||
import io.ebean.ddlrunner.DdlRunner;
|
||||
import io.ebean.ddlrunner.ScriptTransform;
|
||||
import io.ebean.util.IOUtils;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.util.UrlHelper;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.Reader;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
final class DScriptRunner implements ScriptRunner {
|
||||
|
||||
private static final String NEWLINE = "\n";
|
||||
@@ -30,8 +30,8 @@ final class DScriptRunner implements ScriptRunner {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String path) {
|
||||
run(path, null);
|
||||
public void run(String resourcePath) {
|
||||
run(resourcePath, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,26 +49,42 @@ final class DScriptRunner implements ScriptRunner {
|
||||
run(resource, null, placeholderMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Path file) {
|
||||
run(file, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(Path file, Map<String, String> placeholderMap) {
|
||||
requireNonNull(file);
|
||||
String scriptName = file.toFile().getName();
|
||||
String content = fileContent(file);
|
||||
runScript(content, scriptName, placeholderMap, false);
|
||||
}
|
||||
|
||||
private void run(URL resource, String scriptName, Map<String, String> placeholderMap) {
|
||||
if (resource == null) {
|
||||
throw new IllegalArgumentException("resource is null?");
|
||||
}
|
||||
requireNonNull(resource);
|
||||
if (scriptName == null) {
|
||||
scriptName = resource.getFile();
|
||||
}
|
||||
|
||||
String content = content(resource);
|
||||
runScript(content, scriptName, placeholderMap, false);
|
||||
}
|
||||
|
||||
private String content(URL resource) {
|
||||
if (resource == null) {
|
||||
throw new IllegalArgumentException("resource is null?");
|
||||
private String fileContent(Path file) {
|
||||
try (InputStream inputStream = new FileInputStream(file.toFile());
|
||||
Reader reader = IOUtils.newReader(inputStream)) {
|
||||
return readContent(reader);
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException("Failed to read script content", e);
|
||||
}
|
||||
}
|
||||
|
||||
try (InputStream inputStream = UrlHelper.openNoCache(resource)) {
|
||||
return readContent(new InputStreamReader(inputStream));
|
||||
|
||||
private String content(URL resource) {
|
||||
requireNonNull(resource);
|
||||
try (InputStream inputStream = UrlHelper.openNoCache(resource);
|
||||
Reader reader = IOUtils.newReader(inputStream)) {
|
||||
return readContent(reader);
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException("Failed to read script content", e);
|
||||
}
|
||||
@@ -84,10 +100,9 @@ final class DScriptRunner implements ScriptRunner {
|
||||
*/
|
||||
private void runScript(String content, String scriptName, Map<String, String> placeholderMap, boolean useAutoCommit) {
|
||||
try {
|
||||
if (placeholderMap != null) {
|
||||
if (placeholderMap != null && !placeholderMap.isEmpty()) {
|
||||
content = ScriptTransform.build(null, placeholderMap).transform(content);
|
||||
}
|
||||
|
||||
try (Connection connection = obtainConnection()) {
|
||||
DdlRunner runner = new DdlRunner(useAutoCommit, scriptName, platformName);
|
||||
runner.runAll(content, connection);
|
||||
@@ -109,7 +124,6 @@ final class DScriptRunner implements ScriptRunner {
|
||||
}
|
||||
|
||||
private String readContent(Reader reader) throws IOException {
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
try (LineNumberReader lineReader = new LineNumberReader(reader)) {
|
||||
String line;
|
||||
|
||||
+39
-12
@@ -4,7 +4,10 @@ import io.ebean.config.DatabaseConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform;
|
||||
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.db2.DB2ForIPlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2LegacyPlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2LuwPlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2ZosPlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hana.HanaPlatform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
@@ -13,6 +16,7 @@ import io.ebean.config.dbplatform.mysql.MySql55Platform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.nuodb.NuoDbPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.Oracle11Platform;
|
||||
import io.ebean.config.dbplatform.oracle.Oracle12Platform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.Postgres9Platform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
@@ -20,16 +24,14 @@ import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
|
||||
import io.ebean.config.dbplatform.yugabyte.YugabytePlaform;
|
||||
import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.api.DbOffline;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.*;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Create a DatabasePlatform from the configuration.
|
||||
@@ -87,6 +89,9 @@ public class DatabasePlatformFactory {
|
||||
if (dbName.equals("oracle11") || dbName.equals("oracle10") || dbName.equals("oracle9")) {
|
||||
return new Oracle11Platform();
|
||||
}
|
||||
if (dbName.equals("oracle12")) {
|
||||
return new Oracle12Platform();
|
||||
}
|
||||
if (dbName.equals("oracle")) {
|
||||
return new OraclePlatform();
|
||||
}
|
||||
@@ -103,7 +108,19 @@ public class DatabasePlatformFactory {
|
||||
return new SqlAnywherePlatform();
|
||||
}
|
||||
if (dbName.equals("db2")) {
|
||||
return new DB2Platform();
|
||||
throw new IllegalArgumentException("Please choose the more specific db2luw/db2zos/db2fori platform. Refer to issue #2514 for details");
|
||||
}
|
||||
if (dbName.equals("db2legacy")) {
|
||||
return new DB2LegacyPlatform();
|
||||
}
|
||||
if (dbName.equals("db2zos")) {
|
||||
return new DB2ZosPlatform();
|
||||
}
|
||||
if (dbName.equals("db2fori")) {
|
||||
return new DB2ForIPlatform();
|
||||
}
|
||||
if (dbName.equals("db2luw")) {
|
||||
return new DB2LuwPlatform();
|
||||
}
|
||||
if (dbName.equals("clickhouse")) {
|
||||
return new ClickHousePlatform();
|
||||
@@ -149,7 +166,8 @@ public class DatabasePlatformFactory {
|
||||
} else if (dbProductName.contains("hsql database engine")) {
|
||||
return new HsqldbPlatform();
|
||||
} else if (dbProductName.contains("postgres")) {
|
||||
return readPostgres(connection, majorVersion);
|
||||
String productVersion = metaData.getDatabaseProductVersion().toLowerCase(Locale.ENGLISH);
|
||||
return readPostgres(connection, majorVersion, productVersion);
|
||||
} else if (dbProductName.contains("mariadb")) {
|
||||
return new MariaDbPlatform();
|
||||
} else if (dbProductName.contains("mysql")) {
|
||||
@@ -159,7 +177,7 @@ public class DatabasePlatformFactory {
|
||||
} else if (dbProductName.contains("sqlite")) {
|
||||
return new SQLitePlatform();
|
||||
} else if (dbProductName.contains("db2")) {
|
||||
return new DB2Platform();
|
||||
throw new IllegalArgumentException("For DB2 please explicitly choose either db2legacy/db2luw/db2zos/db2fori platform. Refer to issue #2514 for details");
|
||||
} else if (dbProductName.contains("sql anywhere")) {
|
||||
return new SqlAnywherePlatform();
|
||||
} else if (dbProductName.contains("hdb")) {
|
||||
@@ -173,7 +191,13 @@ public class DatabasePlatformFactory {
|
||||
}
|
||||
|
||||
private DatabasePlatform oracleVersion(int majorVersion) {
|
||||
return majorVersion < 12 ? new Oracle11Platform() : new OraclePlatform();
|
||||
if (majorVersion < 12) {
|
||||
return new Oracle11Platform();
|
||||
}
|
||||
if (majorVersion < 13) {
|
||||
return new Oracle12Platform();
|
||||
}
|
||||
return new OraclePlatform();
|
||||
}
|
||||
|
||||
private DatabasePlatform mysqlVersion(int majorVersion, int minorVersion) {
|
||||
@@ -186,11 +210,14 @@ public class DatabasePlatformFactory {
|
||||
/**
|
||||
* Use a select version() query as it could be Postgres or CockroachDB.
|
||||
*/
|
||||
private static DatabasePlatform readPostgres(Connection connection, int majorVersion) {
|
||||
private static DatabasePlatform readPostgres(Connection connection, int majorVersion, String productVersion) {
|
||||
if (productVersion.contains("-yb-")) {
|
||||
return new YugabytePlaform();
|
||||
}
|
||||
try (PreparedStatement statement = connection.prepareStatement("select version() as \"version\"")) {
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
if (resultSet.next()) {
|
||||
String productVersion = resultSet.getString("version").toLowerCase();
|
||||
productVersion = resultSet.getString("version").toLowerCase();
|
||||
if (productVersion.contains("cockroach")) {
|
||||
return new CockroachPlatform();
|
||||
}
|
||||
|
||||
@@ -613,6 +613,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
try (Connection connection = dataSource().getConnection()) {
|
||||
for (String table : tables) {
|
||||
executeSql(connection, databasePlatform.truncateStatement(table));
|
||||
if (databasePlatform.getPlatform().base() == Platform.DB2) {
|
||||
// DB2 requires commit after each truncate statement
|
||||
connection.commit();
|
||||
}
|
||||
}
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
|
||||
@@ -99,7 +99,6 @@ final class InitDataSource {
|
||||
if (config.getDatabasePlatformName() == null) {
|
||||
throw new PersistenceException("You MUST specify a DatabasePlatformName on DatabaseConfig when offline");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
attachAlert(dsConfig);
|
||||
|
||||
@@ -280,7 +280,7 @@ public final class InternalConfiguration {
|
||||
|
||||
private MultiValueBind createMultiValueBind(Platform platform) {
|
||||
// only Postgres at this stage
|
||||
if (platform.base() == Platform.POSTGRES) {
|
||||
if (platform.base() == Platform.POSTGRES || platform.base() == Platform.YUGABYTE) {
|
||||
return new PostgresMultiValueBind();
|
||||
}
|
||||
return new MultiValueBind();
|
||||
|
||||
@@ -40,6 +40,9 @@ public final class PersistDeferredRelationship {
|
||||
|
||||
// bind the set clause for the importedId
|
||||
int pos = importedId.bind(1, sqlUpdate, assocBean);
|
||||
if (pos == -1) {
|
||||
return; // could not bind: TODO: should we log/throw an error?
|
||||
}
|
||||
// bind the where clause for the bean
|
||||
Object[] idValues = beanDescriptor.idBinder().getIdValues(bean);
|
||||
for (int j = 0; j < idValues.length; j++) {
|
||||
|
||||
@@ -86,6 +86,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
private final ConcurrentHashMap<String, ElPropertyDeploy> elDeployCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, STreeProperty> dynamicProperty = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Map<String,String>> pathMaps = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, SpiRawSql> namedRawSql;
|
||||
private final Map<String, String> namedQuery;
|
||||
private final boolean multiValueSupported;
|
||||
@@ -2775,6 +2777,22 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
return draftableElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> pathMap(String prefix) {
|
||||
return pathMaps.computeIfAbsent(prefix, s -> {
|
||||
HashMap<String, String> m = new HashMap<>();
|
||||
for (STreePropertyAssocMany many : propsMany()) {
|
||||
String name = many.name();
|
||||
m.put(name, prefix + "." + name);
|
||||
}
|
||||
for (STreePropertyAssocOne one : propsOne()) {
|
||||
String name = one.name();
|
||||
m.put(name, prefix + "." + name);
|
||||
}
|
||||
return m.isEmpty() ? Collections.emptyMap() : m;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmbeddedPath(String propertyPath) {
|
||||
ElPropertyDeploy elProp = elPropertyDeploy(propertyPath);
|
||||
|
||||
@@ -1160,7 +1160,26 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
public boolean isLob() {
|
||||
return lob;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if this <code>isLob()</code> or the type will effectively map to a lob.
|
||||
*/
|
||||
public boolean isDbLob() {
|
||||
if (lob) {
|
||||
return true;
|
||||
}
|
||||
switch (dbType) {
|
||||
case DbPlatformType.JSON:
|
||||
case DbPlatformType.JSONB:
|
||||
return dbLength == 0; // must be analog to DbPlatformTypeMapping.lookup
|
||||
case DbPlatformType.JSONBlob:
|
||||
case DbPlatformType.JSONClob:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLobType(int type) {
|
||||
switch (type) {
|
||||
case Types.CLOB:
|
||||
|
||||
@@ -226,7 +226,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
*/
|
||||
@Override
|
||||
public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean, boolean withCheck) {
|
||||
BeanCollection<?> bc = (BeanCollection<?>) super.getValue(parentBean);
|
||||
BeanCollection<?> bc = beanCollection(parentBean);
|
||||
if (bc == null) {
|
||||
bc = help.createEmpty(parentBean);
|
||||
setValue(parentBean, bc);
|
||||
@@ -234,6 +234,15 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
help.add(bc, detailBean, withCheck);
|
||||
}
|
||||
|
||||
private BeanCollection<?> beanCollection(EntityBean parentBean) {
|
||||
try {
|
||||
return (BeanCollection<?>) super.getValue(parentBean);
|
||||
} catch (ClassCastException e) {
|
||||
// fetching element collection, ok for now
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is considered 'empty' from a save perspective.
|
||||
*/
|
||||
|
||||
+5
@@ -62,4 +62,9 @@ class DynamicPropertyAggregationFormula extends DynamicPropertyBase {
|
||||
ctx.appendParseSelect(parsedFormula, alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDbLob() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@ public final class ImportedIdEmbedded implements ImportedId {
|
||||
for (ImportedIdSimple anImported : imported) {
|
||||
if (anImported.owner.isUpdateable()) {
|
||||
Object scalarValue = anImported.foreignProperty.getValue(embedded);
|
||||
if (scalarValue == null) {
|
||||
return -1; // could not bind
|
||||
}
|
||||
update.setParameter(pos++, scalarValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,9 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
|
||||
@Override
|
||||
public int bind(int position, SqlUpdate update, EntityBean bean) {
|
||||
Object value = getIdValue(bean);
|
||||
if (value == null) {
|
||||
return -1; // could not bind
|
||||
}
|
||||
update.setParameter(position, value);
|
||||
return ++position;
|
||||
}
|
||||
|
||||
+1
-1
@@ -775,7 +775,7 @@ public class DeployBeanDescriptor<T> {
|
||||
this.identityMode.setIdType(IdType.EXTERNAL);
|
||||
switch (config.getUuidVersion()) {
|
||||
case VERSION1:
|
||||
this.idGenerator = UuidV1IdGenerator.getInstance(config.getUuidStateFile());
|
||||
this.idGenerator = UuidV1IdGenerator.getInstance(config.getUuidStateFile(), config.getUuidNodeId());
|
||||
break;
|
||||
case VERSION1RND:
|
||||
this.idGenerator = UuidV1RndIdGenerator.INSTANCE;
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.InheritInfo;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
|
||||
import javax.persistence.DiscriminatorColumn;
|
||||
import javax.persistence.DiscriminatorType;
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Inheritance;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -105,8 +106,11 @@ public final class DeployInherit {
|
||||
if (da != null) {
|
||||
// lowercase the discriminator column for RawSql and JSON
|
||||
info.setColumnName(da.name().toLowerCase());
|
||||
info.setColumnType(da.discriminatorType());
|
||||
info.setColumnLength(da.length());
|
||||
DiscriminatorType discriminatorType = da.discriminatorType();
|
||||
info.setColumnType(discriminatorType);
|
||||
if (discriminatorType == DiscriminatorType.STRING) {
|
||||
info.setColumnLength(da.length());
|
||||
}
|
||||
info.setColumnDefn(da.columnDefinition());
|
||||
}
|
||||
if (!info.isAbstract()) {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.ebeaninternal.server.expression.platform;
|
||||
|
||||
/**
|
||||
* DB2 handling of platform specific expressions. ARRAY expressions not supported.
|
||||
*/
|
||||
final class Db2DbExpression extends BasicDbExpression {
|
||||
|
||||
@Override
|
||||
public String concat(String property0, String separator, String property1, String suffix) {
|
||||
return concatOperator(property0, separator, property1, suffix);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-6
@@ -10,23 +10,22 @@ public final class DbExpressionHandlerFactory {
|
||||
* Create and return the appropriate platform specific handing of expressions.
|
||||
*/
|
||||
public static DbExpressionHandler from(DatabasePlatform databasePlatform) {
|
||||
Platform platform = databasePlatform.getPlatform();
|
||||
Platform platform = databasePlatform.getPlatform().base();
|
||||
switch (platform) {
|
||||
case H2:
|
||||
return new H2DbExpression();
|
||||
case POSTGRES:
|
||||
case POSTGRES9:
|
||||
case YUGABYTE:
|
||||
case COCKROACH:
|
||||
return new PostgresDbExpression();
|
||||
case MARIADB:
|
||||
return new MariaDbExpression();
|
||||
case MYSQL55:
|
||||
case MYSQL:
|
||||
return new MySqlDbExpression();
|
||||
case ORACLE:
|
||||
case ORACLE11:
|
||||
return new OracleDbExpression();
|
||||
case SQLSERVER16:
|
||||
case SQLSERVER17:
|
||||
case DB2:
|
||||
return new Db2DbExpression();
|
||||
case SQLSERVER:
|
||||
return new SqlServerDbExpression();
|
||||
case HANA:
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ final class H2DbExpression extends BasicDbExpression {
|
||||
|
||||
@Override
|
||||
public void bitwise(SpiExpressionRequest request, String propName, BitwiseOp operator, long flags, String compare, long match) {
|
||||
bitwiseFunction(request, propName, operator, compare);
|
||||
String funcName = functionName(operator);
|
||||
request.append(funcName).append("(").append(propName).append(", cast(? as long)) ").append(compare).append(" cast(? as long)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* It extends the UuidV1RndIdGenerator so that it can generate rfc4122 compliant Type 1 UUIDs.
|
||||
*
|
||||
* This generator produces real Type 1 UUIDs (best for sqlserver) - You should use this generator only,
|
||||
* if you can guarantee, that mac addess is uniqe.
|
||||
* if you can guarantee, that mac addess is uniqe or specify 'ebean.uuidNodeId' in your config file.
|
||||
*
|
||||
*/
|
||||
public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
|
||||
|
||||
@@ -25,38 +26,36 @@ public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
|
||||
/**
|
||||
* Returns an instance for given file.
|
||||
*/
|
||||
public static UuidV1IdGenerator getInstance(String file) {
|
||||
return INSTANCES.computeIfAbsent(new File(file), UuidV1IdGenerator::new);
|
||||
public static UuidV1IdGenerator getInstance(String file, String nodeId) {
|
||||
return getInstance(new File(file), nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance for given file.
|
||||
*/
|
||||
public static UuidV1IdGenerator getInstance(File file) {
|
||||
return INSTANCES.computeIfAbsent(file, UuidV1IdGenerator::new);
|
||||
public static UuidV1IdGenerator getInstance(File file, String nodeId) {
|
||||
return INSTANCES.computeIfAbsent(file, f ->new UuidV1IdGenerator(f, nodeId == null ? null : nodeId.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an alternative node id - set with the 'ebean.uuid.nodeId' system property.
|
||||
*/
|
||||
private static byte[] getAlternativeNodeId() {
|
||||
private static byte[] parseAlternativeNodeId(String altNodeId) {
|
||||
String[] components = altNodeId.split("-");
|
||||
if (components.length != 6) {
|
||||
throw new IllegalArgumentException(altNodeId + " is invalid. Expected format: xx-xx-xx-xx-xx-xx");
|
||||
}
|
||||
try {
|
||||
String altNodeId = System.getProperty("ebean.uuid.nodeId");
|
||||
if (altNodeId != null) {
|
||||
String[] components = altNodeId.split("-");
|
||||
if (components.length != 5) {
|
||||
throw new IllegalArgumentException("Invalid nodeId string: " + altNodeId);
|
||||
}
|
||||
byte[] nodeId = new byte[6];
|
||||
for (int i=0; i<5; i++) {
|
||||
nodeId[i] = Byte.decode("0x"+components[i]).byteValue();
|
||||
}
|
||||
return nodeId;
|
||||
byte[] nodeId = new byte[6];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
// do not use Byte.parseByte
|
||||
// https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6259307
|
||||
nodeId[i] = (byte) Integer.parseInt(components[i], 16);
|
||||
}
|
||||
} catch (SecurityException se) {
|
||||
// ignore
|
||||
return nodeId;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new IllegalArgumentException(altNodeId + " is invalid.", iae);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,43 +107,85 @@ public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
|
||||
* Creates a new instance of UuidGenerator. Note that there should not be more
|
||||
* than one instance per stateFile.
|
||||
*/
|
||||
private UuidV1IdGenerator(final File stateFile) {
|
||||
private UuidV1IdGenerator(final File stateFile, String altNodeId) {
|
||||
super();
|
||||
this.stateFile = stateFile;
|
||||
try {
|
||||
// See, if there is an alternative MAC address set.
|
||||
nodeId = getAlternativeNodeId();
|
||||
if (nodeId != null) {
|
||||
log.info("Using alternative MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
if (altNodeId == null) {
|
||||
// using hardware mode
|
||||
tryHardwareId();
|
||||
} else if (altNodeId.equals("generate")) {
|
||||
tryGenerateMode();
|
||||
} else if (altNodeId.equals("random")) {
|
||||
useRandomMode();
|
||||
} else {
|
||||
nodeId = getHardwareId();
|
||||
log.info("Using MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
// See, if there is an alternative MAC address set.
|
||||
nodeId = parseAlternativeNodeId(altNodeId);
|
||||
restoreState();
|
||||
log.info("Explicitly using ID {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
}
|
||||
if (nodeId == null) {
|
||||
canSaveState = false;
|
||||
// RFC 4.5 use random portion for node
|
||||
nodeId = super.getNodeIdBytes();
|
||||
log.error("Have to fall back to random node identifier {} (Reason: No suitable network interface found)", getNodeIdentifier());
|
||||
UUID uuid = nextId(null);
|
||||
long ts = timeStamp.get();
|
||||
ts -= UUID_EPOCH_OFFSET;
|
||||
ts /= MILLIS_TO_UUID;
|
||||
|
||||
} else {
|
||||
boolean flag = restoreState();
|
||||
UUID uuid = nextId(null);
|
||||
long ts = timeStamp.get();
|
||||
ts -= UUID_EPOCH_OFFSET;
|
||||
ts /= MILLIS_TO_UUID;
|
||||
|
||||
saveState();
|
||||
log.debug("RestoreState: {}, ClockSeq {}, Timestamp {}, uuid {}, stateFile: {})", flag, clockSeq.get(),
|
||||
new Date(ts), uuid, stateFile);
|
||||
}
|
||||
saveState();
|
||||
log.debug("Saved state: clockSeq {}, timestamp {}, uuid {}, stateFile: {})", clockSeq.get(), new Date(ts), uuid, stateFile);
|
||||
} catch (IOException e) {
|
||||
canSaveState = false;
|
||||
log.error("There was a problem while detecting the nodeId. Falling back to random mode. Try using to specify 'ebean.uuidNodeId' property", e);
|
||||
useRandomMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to initialize the generator by retrieving the MAC address from
|
||||
* hardware. If there is no suitable network interface found, it will fall back
|
||||
* to "generate" mode.
|
||||
*
|
||||
* @throws IOException if state file is not readable or tryGenerateMode also
|
||||
* fails.
|
||||
*/
|
||||
private void tryHardwareId() throws IOException {
|
||||
try {
|
||||
nodeId = getHardwareId();
|
||||
} catch (IOException e) {
|
||||
log.error("Error while reading MAC address. Fall back to 'generate' mode", e);
|
||||
tryGenerateMode();
|
||||
}
|
||||
|
||||
if (nodeId != null) {
|
||||
restoreState();
|
||||
log.info("Using MAC {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
return;
|
||||
}
|
||||
log.warn("No suitable network interface found. Fall back to 'generate' mode");
|
||||
tryGenerateMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries the "generate" mode. A nodeId is generated once and saved to the state
|
||||
* file
|
||||
*/
|
||||
private void tryGenerateMode() throws IOException {
|
||||
if (restoreState()) {
|
||||
log.info("Using recently generated nodeId {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
} else {
|
||||
// RFC 4.5 use random portion for node
|
||||
nodeId = super.getNodeIdBytes();
|
||||
log.error("Have to fall back to random node identifier {} (Reason: {} )", getNodeIdentifier(), e.getMessage());
|
||||
log.info("Using a newly generated nodeId {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Random mode. A nodeId is generated every start up. no state file is
|
||||
* maintained. This should always work.
|
||||
*/
|
||||
private void useRandomMode() {
|
||||
canSaveState = false;
|
||||
nodeId = super.getNodeIdBytes();
|
||||
log.info("Explicitly using a new random ID {} to generate Type 1 UUIDs", getNodeIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Node-identifier (=MAC address) as string
|
||||
*/
|
||||
@@ -164,22 +205,38 @@ public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
|
||||
*/
|
||||
private boolean restoreState() throws IOException {
|
||||
Properties prop = new Properties();
|
||||
if (stateFile.exists()) {
|
||||
try (InputStream is = new FileInputStream(stateFile)) {
|
||||
prop.load(is);
|
||||
}
|
||||
if (!stateFile.exists()) {
|
||||
log.debug("State file '{}' does not exist", stateFile);
|
||||
return false;
|
||||
}
|
||||
if (getNodeIdentifier().equals(prop.getProperty("nodeId"))) {
|
||||
try {
|
||||
Integer seq = Integer.valueOf(prop.getProperty("clockSeq")) & 0x3FFF;
|
||||
Long ts = Long.valueOf(prop.getProperty("timeStamp"));
|
||||
clockSeq.set(seq);
|
||||
timeStamp.set(ts);
|
||||
log.debug("Restored state from '{}'", stateFile);
|
||||
return true;
|
||||
} catch (NumberFormatException nfe) {
|
||||
// nop
|
||||
try (InputStream is = new FileInputStream(stateFile)) {
|
||||
prop.load(is);
|
||||
}
|
||||
|
||||
String propNodeId = prop.getProperty("nodeId");
|
||||
if (propNodeId == null || propNodeId.isEmpty()) {
|
||||
log.warn("State file '{}' is incomplete", stateFile);
|
||||
return false; // we cannot restore
|
||||
}
|
||||
try {
|
||||
if (nodeId == null) {
|
||||
nodeId = parseAlternativeNodeId(propNodeId);
|
||||
} else if (!getNodeIdentifier().equals(propNodeId)) {
|
||||
log.warn(
|
||||
"The nodeId in the state file '{}' has changed from {} to {}. "
|
||||
+ "This can happen when MAC address changes or when two containers share the same state file",
|
||||
stateFile, propNodeId, getNodeIdentifier());
|
||||
return false;
|
||||
}
|
||||
Integer seq = Integer.valueOf(prop.getProperty("clockSeq")) & 0x3FFF;
|
||||
Long ts = Long.valueOf(prop.getProperty("timeStamp"));
|
||||
clockSeq.set(seq);
|
||||
timeStamp.set(ts);
|
||||
log.debug("State successfully restored: {}", prop);
|
||||
return true;
|
||||
|
||||
} catch (IllegalArgumentException nfe) {
|
||||
log.error("State file '{}' is corrupt", stateFile, nfe);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,18 +21,12 @@ import java.util.List;
|
||||
*/
|
||||
public final class BatchedPstmt implements SpiProfileTransactionEvent {
|
||||
|
||||
/**
|
||||
* The underlying statement.
|
||||
*/
|
||||
private static final DB2GetKeys DB2_HACK = new DB2GetKeys();
|
||||
|
||||
private PreparedStatement pstmt;
|
||||
|
||||
/**
|
||||
* True if an insert that uses generated keys.
|
||||
*/
|
||||
private final boolean isGenKeys;
|
||||
|
||||
/**
|
||||
* The list of BatchPostExecute used to perform post processing.
|
||||
* The list of BatchPostExecute used to perform post-processing.
|
||||
*/
|
||||
private final List<BatchPostExecute> list = new ArrayList<>();
|
||||
private final String sql;
|
||||
@@ -170,12 +164,14 @@ public final class BatchedPstmt implements SpiProfileTransactionEvent {
|
||||
}
|
||||
|
||||
private void getGeneratedKeys() throws SQLException {
|
||||
if (DB2_HACK.getGeneratedKeys(pstmt, list)) {
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
try (ResultSet rset = pstmt.getGeneratedKeys()) {
|
||||
while (rset.next()) {
|
||||
Object idValue = rset.getObject(1);
|
||||
list.get(index).setGeneratedKey(idValue);
|
||||
index++;
|
||||
list.get(index++).setGeneratedKey(idValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Horrible DB2 hack.
|
||||
*/
|
||||
final class DB2GetKeys {
|
||||
|
||||
private static Class<? extends PreparedStatement> DB2_PREPARED_STATEMENT;
|
||||
private static Method GET_DB_GENERATED_KEYS;
|
||||
|
||||
static {
|
||||
try {
|
||||
DB2_PREPARED_STATEMENT = (Class<? extends PreparedStatement>) Class.forName("com.ibm.db2.jcc.DB2PreparedStatement");
|
||||
GET_DB_GENERATED_KEYS = DB2_PREPARED_STATEMENT.getDeclaredMethod("getDBGeneratedKeys");
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException cnf) {
|
||||
// NOP
|
||||
}
|
||||
}
|
||||
|
||||
static boolean getGeneratedKeys(PreparedStatement pstmt, List<BatchPostExecute> list) throws SQLException {
|
||||
if (DB2_PREPARED_STATEMENT != null) {
|
||||
PreparedStatement db2Stmt = null;
|
||||
if (DB2_PREPARED_STATEMENT.isInstance(pstmt)) {
|
||||
db2Stmt = pstmt;
|
||||
} else if (pstmt.isWrapperFor(DB2_PREPARED_STATEMENT)) {
|
||||
db2Stmt = pstmt.unwrap(DB2_PREPARED_STATEMENT);
|
||||
}
|
||||
if (db2Stmt != null) {
|
||||
// WTF: https://stackoverflow.com/questions/41725492/how-to-get-auto-generated-keys-of-batch-insert-statement
|
||||
ResultSet[] result;
|
||||
try {
|
||||
result = (ResultSet[]) GET_DB_GENERATED_KEYS.invoke(db2Stmt);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new SQLException("Could not get generated keys for DB2", e);
|
||||
}
|
||||
int index = 0;
|
||||
for (ResultSet resultSet : result) {
|
||||
while (resultSet.next()) {
|
||||
Object idValue = resultSet.getObject(1);
|
||||
list.get(index++).setGeneratedKey(idValue);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -41,15 +41,15 @@ public final class DefaultPersister implements Persister {
|
||||
* Actually does the persisting work.
|
||||
*/
|
||||
private final PersistExecute persistExecute;
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final BeanDescriptorManager beanDescriptorManager;
|
||||
private final int maxInBinding;
|
||||
|
||||
public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr) {
|
||||
this.server = server;
|
||||
this.beanDescriptorManager = descMgr;
|
||||
this.persistExecute = new DefaultPersistExecute(binder, server.config().getPersistBatchSize());
|
||||
this.maxInBinding = server.databasePlatform().getMaxInBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -649,6 +649,20 @@ public final class DefaultPersister implements Persister {
|
||||
* Delete by Id or a List of Id's.
|
||||
*/
|
||||
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, DeleteMode deleteMode) {
|
||||
int rows = 0;
|
||||
if (maxInBinding > 0) {
|
||||
// SqlServer has a 2100 parameter limit, so delete max 2000 ids at once
|
||||
// this gives space up to 100 more query parameters.
|
||||
while (idList != null && idList.size() > maxInBinding) {
|
||||
rows += deleteBatch(descriptor, id, idList.subList(0, maxInBinding), transaction, deleteMode);
|
||||
idList = idList.subList(maxInBinding, idList.size());
|
||||
}
|
||||
}
|
||||
rows += deleteBatch(descriptor, id, idList, transaction, deleteMode);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private int deleteBatch(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, DeleteMode deleteMode) {
|
||||
SpiTransaction t = (SpiTransaction) transaction;
|
||||
if (t.isPersistCascade()) {
|
||||
BeanPropertyAssocOne<?>[] propImportDelete = descriptor.propertiesOneImportedDelete();
|
||||
|
||||
@@ -13,11 +13,8 @@ import io.ebeaninternal.server.persist.dmlbind.BindableList;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static io.ebean.annotation.Platform.MARIADB;
|
||||
import static io.ebean.annotation.Platform.MYSQL;
|
||||
|
||||
/**
|
||||
* Meta data for insert handler. The meta data is for a particular bean type. It
|
||||
* Metadata for insert handler. The metadata is for a particular bean type. It
|
||||
* is considered immutable and is thread safe.
|
||||
*/
|
||||
final class InsertMeta {
|
||||
@@ -153,31 +150,25 @@ final class InsertMeta {
|
||||
private String genSql(boolean nullId, String table, boolean draftTable) {
|
||||
GenerateDmlRequest request = new GenerateDmlRequest();
|
||||
request.setInsertSetMode();
|
||||
|
||||
request.append("insert into ").append(table);
|
||||
if (nullId && noColumnsForInsert(draftTable)) {
|
||||
return request.append(defaultValues()).toString();
|
||||
}
|
||||
|
||||
request.append(" (");
|
||||
if (!nullId) {
|
||||
id.dmlAppend(request);
|
||||
}
|
||||
|
||||
if (shadowFKey != null) {
|
||||
shadowFKey.dmlAppend(request);
|
||||
}
|
||||
|
||||
if (discriminator != null) {
|
||||
discriminator.dmlAppend(request);
|
||||
}
|
||||
|
||||
if (draftTable) {
|
||||
all.dmlAppend(request);
|
||||
} else {
|
||||
allExcludeDraftOnly.dmlAppend(request);
|
||||
}
|
||||
|
||||
request.append(") values (");
|
||||
request.append(request.getInsertBindBuffer());
|
||||
request.append(")");
|
||||
@@ -185,7 +176,16 @@ final class InsertMeta {
|
||||
}
|
||||
|
||||
private String defaultValues() {
|
||||
return platform.base() == MYSQL || platform.base() == MARIADB ? " values (default)" : " default values";
|
||||
switch (platform.base()) {
|
||||
case MYSQL:
|
||||
case MARIADB:
|
||||
case ORACLE:
|
||||
return " values (default)";
|
||||
case DB2:
|
||||
return " (" + id.getIdentityColumn() + ") values (default)";
|
||||
default:
|
||||
return " default values";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -193,7 +193,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
|
||||
// set the generated sql back to the query
|
||||
// so its available to the user...
|
||||
query.setGeneratedSql(queryPlan.getSql());
|
||||
SqlTree sqlTree = queryPlan.getSqlTree();
|
||||
SqlTreePlan sqlTree = queryPlan.getSqlTree();
|
||||
this.rootNode = sqlTree.getRootNode();
|
||||
this.manyProperty = sqlTree.getManyProperty();
|
||||
this.sql = queryPlan.getSql();
|
||||
@@ -581,20 +581,17 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
|
||||
|
||||
@Override
|
||||
public void registerBeanInherit(BeanPropertyAssocOne<?> property, EntityBeanIntercept ebi) {
|
||||
String path = getPath(property.name());
|
||||
request.loadContext().register(path, ebi, property);
|
||||
request.loadContext().register(path(property.name()), ebi, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(String path, EntityBeanIntercept ebi) {
|
||||
path = getPath(path);
|
||||
request.loadContext().register(path, ebi);
|
||||
request.loadContext().register(path(path), ebi);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(BeanPropertyAssocMany<?> many, BeanCollection<?> bc) {
|
||||
String path = getPath(many.name());
|
||||
request.loadContext().register(path, many, bc);
|
||||
request.loadContext().register(path(many.name()), many, bc);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -663,18 +660,14 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
|
||||
return autoTuneProfiling && query.isUsageProfiling();
|
||||
}
|
||||
|
||||
private String getPath(String propertyName) {
|
||||
private String path(String propertyName) {
|
||||
if (currentPrefix == null) {
|
||||
return propertyName;
|
||||
} else if (propertyName == null) {
|
||||
return currentPrefix;
|
||||
}
|
||||
String path = currentPathMap.get(propertyName);
|
||||
if (path != null) {
|
||||
return path;
|
||||
} else {
|
||||
return currentPrefix + "." + propertyName;
|
||||
}
|
||||
return path != null ? path : currentPrefix + "." + propertyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -110,7 +110,7 @@ final class CQueryBuilder {
|
||||
sql = buildUpdateSql(request, rootTableAlias, predicates, sqlTree);
|
||||
}
|
||||
// cache the query plan
|
||||
queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql());
|
||||
queryPlan = new CQueryPlan(request, sql, sqlTree.plan(), predicates.getLogWhereSql());
|
||||
request.putQueryPlan(queryPlan);
|
||||
return new CQueryUpdate(request, predicates, queryPlan);
|
||||
}
|
||||
@@ -197,7 +197,7 @@ final class CQueryBuilder {
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
|
||||
|
||||
queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, predicates.getLogWhereSql());
|
||||
queryPlan = new CQueryPlan(request, s.getSql(), sqlTree.plan(), predicates.getLogWhereSql());
|
||||
request.putQueryPlan(queryPlan);
|
||||
return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct());
|
||||
}
|
||||
@@ -288,7 +288,7 @@ final class CQueryBuilder {
|
||||
}
|
||||
}
|
||||
// cache the query plan
|
||||
queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql());
|
||||
queryPlan = new CQueryPlan(request, sql, sqlTree.plan(), predicates.getLogWhereSql());
|
||||
request.putQueryPlan(queryPlan);
|
||||
return new CQueryRowCount(queryPlan, request, predicates);
|
||||
}
|
||||
@@ -343,7 +343,7 @@ final class CQueryBuilder {
|
||||
if (rawSql) {
|
||||
queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql());
|
||||
} else {
|
||||
queryPlan = new CQueryPlan(request, res, sqlTree, false, predicates.getLogWhereSql());
|
||||
queryPlan = new CQueryPlan(request, res, sqlTree.plan(), false, predicates.getLogWhereSql());
|
||||
}
|
||||
BeanDescriptor<T> desc = request.descriptor();
|
||||
if (desc.isReadAuditing()) {
|
||||
@@ -568,8 +568,16 @@ final class CQueryBuilder {
|
||||
if (countSingleAttribute) {
|
||||
sb.append("r1.attribute_, count(*) from (select ");
|
||||
if (distinct) {
|
||||
sb.append("distinct t0.");
|
||||
sb.append(request.descriptor().idProperty().dbColumn()).append(", ");
|
||||
sb.append("distinct ");
|
||||
BeanProperty idProp = request.descriptor().idProperty();
|
||||
if (idProp.isEmbedded()) {
|
||||
BeanProperty[] props = ((BeanPropertyAssocOne<?>) idProp).properties();
|
||||
for (BeanProperty prop : props) {
|
||||
sb.append("t0.").append(prop.dbColumn()).append(", ");
|
||||
}
|
||||
} else {
|
||||
sb.append("t0.").append(idProp.dbColumn()).append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(select.getSelectSql()).append(" as attribute_");
|
||||
} else {
|
||||
@@ -749,7 +757,11 @@ final class CQueryBuilder {
|
||||
}
|
||||
|
||||
boolean isPlatformDistinctOn() {
|
||||
return dbPlatform.isPlatform(Platform.POSTGRES);
|
||||
return dbPlatform.isPlatform(Platform.POSTGRES) || dbPlatform.isPlatform(Platform.YUGABYTE);
|
||||
}
|
||||
|
||||
boolean isPlatformDistinctNoLobs() {
|
||||
return dbPlatform.isPlatform(Platform.DB2); // CHECKME: Also oracle?
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,7 +55,7 @@ public class CQueryPlan implements SpiQueryPlan {
|
||||
private final String sql;
|
||||
private final String hash;
|
||||
private final String logWhereSql;
|
||||
private final SqlTree sqlTree;
|
||||
private final SqlTreePlan sqlTree;
|
||||
|
||||
/**
|
||||
* Encrypted properties required additional binding.
|
||||
@@ -76,7 +76,7 @@ public class CQueryPlan implements SpiQueryPlan {
|
||||
/**
|
||||
* Create a query plan based on a OrmQueryRequest.
|
||||
*/
|
||||
CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) {
|
||||
CQueryPlan(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTreePlan sqlTree, boolean rawSql, String logWhereSql) {
|
||||
this.server = request.server();
|
||||
this.dataTimeZone = server.dataTimeZone();
|
||||
this.beanType = request.descriptor().type();
|
||||
@@ -101,7 +101,7 @@ public class CQueryPlan implements SpiQueryPlan {
|
||||
/**
|
||||
* Create a query plan for a raw sql query.
|
||||
*/
|
||||
CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTree sqlTree, String logWhereSql) {
|
||||
CQueryPlan(OrmQueryRequest<?> request, String sql, SqlTreePlan sqlTree, String logWhereSql) {
|
||||
this.server = request.server();
|
||||
this.dataTimeZone = server.dataTimeZone();
|
||||
this.beanType = request.descriptor().type();
|
||||
@@ -249,7 +249,7 @@ public class CQueryPlan implements SpiQueryPlan {
|
||||
return rawSql ? planKey.getPartialKey() + "_" + hash : planKey.getPartialKey();
|
||||
}
|
||||
|
||||
final SqlTree getSqlTree() {
|
||||
final SqlTreePlan getSqlTree() {
|
||||
return sqlTree;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ final class CQueryPlanRawSql extends CQueryPlan {
|
||||
private final int[] rsetIndexPositions;
|
||||
|
||||
CQueryPlanRawSql(OrmQueryRequest<?> request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) {
|
||||
super(request, sqlRes, sqlTree, true, logWhereSql);
|
||||
super(request, sqlRes, sqlTree.plan(), true, logWhereSql);
|
||||
this.rsetIndexPositions = createIndexPositions(request, sqlTree);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ public interface STreeProperty extends ScalarDataReader<Object> {
|
||||
* Return true if the property is the Id.
|
||||
*/
|
||||
boolean isId();
|
||||
|
||||
/**
|
||||
* Returns true, if this is a lob property from db-perspective.
|
||||
*/
|
||||
boolean isDbLob();
|
||||
|
||||
/**
|
||||
* Return true if the property is an embedded type.
|
||||
|
||||
@@ -7,6 +7,8 @@ import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.InheritInfo;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean type interface for Sql query tree.
|
||||
*/
|
||||
@@ -136,4 +138,8 @@ public interface STreeType {
|
||||
*/
|
||||
void markAsDeleted(EntityBean bean);
|
||||
|
||||
/**
|
||||
* Return the "path map" to toMany or toOne properties using the given prefix.
|
||||
*/
|
||||
Map<String, String> pathMap(String prefix);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ final class SqlTree {
|
||||
this.noJoins = !includeJoins;
|
||||
}
|
||||
|
||||
SqlTreePlan plan() {
|
||||
return new SqlTreePlan(rootNode.createLoad(), manyProperty, encryptedProps, dependentTables());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query mandates SQL Distinct due to ToMany inclusion.
|
||||
*/
|
||||
@@ -123,10 +127,6 @@ final class SqlTree {
|
||||
return inheritanceWhereSql;
|
||||
}
|
||||
|
||||
SqlTreeRoot getRootNode() {
|
||||
return (SqlTreeRoot)rootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property that is associated with the many. There can only be one
|
||||
* per SqlSelect. This can be null.
|
||||
@@ -135,10 +135,6 @@ final class SqlTree {
|
||||
return manyProperty;
|
||||
}
|
||||
|
||||
STreeProperty[] getEncryptedProps() {
|
||||
return encryptedProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query has a many join.
|
||||
*/
|
||||
@@ -153,7 +149,7 @@ final class SqlTree {
|
||||
/**
|
||||
* Return the tables that are joined in this query.
|
||||
*/
|
||||
Set<String> dependentTables() {
|
||||
private Set<String> dependentTables() {
|
||||
Set<String> tables = new LinkedHashSet<>();
|
||||
rootNode.dependentTables(tables);
|
||||
return tables;
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class SqlTreeBuilder {
|
||||
private final SpiQuery.TemporalMode temporalMode;
|
||||
private SqlTreeNode rootNode;
|
||||
private boolean sqlDistinct;
|
||||
private final boolean distinctNoLobs;
|
||||
|
||||
/**
|
||||
* Construct for RawSql query.
|
||||
@@ -65,6 +66,7 @@ public final class SqlTreeBuilder {
|
||||
this.query = null;
|
||||
this.subQuery = false;
|
||||
this.distinctOnPlatform = false;
|
||||
this.distinctNoLobs = false;
|
||||
this.queryDetail = queryDetail;
|
||||
this.predicates = predicates;
|
||||
this.temporalMode = SpiQuery.TemporalMode.CURRENT;
|
||||
@@ -97,6 +99,7 @@ public final class SqlTreeBuilder {
|
||||
this.predicates = predicates;
|
||||
this.alias = new SqlTreeAlias(request.baseTableAlias(), temporalMode);
|
||||
this.distinctOnPlatform = builder.isPlatformDistinctOn();
|
||||
this.distinctNoLobs = builder.isPlatformDistinctNoLobs();
|
||||
String fromForUpdate = builder.fromForUpdate(query);
|
||||
CQueryHistorySupport historySupport = builder.getHistorySupport(query);
|
||||
CQueryDraftSupport draftSupport = builder.getDraftSupport(query);
|
||||
@@ -266,6 +269,9 @@ public final class SqlTreeBuilder {
|
||||
if (joinList != null) {
|
||||
joinList.add(selectNode);
|
||||
}
|
||||
if (sqlDistinct && distinctNoLobs) {
|
||||
selectNode.unselectLobs();
|
||||
}
|
||||
return selectNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.core.type.ScalarDataReader;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Tree node that loads an entity bean type.
|
||||
*/
|
||||
interface SqlTreeLoad {
|
||||
|
||||
/**
|
||||
* Load the appropriate information from the SqlSelectReader.
|
||||
* <p>
|
||||
* At a high level this actually controls the reading of the data from the
|
||||
* jdbc resultSet and putting it into the bean etc.
|
||||
* </p>
|
||||
*/
|
||||
EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return the reader for the single attribute query.
|
||||
*/
|
||||
ScalarDataReader<?> getSingleAttributeReader();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.core.type.ScalarDataReader;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiQuery.Mode;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.InheritInfo;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Normal bean included in the query.
|
||||
*/
|
||||
class SqlTreeLoadBean implements SqlTreeLoad {
|
||||
|
||||
final STreeType desc;
|
||||
final IdBinder idBinder;
|
||||
final SqlTreeLoad[] children;
|
||||
private final boolean partialObject;
|
||||
private final STreeProperty[] properties;
|
||||
private final STreePropertyAssoc nodeBeanProp;
|
||||
final boolean readId;
|
||||
private final boolean readIdNormal;
|
||||
private final boolean disableLazyLoad;
|
||||
private final InheritInfo inheritInfo;
|
||||
final String prefix;
|
||||
private final Map<String, String> pathMap;
|
||||
final STreePropertyAssocMany lazyLoadParent;
|
||||
private final SpiQuery.TemporalMode temporalMode;
|
||||
private final boolean temporalVersions;
|
||||
final IdBinder lazyLoadParentIdBinder;
|
||||
|
||||
SqlTreeLoadBean(SqlTreeNodeBean node) {
|
||||
this.lazyLoadParent = node.lazyLoadParent;
|
||||
this.lazyLoadParentIdBinder = node.lazyLoadParentIdBinder;
|
||||
this.prefix = node.prefix;
|
||||
this.desc = node.desc;
|
||||
this.inheritInfo = desc.inheritInfo();
|
||||
this.idBinder = desc.idBinder();
|
||||
this.temporalMode = node.temporalMode;
|
||||
this.temporalVersions = node.temporalVersions;
|
||||
this.nodeBeanProp = node.nodeBeanProp;
|
||||
this.readId = node.readId;
|
||||
this.readIdNormal = readId && !temporalVersions;
|
||||
this.disableLazyLoad = node.disableLazyLoad;
|
||||
this.partialObject = node.partialObject;
|
||||
this.properties = node.properties;
|
||||
this.pathMap = node.pathMap;
|
||||
this.children = node.createLoadChildren();
|
||||
}
|
||||
|
||||
boolean isRoot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ScalarDataReader<?> getSingleAttributeReader() {
|
||||
if (properties == null || properties.length == 0) {
|
||||
// if we have no property ask first children (in a distinct select with join)
|
||||
if (children.length == 0) {
|
||||
// expected to be a findIds query
|
||||
return desc.idBinder().getBeanProperty();
|
||||
}
|
||||
return children[0].getSingleAttributeReader();
|
||||
}
|
||||
if (properties[0] instanceof STreePropertyAssocOne) {
|
||||
STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0];
|
||||
if (assocOne.isAssocId()) {
|
||||
return assocOne.idReader();
|
||||
}
|
||||
}
|
||||
return properties[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load that takes into account inheritance.
|
||||
*/
|
||||
private final class LoadInherit extends Load {
|
||||
|
||||
private LoadInherit(DbReadContext ctx, EntityBean parentBean) {
|
||||
super(ctx, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initBeanType() throws SQLException {
|
||||
InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.inheritInfo();
|
||||
if (localInfo == null) {
|
||||
// the bean must be null
|
||||
localIdBinder = idBinder;
|
||||
localDesc = desc;
|
||||
} else {
|
||||
localBean = localInfo.createEntityBean();
|
||||
localType = localInfo.getType();
|
||||
localIdBinder = localInfo.getIdBinder();
|
||||
localDesc = localInfo.desc();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void loadProperties() {
|
||||
// take account of inheritance
|
||||
for (STreeProperty property : properties) {
|
||||
localDesc.inheritanceLoad(sqlBeanLoad, property, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a bean instance.
|
||||
*/
|
||||
class Load {
|
||||
|
||||
final DbReadContext ctx;
|
||||
final EntityBean parentBean;
|
||||
|
||||
Object lazyLoadParentId;
|
||||
Class<?> localType;
|
||||
STreeType localDesc;
|
||||
IdBinder localIdBinder;
|
||||
EntityBean localBean;
|
||||
|
||||
Mode queryMode;
|
||||
PersistenceContext persistenceContext;
|
||||
Object id;
|
||||
EntityBean contextBean;
|
||||
SqlBeanLoad sqlBeanLoad;
|
||||
boolean lazyLoadMany;
|
||||
|
||||
private Load(DbReadContext ctx, EntityBean parentBean) {
|
||||
this.ctx = ctx;
|
||||
this.parentBean = parentBean;
|
||||
}
|
||||
|
||||
private void initLazyParent() throws SQLException {
|
||||
if (lazyLoadParentIdBinder != null) {
|
||||
lazyLoadParentId = lazyLoadParentIdBinder.read(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void initBeanType() throws SQLException {
|
||||
localDesc = desc;
|
||||
localBean = desc.createEntityBean();
|
||||
localIdBinder = idBinder;
|
||||
}
|
||||
|
||||
private void initPersistenceContext() {
|
||||
queryMode = ctx.getQueryMode();
|
||||
persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext();
|
||||
}
|
||||
|
||||
private void readId() throws SQLException {
|
||||
if (readId) {
|
||||
id = localIdBinder.readSet(ctx, localBean);
|
||||
if (id == null) {
|
||||
readIdNullBean();
|
||||
} else if (!temporalVersions) {
|
||||
readIdBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readIdBean() {
|
||||
// check the PersistenceContext to see if the bean already exists
|
||||
contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean);
|
||||
if (contextBean == null) {
|
||||
// bean just added to the persistenceContext
|
||||
contextBean = localBean;
|
||||
} else {
|
||||
// bean already exists in persistenceContext
|
||||
|
||||
if (queryMode.isLoadContextBean()) {
|
||||
// if explicitly set loadContextBean to true, then reload
|
||||
localBean = contextBean;
|
||||
} else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) {
|
||||
// reload if contextBean is partial object
|
||||
localBean = contextBean;
|
||||
// and switch to lazyLoad query mode in order not to overwrite
|
||||
// existing properties in SqlBeanLoad::load
|
||||
queryMode = Mode.LAZYLOAD_BEAN;
|
||||
} else {
|
||||
// ignore the DB data...
|
||||
localBean = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readIdNullBean() {
|
||||
// bean must be null...
|
||||
localBean = null;
|
||||
// ... but there may exist as reference bean in parent which has to be marked as deleted.
|
||||
if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) {
|
||||
contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean);
|
||||
if (contextBean != null) {
|
||||
desc.markAsDeleted(contextBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initSqlLoadBean() {
|
||||
ctx.setCurrentPrefix(prefix, pathMap);
|
||||
ctx.propagateState(localBean);
|
||||
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
|
||||
}
|
||||
|
||||
void loadProperties() {
|
||||
for (STreeProperty property : properties) {
|
||||
property.load(sqlBeanLoad);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadChildren() throws SQLException {
|
||||
if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) {
|
||||
// batch lazy load many into existing contextBean
|
||||
localBean = contextBean;
|
||||
lazyLoadMany = true;
|
||||
}
|
||||
for (SqlTreeLoad child : children) {
|
||||
child.load(ctx, localBean, contextBean);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLazyLoadManyRoot() {
|
||||
return queryMode == Mode.LAZYLOAD_MANY && isRoot();
|
||||
}
|
||||
|
||||
private EntityBean getContextBean() {
|
||||
return contextBean;
|
||||
}
|
||||
|
||||
private void postLoad() {
|
||||
if (!lazyLoadMany && localBean != null) {
|
||||
ctx.setCurrentPrefix(prefix, pathMap);
|
||||
if (readIdNormal) {
|
||||
createListProxies();
|
||||
}
|
||||
if (temporalMode == SpiQuery.TemporalMode.DRAFT) {
|
||||
localDesc.setDraft(localBean);
|
||||
}
|
||||
localDesc.postLoad(localBean);
|
||||
|
||||
EntityBeanIntercept ebi = localBean._ebean_getIntercept();
|
||||
ebi.setPersistenceContext(persistenceContext);
|
||||
if (Mode.LAZYLOAD_BEAN == queryMode) {
|
||||
// Lazy Load does not reset the dirty state
|
||||
ebi.setLoadedLazy();
|
||||
} else if (readId) {
|
||||
// normal bean loading
|
||||
ebi.setLoaded();
|
||||
}
|
||||
|
||||
if (disableLazyLoad) {
|
||||
// bean does not have an Id or is SqlSelect based
|
||||
ebi.setDisableLazyLoad(true);
|
||||
} else if (partialObject) {
|
||||
if (readId) {
|
||||
// register for lazy loading
|
||||
ctx.register(null, ebi);
|
||||
}
|
||||
} else {
|
||||
ebi.setFullyLoadedBean(true);
|
||||
}
|
||||
|
||||
if (ctx.isAutoTuneProfiling() && !disableLazyLoad) {
|
||||
// collect autoTune profiling for this bean...
|
||||
ctx.profileBean(ebi, prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create lazy loading proxies for the Many's except for the one that is
|
||||
* included in the actual query.
|
||||
*/
|
||||
private void createListProxies() {
|
||||
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
|
||||
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
|
||||
for (STreePropertyAssocMany many : localDesc.propsMany()) {
|
||||
if (many != fetchedMany) {
|
||||
// create a proxy for the many (deferred fetching)
|
||||
BeanCollection<?> ref = many.createReference(localBean, forceNewReference);
|
||||
if (ref != null) {
|
||||
if (disableLazyLoad) {
|
||||
ref.setDisableLazyLoad(true);
|
||||
}
|
||||
if (!ref.isRegisteredWithLoadContext()) {
|
||||
ctx.register(many.asMany(), ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setBeanToParent() {
|
||||
if (parentBean != null) {
|
||||
// set this back to the parentBean
|
||||
nodeBeanProp.setValue(parentBean, contextBean);
|
||||
}
|
||||
}
|
||||
|
||||
private EntityBean complete() {
|
||||
if (!readIdNormal) {
|
||||
// a bean with no Id (never found in context)
|
||||
if (lazyLoadParentId != null) {
|
||||
ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId);
|
||||
}
|
||||
return localBean;
|
||||
} else {
|
||||
if (lazyLoadParentId != null) {
|
||||
ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId);
|
||||
}
|
||||
return contextBean;
|
||||
}
|
||||
}
|
||||
|
||||
private void initialise() throws SQLException {
|
||||
initLazyParent();
|
||||
initBeanType();
|
||||
initPersistenceContext();
|
||||
readId();
|
||||
initSqlLoadBean();
|
||||
loadProperties();
|
||||
loadChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the load returning the loaded bean.
|
||||
*/
|
||||
final EntityBean perform() throws SQLException {
|
||||
initialise();
|
||||
if (isLazyLoadManyRoot()) {
|
||||
return getContextBean();
|
||||
}
|
||||
postLoad();
|
||||
setBeanToParent();
|
||||
return complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean was already in the context. If already in the
|
||||
* context we need to check if it is already contained in the collection.
|
||||
*/
|
||||
final boolean isContextBean() {
|
||||
return localBean == null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the properties from the resultSet.
|
||||
*/
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException {
|
||||
return createLoad(ctx, parentBean).perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the loader with or without inheritance.
|
||||
*/
|
||||
final Load createLoad(DbReadContext ctx, EntityBean parentBean) {
|
||||
return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SqlTreeLoadBean: " + desc;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
final class SqlTreeLoadManyRoot extends SqlTreeLoadBean {
|
||||
|
||||
private final STreePropertyAssocMany manyProp;
|
||||
|
||||
SqlTreeLoadManyRoot(SqlTreeNodeManyRoot node) {
|
||||
super(node);
|
||||
this.manyProp = node.manyProp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
|
||||
// pass in null for parentBean because added to a collection rather than set to the parentBean
|
||||
Load load = createLoad(cquery, null);
|
||||
EntityBean detailBean = load.perform();
|
||||
if (contextParent != null) {
|
||||
// Add to the collection and initialise collection if needed
|
||||
// A null detailBean may initialise an empty collection
|
||||
// Check for bean existing in collection based on load.isContextBean()
|
||||
manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean());
|
||||
}
|
||||
return detailBean;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/**
|
||||
* Represents the root node of the Sql Tree.
|
||||
*/
|
||||
final class SqlTreeLoadRoot extends SqlTreeLoadBean implements SqlTreeRoot {
|
||||
|
||||
SqlTreeLoadRoot(SqlTreeNodeBean node) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRoot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx) throws SQLException {
|
||||
return load(ctx, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the version bean.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
|
||||
// read the sys period lower and upper bounds
|
||||
// these are always the first 2 columns in the resultSet
|
||||
Timestamp start = ctx.getDataReader().getTimestamp();
|
||||
Timestamp end = ctx.getDataReader().getTimestamp();
|
||||
T bean = (T) load(ctx, null, null);
|
||||
return new Version<>(bean, start, end);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,8 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.core.type.ScalarDataReader;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -64,25 +59,11 @@ interface SqlTreeNode {
|
||||
*/
|
||||
void addSoftDeletePredicate(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Load the appropriate information from the SqlSelectReader.
|
||||
* <p>
|
||||
* At a high level this actually controls the reading of the data from the
|
||||
* jdbc resultSet and putting it into the bean etc.
|
||||
* </p>
|
||||
*/
|
||||
EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return true if the query has a many join.
|
||||
*/
|
||||
boolean hasMany();
|
||||
|
||||
/**
|
||||
* Return the reader for the single attribute query.
|
||||
*/
|
||||
ScalarDataReader<?> getSingleAttributeReader();
|
||||
|
||||
/**
|
||||
* Return true if the query is known to only have a single property selected.
|
||||
*/
|
||||
@@ -92,4 +73,15 @@ interface SqlTreeNode {
|
||||
* Add dependent tables to the given set.
|
||||
*/
|
||||
void dependentTables(Set<String> tables);
|
||||
|
||||
/**
|
||||
* Create the loader for this node.
|
||||
*/
|
||||
SqlTreeLoad createLoad();
|
||||
|
||||
/**
|
||||
* Unselect lobs (for distinct queries on DB2 and Oracle).
|
||||
*/
|
||||
default void unselectLobs() {
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,68 +1,57 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.core.type.ScalarDataReader;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiQuery.Mode;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.deploy.InheritInfo;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
import io.ebeaninternal.server.deploy.id.IdBinder;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Normal bean included in the query.
|
||||
*/
|
||||
class SqlTreeNodeBean implements SqlTreeNode {
|
||||
|
||||
private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];
|
||||
private static final SqlTreeLoad[] NO_LOAD_CHILDREN = new SqlTreeLoad[0];
|
||||
|
||||
final STreeType desc;
|
||||
final IdBinder idBinder;
|
||||
/**
|
||||
* The children which will be other SelectBean or SelectProxyBean.
|
||||
*/
|
||||
final SqlTreeNode[] children;
|
||||
final List<SqlTreeNode> children;
|
||||
/**
|
||||
* Set to true if this is a partial object fetch.
|
||||
*/
|
||||
private final boolean partialObject;
|
||||
private final STreeProperty[] properties;
|
||||
boolean partialObject;
|
||||
STreeProperty[] properties;
|
||||
/**
|
||||
* Extra where clause added by Where annotation on associated many.
|
||||
*/
|
||||
private final String extraWhere;
|
||||
private final STreePropertyAssoc nodeBeanProp;
|
||||
final String extraWhere;
|
||||
final STreePropertyAssoc nodeBeanProp;
|
||||
/**
|
||||
* False if report bean and has no id property.
|
||||
*/
|
||||
final boolean readId;
|
||||
private final boolean readIdNormal;
|
||||
private final boolean disableLazyLoad;
|
||||
private final InheritInfo inheritInfo;
|
||||
final boolean readIdNormal;
|
||||
final boolean disableLazyLoad;
|
||||
final InheritInfo inheritInfo;
|
||||
final String prefix;
|
||||
private final Map<String, String> pathMap;
|
||||
final Map<String, String> pathMap;
|
||||
final STreePropertyAssocMany lazyLoadParent;
|
||||
private final SpiQuery.TemporalMode temporalMode;
|
||||
private final boolean temporalVersions;
|
||||
private final IdBinder lazyLoadParentIdBinder;
|
||||
final SpiQuery.TemporalMode temporalMode;
|
||||
final boolean temporalVersions;
|
||||
final IdBinder lazyLoadParentIdBinder;
|
||||
String baseTableAlias;
|
||||
/**
|
||||
* Table alias set if this bean node includes a join to a intersection
|
||||
* table and that table has history support.
|
||||
*/
|
||||
private boolean intersectionAsOfTableAlias;
|
||||
private final boolean aggregation;
|
||||
boolean intersectionAsOfTableAlias;
|
||||
final boolean aggregation;
|
||||
|
||||
/**
|
||||
* Construct for leaf node.
|
||||
@@ -104,53 +93,40 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
this.disableLazyLoad = disableLazyLoad || !readIdNormal || desc.isRawSqlBased();
|
||||
this.partialObject = props.isPartialObject();
|
||||
this.properties = props.getProps();
|
||||
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[0]);
|
||||
pathMap = createPathMap(prefix, desc);
|
||||
this.children = myChildren == null ? Collections.emptyList() : myChildren;
|
||||
this.pathMap = createPathMap(prefix, desc);
|
||||
}
|
||||
|
||||
boolean isRoot() {
|
||||
return false;
|
||||
@Override
|
||||
public SqlTreeLoad createLoad() {
|
||||
return new SqlTreeLoadBean(this);
|
||||
}
|
||||
|
||||
protected SqlTreeLoad[] createLoadChildren() {
|
||||
if (children.isEmpty()) {
|
||||
return NO_LOAD_CHILDREN;
|
||||
}
|
||||
List<SqlTreeLoad> loadChildren = new ArrayList<>(children.size());
|
||||
for (SqlTreeNode child : children) {
|
||||
SqlTreeLoad load = child.createLoad();
|
||||
if (load != null) {
|
||||
loadChildren.add(load);
|
||||
}
|
||||
}
|
||||
return loadChildren.toArray(new SqlTreeLoad[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isSingleProperty() {
|
||||
return properties != null && properties.length == 1 && children.length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ScalarDataReader<?> getSingleAttributeReader() {
|
||||
if (properties == null || properties.length == 0) {
|
||||
// if we have no property ask first children (in a distinct select with join)
|
||||
if (children.length == 0) {
|
||||
// expected to be a findIds query
|
||||
return desc.idBinder().getBeanProperty();
|
||||
}
|
||||
return children[0].getSingleAttributeReader();
|
||||
}
|
||||
if (properties[0] instanceof STreePropertyAssocOne) {
|
||||
STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0];
|
||||
if (assocOne.isAssocId()) {
|
||||
return assocOne.idReader();
|
||||
}
|
||||
}
|
||||
return properties[0];
|
||||
return properties != null && properties.length == 1 && children.isEmpty();
|
||||
}
|
||||
|
||||
private Map<String, String> createPathMap(String prefix, STreeType desc) {
|
||||
HashMap<String, String> m = new HashMap<>();
|
||||
for (STreePropertyAssocMany many : desc.propsMany()) {
|
||||
String name = many.name();
|
||||
m.put(name, getPath(prefix, name));
|
||||
}
|
||||
return m;
|
||||
return prefix == null ? Collections.emptyMap() : desc.pathMap(prefix);
|
||||
}
|
||||
|
||||
private String getPath(String prefix, String propertyName) {
|
||||
if (prefix == null) {
|
||||
return propertyName;
|
||||
} else {
|
||||
return prefix + "." + propertyName;
|
||||
}
|
||||
private String path(String prefix, String propertyName) {
|
||||
return prefix == null ? propertyName : prefix + "." + propertyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,7 +134,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
if (readId) {
|
||||
if (inheritInfo != null) {
|
||||
// discriminator column always proceeds id column
|
||||
selectChain.add(getPath(prefix, inheritInfo.getDiscriminatorColumn()));
|
||||
selectChain.add(path(prefix, inheritInfo.getDiscriminatorColumn()));
|
||||
}
|
||||
idBinder.buildRawSqlSelectChain(prefix, selectChain);
|
||||
}
|
||||
@@ -173,293 +149,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load that takes into account inheritance.
|
||||
*/
|
||||
private final class LoadInherit extends Load {
|
||||
|
||||
private LoadInherit(DbReadContext ctx, EntityBean parentBean) {
|
||||
super(ctx, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
void initBeanType() throws SQLException {
|
||||
InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.inheritInfo();
|
||||
if (localInfo == null) {
|
||||
// the bean must be null
|
||||
localIdBinder = idBinder;
|
||||
localDesc = desc;
|
||||
} else {
|
||||
localBean = localInfo.createEntityBean();
|
||||
localType = localInfo.getType();
|
||||
localIdBinder = localInfo.getIdBinder();
|
||||
localDesc = localInfo.desc();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void loadProperties() {
|
||||
// take account of inheritance
|
||||
for (STreeProperty property : properties) {
|
||||
localDesc.inheritanceLoad(sqlBeanLoad, property, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a bean instance.
|
||||
*/
|
||||
class Load {
|
||||
|
||||
final DbReadContext ctx;
|
||||
final EntityBean parentBean;
|
||||
|
||||
Object lazyLoadParentId;
|
||||
Class<?> localType;
|
||||
STreeType localDesc;
|
||||
IdBinder localIdBinder;
|
||||
EntityBean localBean;
|
||||
|
||||
Mode queryMode;
|
||||
PersistenceContext persistenceContext;
|
||||
Object id;
|
||||
EntityBean contextBean;
|
||||
SqlBeanLoad sqlBeanLoad;
|
||||
boolean lazyLoadMany;
|
||||
|
||||
private Load(DbReadContext ctx, EntityBean parentBean) {
|
||||
this.ctx = ctx;
|
||||
this.parentBean = parentBean;
|
||||
}
|
||||
|
||||
private void initLazyParent() throws SQLException {
|
||||
if (lazyLoadParentIdBinder != null) {
|
||||
lazyLoadParentId = lazyLoadParentIdBinder.read(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void initBeanType() throws SQLException {
|
||||
localDesc = desc;
|
||||
localBean = desc.createEntityBean();
|
||||
localIdBinder = idBinder;
|
||||
}
|
||||
|
||||
private void initPersistenceContext() {
|
||||
queryMode = ctx.getQueryMode();
|
||||
persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext();
|
||||
}
|
||||
|
||||
private void readId() throws SQLException {
|
||||
if (readId) {
|
||||
id = localIdBinder.readSet(ctx, localBean);
|
||||
if (id == null) {
|
||||
readIdNullBean();
|
||||
} else if (!temporalVersions) {
|
||||
readIdBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readIdBean() {
|
||||
// check the PersistenceContext to see if the bean already exists
|
||||
contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean);
|
||||
if (contextBean == null) {
|
||||
// bean just added to the persistenceContext
|
||||
contextBean = localBean;
|
||||
} else {
|
||||
// bean already exists in persistenceContext
|
||||
|
||||
if (queryMode.isLoadContextBean()) {
|
||||
// if explicitly set loadContextBean to true, then reload
|
||||
localBean = contextBean;
|
||||
} else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) {
|
||||
// reload if contextBean is partial object
|
||||
localBean = contextBean;
|
||||
// and switch to lazyLoad query mode in order not to overwrite
|
||||
// existing properties in SqlBeanLoad::load
|
||||
queryMode = Mode.LAZYLOAD_BEAN;
|
||||
} else {
|
||||
// ignore the DB data...
|
||||
localBean = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readIdNullBean() {
|
||||
// bean must be null...
|
||||
localBean = null;
|
||||
// ... but there may exist as reference bean in parent which has to be marked as deleted.
|
||||
if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) {
|
||||
contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean);
|
||||
if (contextBean != null) {
|
||||
desc.markAsDeleted(contextBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initSqlLoadBean() {
|
||||
ctx.setCurrentPrefix(prefix, pathMap);
|
||||
ctx.propagateState(localBean);
|
||||
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
|
||||
}
|
||||
|
||||
void loadProperties() {
|
||||
for (STreeProperty property : properties) {
|
||||
property.load(sqlBeanLoad);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadChildren() throws SQLException {
|
||||
if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) {
|
||||
// batch lazy load many into existing contextBean
|
||||
localBean = contextBean;
|
||||
lazyLoadMany = true;
|
||||
}
|
||||
for (SqlTreeNode child : children) {
|
||||
child.load(ctx, localBean, contextBean);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLazyLoadManyRoot() {
|
||||
return queryMode == Mode.LAZYLOAD_MANY && isRoot();
|
||||
}
|
||||
|
||||
private EntityBean getContextBean() {
|
||||
return contextBean;
|
||||
}
|
||||
|
||||
private void postLoad() {
|
||||
if (!lazyLoadMany && localBean != null) {
|
||||
ctx.setCurrentPrefix(prefix, pathMap);
|
||||
if (readIdNormal) {
|
||||
createListProxies();
|
||||
}
|
||||
if (temporalMode == SpiQuery.TemporalMode.DRAFT) {
|
||||
localDesc.setDraft(localBean);
|
||||
}
|
||||
localDesc.postLoad(localBean);
|
||||
|
||||
EntityBeanIntercept ebi = localBean._ebean_getIntercept();
|
||||
ebi.setPersistenceContext(persistenceContext);
|
||||
if (Mode.LAZYLOAD_BEAN == queryMode) {
|
||||
// Lazy Load does not reset the dirty state
|
||||
ebi.setLoadedLazy();
|
||||
} else if (readId) {
|
||||
// normal bean loading
|
||||
ebi.setLoaded();
|
||||
}
|
||||
|
||||
if (disableLazyLoad) {
|
||||
// bean does not have an Id or is SqlSelect based
|
||||
ebi.setDisableLazyLoad(true);
|
||||
} else if (partialObject) {
|
||||
if (readId) {
|
||||
// register for lazy loading
|
||||
ctx.register(null, ebi);
|
||||
}
|
||||
} else {
|
||||
ebi.setFullyLoadedBean(true);
|
||||
}
|
||||
|
||||
if (ctx.isAutoTuneProfiling() && !disableLazyLoad) {
|
||||
// collect autoTune profiling for this bean...
|
||||
ctx.profileBean(ebi, prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create lazy loading proxies for the Many's except for the one that is
|
||||
* included in the actual query.
|
||||
*/
|
||||
private void createListProxies() {
|
||||
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
|
||||
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
|
||||
for (STreePropertyAssocMany many : localDesc.propsMany()) {
|
||||
if (many != fetchedMany) {
|
||||
// create a proxy for the many (deferred fetching)
|
||||
BeanCollection<?> ref = many.createReference(localBean, forceNewReference);
|
||||
if (ref != null) {
|
||||
if (disableLazyLoad) {
|
||||
ref.setDisableLazyLoad(true);
|
||||
}
|
||||
if (!ref.isRegisteredWithLoadContext()) {
|
||||
ctx.register(many.asMany(), ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setBeanToParent() {
|
||||
if (parentBean != null) {
|
||||
// set this back to the parentBean
|
||||
nodeBeanProp.setValue(parentBean, contextBean);
|
||||
}
|
||||
}
|
||||
|
||||
private EntityBean complete() {
|
||||
if (!readIdNormal) {
|
||||
// a bean with no Id (never found in context)
|
||||
if (lazyLoadParentId != null) {
|
||||
ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId);
|
||||
}
|
||||
return localBean;
|
||||
} else {
|
||||
if (lazyLoadParentId != null) {
|
||||
ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId);
|
||||
}
|
||||
return contextBean;
|
||||
}
|
||||
}
|
||||
|
||||
private void initialise() throws SQLException {
|
||||
initLazyParent();
|
||||
initBeanType();
|
||||
initPersistenceContext();
|
||||
readId();
|
||||
initSqlLoadBean();
|
||||
loadProperties();
|
||||
loadChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the load returning the loaded bean.
|
||||
*/
|
||||
final EntityBean perform() throws SQLException {
|
||||
initialise();
|
||||
if (isLazyLoadManyRoot()) {
|
||||
return getContextBean();
|
||||
}
|
||||
postLoad();
|
||||
setBeanToParent();
|
||||
return complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean was already in the context. If already in the
|
||||
* context we need to check if it is already contained in the collection.
|
||||
*/
|
||||
final boolean isContextBean() {
|
||||
return localBean == null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read the properties from the resultSet.
|
||||
*/
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException {
|
||||
return createLoad(ctx, parentBean).perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the loader with or without inheritance.
|
||||
*/
|
||||
final Load createLoad(DbReadContext ctx, EntityBean parentBean) {
|
||||
return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void appendGroupBy(DbSqlContext ctx, boolean subQuery) {
|
||||
ctx.pushJoin(prefix);
|
||||
@@ -700,4 +389,33 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void unselectLobs() {
|
||||
if (children != null) {
|
||||
for (SqlTreeNode child : children) {
|
||||
child.unselectLobs();
|
||||
}
|
||||
}
|
||||
if (hasLob()) {
|
||||
List<STreeProperty> lst = new ArrayList<>();
|
||||
for (STreeProperty prop : properties) {
|
||||
if (!prop.isDbLob()) {
|
||||
lst.add(prop);
|
||||
}
|
||||
}
|
||||
properties = lst.toArray(new STreeProperty[0]);
|
||||
partialObject = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasLob() {
|
||||
for (STreeProperty prop : properties) {
|
||||
if (prop.isDbLob()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTreeLoad createLoad() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleProperty() {
|
||||
return false;
|
||||
@@ -73,11 +78,6 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getSingleAttributeReader() {
|
||||
throw new IllegalStateException("No expected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the extra join is a many join.
|
||||
* <p>
|
||||
@@ -184,14 +184,6 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
public void appendWhere(DbSqlContext ctx) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing.
|
||||
*/
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMany() {
|
||||
return manyJoin;
|
||||
|
||||
+4
-10
@@ -33,13 +33,13 @@ final class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleProperty() {
|
||||
return true;
|
||||
public SqlTreeLoad createLoad() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getSingleAttributeReader() {
|
||||
throw new IllegalStateException("No expected");
|
||||
public boolean isSingleProperty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,12 +98,6 @@ final class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) {
|
||||
// nothing to do here
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMany() {
|
||||
return true;
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
|
||||
|
||||
private final STreePropertyAssocMany manyProp;
|
||||
final STreePropertyAssocMany manyProp;
|
||||
|
||||
SqlTreeNodeManyRoot(String prefix, STreePropertyAssocMany prop, SqlTreeProperties props, List<SqlTreeNode> myList,
|
||||
boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
|
||||
@@ -19,22 +16,13 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMany() {
|
||||
return true;
|
||||
public SqlTreeLoad createLoad() {
|
||||
return new SqlTreeLoadManyRoot(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
|
||||
// pass in null for parentBean because added to a collection rather than set to the parentBean
|
||||
SqlTreeNodeBean.Load load = createLoad(cquery, null);
|
||||
EntityBean detailBean = load.perform();
|
||||
if (contextParent != null) {
|
||||
// Add to the collection and initialise collection if needed
|
||||
// A null detailBean may initialise an empty collection
|
||||
// Check for bean existing in collection based on load.isContextBean()
|
||||
manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean());
|
||||
}
|
||||
return detailBean;
|
||||
public boolean hasMany() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-13
@@ -1,10 +1,7 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
|
||||
@@ -37,13 +34,13 @@ final class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleProperty() {
|
||||
return false;
|
||||
public SqlTreeLoad createLoad() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getSingleAttributeReader() {
|
||||
throw new IllegalStateException("No expected");
|
||||
public boolean isSingleProperty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,12 +129,6 @@ final class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) {
|
||||
// nothing to do here
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMany() {
|
||||
return true;
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.deploy.DbReadContext;
|
||||
import io.ebeaninternal.server.deploy.DbSqlContext;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents the root node of the Sql Tree.
|
||||
*/
|
||||
final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot {
|
||||
final class SqlTreeNodeRoot extends SqlTreeNodeBean {
|
||||
|
||||
private final TableJoin includeJoin;
|
||||
private final boolean sqlDistinct;
|
||||
@@ -34,27 +29,8 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRoot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean load(DbReadContext ctx) throws SQLException {
|
||||
return load(ctx, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the version bean.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
|
||||
// read the sys period lower and upper bounds
|
||||
// these are always the first 2 columns in the resultSet
|
||||
Timestamp start = ctx.getDataReader().getTimestamp();
|
||||
Timestamp end = ctx.getDataReader().getTimestamp();
|
||||
T bean = (T) load(ctx, null, null);
|
||||
return new Version<>(bean, start, end);
|
||||
public SqlTreeLoad createLoad() {
|
||||
return new SqlTreeLoadRoot(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
final class SqlTreePlan {
|
||||
|
||||
private final SqlTreeLoad rootNode;
|
||||
private final STreePropertyAssocMany manyProperty;
|
||||
private final STreeProperty[] encryptedProps;
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
SqlTreePlan(SqlTreeLoad rootNode, STreePropertyAssocMany manyProperty, STreeProperty[] encryptedProps, Set<String> dependentTables) {
|
||||
this.rootNode = rootNode;
|
||||
this.manyProperty = manyProperty;
|
||||
this.encryptedProps = encryptedProps;
|
||||
this.dependentTables = dependentTables;
|
||||
}
|
||||
|
||||
SqlTreeRoot getRootNode() {
|
||||
return (SqlTreeRoot)rootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property that is associated with the many. There can only be one
|
||||
* per SqlSelect. This can be null.
|
||||
*/
|
||||
STreePropertyAssocMany getManyProperty() {
|
||||
return manyProperty;
|
||||
}
|
||||
|
||||
STreeProperty[] getEncryptedProps() {
|
||||
return encryptedProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tables that are joined in this query.
|
||||
*/
|
||||
Set<String> dependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -4,11 +4,13 @@ import io.ebean.ProfileLocation;
|
||||
import io.ebean.config.ProfilingConfig;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.util.IOUtils;
|
||||
import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.api.SpiProfileHandler;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
@@ -123,7 +125,7 @@ public final class DefaultProfileHandler implements SpiProfileHandler, Plugin {
|
||||
try {
|
||||
String now = DTF.format(LocalDateTime.now());
|
||||
File file = new File(dir, "txprofile-" + now + ".tprofile");
|
||||
out = new BufferedWriter(new FileWriter(file));
|
||||
out = IOUtils.newWriter(file);
|
||||
} catch (IOException e) {
|
||||
log.error("Not expected", e);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebeaninternal.server.core.BasicTypeConverter;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,8 @@ interface ArrayElementConverter<T> {
|
||||
*/
|
||||
ArrayElementConverter<Double> DOUBLE = new DoubleConverter();
|
||||
|
||||
ArrayElementConverter<BigDecimal> BIG_DECIMAL = new BigDecimalConverter();
|
||||
|
||||
class LongConverter implements ArrayElementConverter<Long> {
|
||||
|
||||
@Override
|
||||
@@ -99,6 +103,23 @@ interface ArrayElementConverter<T> {
|
||||
}
|
||||
}
|
||||
|
||||
class BigDecimalConverter implements ArrayElementConverter<BigDecimal> {
|
||||
|
||||
@Override
|
||||
public BigDecimal fromSerialized(Object rawValue) {
|
||||
return fromDbArray(rawValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal fromDbArray(Object rawValue) {
|
||||
if (rawValue instanceof BigDecimal) {
|
||||
return (BigDecimal) rawValue;
|
||||
} else {
|
||||
return BasicTypeConverter.toBigDecimal(rawValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String converter (noop based).
|
||||
*/
|
||||
|
||||
@@ -95,6 +95,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
private final TypeJsonManager jsonManager;
|
||||
private final boolean offlineMigrationGeneration;
|
||||
private final EnumType defaultEnumType;
|
||||
private final DatabasePlatform databasePlatform;
|
||||
|
||||
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
|
||||
|
||||
@@ -132,7 +133,8 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
this.typeMap = new ConcurrentHashMap<>();
|
||||
this.nativeMap = new ConcurrentHashMap<>();
|
||||
this.logicalMap = new ConcurrentHashMap<>();
|
||||
this.postgres = isPostgres(config.getDatabasePlatform());
|
||||
this.databasePlatform = config.getDatabasePlatform();
|
||||
this.postgres = isPostgresCompatible(config.getDatabasePlatform());
|
||||
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
|
||||
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
|
||||
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.getJsonMutationDetection()) : null;
|
||||
@@ -193,13 +195,18 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
List<? extends ScalarType<?>> types = plugin.createTypes(config, objectMapper);
|
||||
for (ScalarType<?> type : types) {
|
||||
log.debug("adding ScalarType {}", type.getClass());
|
||||
addCustomType(type);
|
||||
add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPostgres(DatabasePlatform databasePlatform) {
|
||||
return databasePlatform.getPlatform().base() == Platform.POSTGRES;
|
||||
private boolean isPostgresCompatible(DatabasePlatform databasePlatform) {
|
||||
return databasePlatform.isPlatform(Platform.POSTGRES)
|
||||
|| databasePlatform.isPlatform(Platform.YUGABYTE);
|
||||
}
|
||||
|
||||
private boolean hstoreSupport() {
|
||||
return databasePlatform.isPlatform(Platform.POSTGRES);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,25 +218,6 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
logAdd(scalarType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the ScalarType for an enum. This is special in the sense that an Enum
|
||||
* can have many classes if it uses method overrides and we need to register all
|
||||
* the variations/classes for the enum.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public void addEnumType(ScalarType<?> scalarType, Class<? extends Enum> enumClass) {
|
||||
Set<Class<?>> mappedClasses = new HashSet<>();
|
||||
mappedClasses.add(enumClass);
|
||||
for (Object value : EnumSet.allOf(enumClass).toArray()) {
|
||||
mappedClasses.add(value.getClass());
|
||||
}
|
||||
for (Class<?> cls : mappedClasses) {
|
||||
typeMap.put(cls, scalarType);
|
||||
}
|
||||
logAdd(scalarType);
|
||||
}
|
||||
|
||||
private void logAdd(ScalarType<?> scalarType) {
|
||||
if (log.isTraceEnabled()) {
|
||||
String msg = "ScalarType register [" + scalarType.getClass().getName() + "]";
|
||||
@@ -275,16 +263,43 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
"ScalarType of Joda LocalTime not defined. You need to set DatabaseConfig.jodaLocalTimeMode to"
|
||||
+ " either 'normal' or 'utc'. UTC is the old mode using UTC timezone but local time zone is now preferred as 'normal' mode.");
|
||||
}
|
||||
found = checkInterfaceTypes(type);
|
||||
found = checkInheritedTypes(type);
|
||||
}
|
||||
return found;
|
||||
return found != ScalarTypeNotFound.INSTANCE ? found : null; // Do not return ScalarTypeNotFound, otherwise checks will fail
|
||||
}
|
||||
|
||||
private ScalarType<?> checkInterfaceTypes(Class<?> type) {
|
||||
if (java.nio.file.Path.class.isAssignableFrom(type)) {
|
||||
return typeMap.get(java.nio.file.Path.class);
|
||||
/**
|
||||
* Checks the typeMap for inherited types.
|
||||
*
|
||||
* If e.g. <code>type</code> is a <code>GregorianCalendar</code>, then this method
|
||||
* will check the class hierarchy and will probably return a
|
||||
* <code>ScalarTypeCalendar</code> To speed up a second lookup, it will write
|
||||
* back the found scalarType to typeMap.
|
||||
*
|
||||
* @param type the for which to search for a <code>ScalarType</code>
|
||||
* @return either a valid <code>ScalarType</code> if one could be found or {@link ScalarTypeNotFound#INSTANCE} if not
|
||||
*/
|
||||
private ScalarType<?> checkInheritedTypes(Class<?> type) {
|
||||
// first step loop through inheritance chain
|
||||
Class<?> parent = type;
|
||||
while (parent != null && parent != Object.class) {
|
||||
ScalarType<?> found = typeMap.get(parent);
|
||||
if (found != null && found != ScalarTypeNotFound.INSTANCE) {
|
||||
typeMap.put(type, found); // store type for next lookup
|
||||
return found;
|
||||
}
|
||||
// second step - loop through interfaces of this type
|
||||
for (Class<?> iface: parent.getInterfaces()) {
|
||||
found = checkInheritedTypes(iface);
|
||||
if (found != null && found != ScalarTypeNotFound.INSTANCE) {
|
||||
typeMap.put(type, found); // store type for next lookup
|
||||
return found;
|
||||
}
|
||||
}
|
||||
parent = parent.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
typeMap.put(type, ScalarTypeNotFound.INSTANCE);
|
||||
return ScalarTypeNotFound.INSTANCE; // no success
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -294,7 +309,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getDbMapScalarType() {
|
||||
return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR, false);
|
||||
return hstoreSupport() ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -389,7 +404,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
|
||||
private DocPropertyType getDocType(Type genericType) {
|
||||
if (genericType instanceof Class<?>) {
|
||||
ScalarType<?> found = typeMap.get(genericType);
|
||||
ScalarType<?> found = getScalarType((Class<?>)genericType);
|
||||
if (found != null) {
|
||||
return found.getDocType();
|
||||
}
|
||||
@@ -447,7 +462,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
return scalarType;
|
||||
}
|
||||
|
||||
scalarType = typeMap.get(type);
|
||||
scalarType = getScalarType(type);
|
||||
if (scalarType != null) {
|
||||
if (jdbcType == 0 || scalarType.getJdbcType() == jdbcType) {
|
||||
// matching type
|
||||
@@ -565,7 +580,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
// use JPA normal Enum type (without mapping)
|
||||
scalarEnum = createEnumScalarTypePerSpec(enumType, type);
|
||||
}
|
||||
addEnumType(scalarEnum, enumType);
|
||||
add(scalarEnum);
|
||||
return scalarEnum;
|
||||
}
|
||||
|
||||
@@ -655,16 +670,13 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
scalarType = cls.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
}
|
||||
addCustomType(scalarType);
|
||||
add(scalarType);
|
||||
} catch (Exception e) {
|
||||
log.error("Error loading ScalarType [" + cls.getName() + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addCustomType(ScalarType<?> scalarType) {
|
||||
add(scalarType);
|
||||
}
|
||||
|
||||
private Object initObjectMapper(DatabaseConfig config) {
|
||||
Object objectMapper = config.getObjectMapper();
|
||||
@@ -735,7 +747,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
jsonNodeVarchar = new ScalarTypeJsonNode.Varchar(mapper);
|
||||
jsonNodeJson = jsonNodeClob; // Default for non-Postgres databases
|
||||
jsonNodeJsonb = jsonNodeClob; // Default for non-Postgres databases
|
||||
if (isPostgres(config.getDatabasePlatform())) {
|
||||
if (postgres) {
|
||||
jsonNodeJson = new ScalarTypeJsonNodePostgres.JSON(mapper);
|
||||
jsonNodeJsonb = new ScalarTypeJsonNodePostgres.JSONB(mapper);
|
||||
}
|
||||
@@ -750,7 +762,11 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
|
||||
typeMap.put(java.nio.file.Path.class, new ScalarTypePath());
|
||||
addType(java.time.Period.class, new ScalarTypePeriod());
|
||||
addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate));
|
||||
if (config.getDatabasePlatform().supportsNativeJavaTime()) {
|
||||
addType(java.time.LocalDate.class, new ScalarTypeLocalDateNative(jsonDate));
|
||||
} else {
|
||||
addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate));
|
||||
}
|
||||
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime));
|
||||
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime, zoneId));
|
||||
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime, zoneId));
|
||||
@@ -790,7 +806,11 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
log.debug("Registering Joda data types");
|
||||
addType(LocalDateTime.class, new ScalarTypeJodaLocalDateTime(jsonDateTime));
|
||||
addType(DateTime.class, new ScalarTypeJodaDateTime(jsonDateTime));
|
||||
addType(LocalDate.class, new ScalarTypeJodaLocalDate(jsonDate));
|
||||
if (config.getDatabasePlatform().supportsNativeJavaTime()) {
|
||||
addType(LocalDate.class, new ScalarTypeJodaLocalDateNative(jsonDate));
|
||||
} else {
|
||||
addType(LocalDate.class, new ScalarTypeJodaLocalDate(jsonDate));
|
||||
}
|
||||
addType(org.joda.time.DateMidnight.class, new ScalarTypeJodaDateMidnight(jsonDate));
|
||||
addType(org.joda.time.Period.class, new ScalarTypeJodaPeriod());
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ final class EnumToDbIntegerMap extends EnumToDbValueMap<Integer> {
|
||||
addInternal(beanValue, dbValue, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBeanValue(Object dbValue) {
|
||||
if (dbValue instanceof String) {
|
||||
return super.getBeanValue(Integer.parseInt(dbValue.toString()));
|
||||
}
|
||||
return super.getBeanValue(dbValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumToDbIntegerMap add(Object beanValue, String stringDbValue, String name) {
|
||||
try {
|
||||
|
||||
@@ -116,7 +116,8 @@ abstract class EnumToDbValueMap<T> {
|
||||
/**
|
||||
* Return the Bean value given the DB value.
|
||||
*/
|
||||
public Object getBeanValue(T dbValue) {
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
public Object getBeanValue(Object dbValue) {
|
||||
if (dbValue == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -125,8 +126,7 @@ abstract class EnumToDbValueMap<T> {
|
||||
beanValue = nameMap.get(dbValue);
|
||||
}
|
||||
if (beanValue == null && !allowNulls) {
|
||||
String msg = "Bean value for " + dbValue + " not found in " + valueMap;
|
||||
throw new IllegalArgumentException(msg);
|
||||
throw new IllegalArgumentException("Bean value for " + dbValue + " not found in " + valueMap);
|
||||
}
|
||||
return beanValue;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ public class RsetDataReader implements DataReader {
|
||||
return rset.getObject(pos());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getObject(Class<T> cls) throws SQLException {
|
||||
return rset.getObject(pos(), cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getBigDecimal() throws SQLException {
|
||||
return rset.getBigDecimal(pos());
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.ebeaninternal.json.ModifyAwareList;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
@@ -59,6 +60,9 @@ class ScalarTypeArrayList extends ScalarTypeArrayBase<List> implements ScalarTyp
|
||||
if (valueType.equals(Double.class)) {
|
||||
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE));
|
||||
}
|
||||
if (valueType.equals(BigDecimal.class)) {
|
||||
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "decimal", DocPropertyType.DOUBLE, ArrayElementConverter.BIG_DECIMAL));
|
||||
}
|
||||
if (valueType.equals(String.class)) {
|
||||
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.HashMap;
|
||||
@@ -50,6 +51,9 @@ final class ScalarTypeArrayListH2 extends ScalarTypeArrayList {
|
||||
if (valueType.equals(Double.class)) {
|
||||
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE));
|
||||
}
|
||||
if (valueType.equals(BigDecimal.class)) {
|
||||
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.BIG_DECIMAL));
|
||||
}
|
||||
if (valueType.equals(String.class)) {
|
||||
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING));
|
||||
}
|
||||
|
||||
@@ -189,7 +189,12 @@ final class ScalarTypeEnumStandard {
|
||||
if (dbValue == null || dbValue instanceof Enum<?>) {
|
||||
return dbValue;
|
||||
}
|
||||
int ordinal = (Integer) dbValue;
|
||||
int ordinal;
|
||||
if (dbValue instanceof Integer) {
|
||||
ordinal = (Integer) dbValue;
|
||||
} else {
|
||||
ordinal = Integer.parseInt(dbValue.toString());
|
||||
}
|
||||
if (ordinal < 0 || ordinal >= enumArray.length) {
|
||||
String m = "Unexpected ordinal [" + ordinal + "] out of range [" + enumArray.length + "]";
|
||||
throw new IllegalStateException(m);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebeaninternal.server.core.BasicTypeConverter;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.Types;
|
||||
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebeaninternal.server.core.BasicTypeConverter;
|
||||
|
||||
/**
|
||||
* ScalarType for Joda LocalDate. This maps to a JDBC Date.
|
||||
*/
|
||||
final class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
|
||||
ScalarTypeJodaLocalDate(JsonConfig.Date mode) {
|
||||
super(mode, LocalDate.class, false, Types.DATE);
|
||||
@@ -40,7 +41,7 @@ final class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public Date convertToDate(LocalDate value) {
|
||||
return new Date(value .getYear() - 1900, value .getMonthOfYear() - 1, value .getDayOfMonth());
|
||||
return new Date(value.getYear() - 1900, value.getMonthOfYear() - 1, value.getDayOfMonth());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,9 +55,8 @@ final class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
@Override
|
||||
public LocalDate toBeanType(Object value) {
|
||||
if (value instanceof java.util.Date) {
|
||||
return LocalDate.fromDateFields((java.util.Date)value);
|
||||
return LocalDate.fromDateFields((java.util.Date) value);
|
||||
}
|
||||
return (LocalDate) value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
|
||||
/**
|
||||
* ScalarType for Joda LocalDate. This maps to a LocalDate. Not all drivers/platforms may support this.
|
||||
*/
|
||||
final class ScalarTypeJodaLocalDateNative extends ScalarTypeJodaLocalDate{
|
||||
|
||||
ScalarTypeJodaLocalDateNative(JsonConfig.Date mode) {
|
||||
super(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBinder binder, LocalDate value) throws SQLException {
|
||||
if (value == null) {
|
||||
binder.setNull(Types.DATE);
|
||||
} else {
|
||||
binder.setObject(java.time.LocalDate.of(value.getYear(), value.getMonthOfYear(), value.getDayOfMonth()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate read(DataReader reader) throws SQLException {
|
||||
java.time.LocalDate jtDate = reader.getObject(java.time.LocalDate.class);
|
||||
return jtDate == null ? null : new org.joda.time.LocalDate(jtDate.getYear(), jtDate.getMonthValue(), jtDate.getDayOfMonth());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.text.TextException;
|
||||
import io.ebean.text.json.EJson;
|
||||
import io.ebean.util.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -75,14 +76,14 @@ abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
|
||||
try {
|
||||
if (keepSource) {
|
||||
StringWriter jsonBuffer = new StringWriter();
|
||||
try (InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
|
||||
try (Reader streamReader = IOUtils.newReader(is)) {
|
||||
transferTo(streamReader, jsonBuffer);
|
||||
}
|
||||
String rawJson = jsonBuffer.toString();
|
||||
reader.pushJson(rawJson);
|
||||
return parse(rawJson);
|
||||
} else {
|
||||
try (InputStreamReader streamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
|
||||
try (Reader streamReader = IOUtils.newReader(is)) {
|
||||
return parse(streamReader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.text.TextException;
|
||||
import io.ebean.util.IOUtils;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
@@ -68,7 +68,7 @@ abstract class ScalarTypeJsonNode extends ScalarTypeBase<JsonNode> {
|
||||
if (is == null) {
|
||||
return null;
|
||||
}
|
||||
try (InputStreamReader reader = new InputStreamReader(is)) {
|
||||
try (Reader reader = IOUtils.newReader(is)) {
|
||||
return parse(reader);
|
||||
} catch (IOException e) {
|
||||
throw new SQLException("Error reading Blob stream from DB", e);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebeaninternal.server.core.BasicTypeConverter;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
@@ -10,10 +7,13 @@ import java.time.LocalDate;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebeaninternal.server.core.BasicTypeConverter;
|
||||
|
||||
/**
|
||||
* ScalarType for java.time.LocalDate. This maps to a JDBC Date.
|
||||
*/
|
||||
final class ScalarTypeLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
class ScalarTypeLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
|
||||
ScalarTypeLocalDate(JsonConfig.Date mode) {
|
||||
super(mode, LocalDate.class, false, Types.DATE);
|
||||
@@ -60,5 +60,4 @@ final class ScalarTypeLocalDate extends ScalarTypeBaseDate<LocalDate> {
|
||||
}
|
||||
return (LocalDate) value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
|
||||
/**
|
||||
* ScalarType for java.time.LocalDate. This maps to a LocalDate. Not all drivers/platforms may support this.
|
||||
*/
|
||||
final class ScalarTypeLocalDateNative extends ScalarTypeLocalDate {
|
||||
|
||||
ScalarTypeLocalDateNative(JsonConfig.Date mode) {
|
||||
super(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBinder binder, LocalDate value) throws SQLException {
|
||||
if (value == null) {
|
||||
binder.setNull(Types.DATE);
|
||||
} else {
|
||||
binder.setObject(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate read(DataReader reader) throws SQLException {
|
||||
return reader.getObject(LocalDate.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package io.ebeaninternal.server.type;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
|
||||
/**
|
||||
* Class is required as "null" key in ConcurrentHashMap.
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
class ScalarTypeNotFound implements ScalarType<Void> {
|
||||
|
||||
public static final ScalarTypeNotFound INSTANCE = new ScalarTypeNotFound();
|
||||
private ScalarTypeNotFound() { }
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJdbcNative() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getJdbcType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Void> getType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void read(DataReader reader) throws SQLException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadIgnore(DataReader reader) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBinder binder, Void value) throws SQLException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toJdbcType(Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void toBeanType(Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatValue(Void value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void parse(String value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocPropertyType getDocType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDateTimeCapable() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long asVersion(Void value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void convertFromMillis(long dateTime) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void readData(DataInput dataInput) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(DataOutput dataOutput, Void v) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void jsonRead(JsonParser parser) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(JsonGenerator writer, Void value) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public final class SimpleAesEncryptor implements Encryptor {
|
||||
}
|
||||
|
||||
private IvParameterSpec getIvParameterSpec(String initialVector) {
|
||||
return new IvParameterSpec(initialVector.getBytes());
|
||||
return new IvParameterSpec(initialVector.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,12 +17,6 @@ public interface TypeManager {
|
||||
*/
|
||||
void add(ScalarType<?> scalarType);
|
||||
|
||||
/**
|
||||
* Register a ScalarType for an Enum with can have multiple classes.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
void addEnumType(ScalarType<?> type, Class<? extends Enum> myEnumClass);
|
||||
|
||||
/**
|
||||
* Return the scalar type for the given logical type.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean ddl generation</name>
|
||||
@@ -22,20 +22,20 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>12.12.1</version>
|
||||
<version>12.13.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.14.0</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.config.DatabaseConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.ddlrunner.DdlRunner;
|
||||
import io.ebean.ddlrunner.ScriptTransform;
|
||||
import io.ebean.util.IOUtils;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.SpiDdlGenerator;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
@@ -17,13 +18,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -50,6 +49,7 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
private final ScriptTransform scriptTransform;
|
||||
private final Platform platform;
|
||||
private final String platformName;
|
||||
private final boolean useMigrationStoredProcedures;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropAllContent;
|
||||
@@ -71,9 +71,11 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
log.warn("DDL can't be run on startup with TenantMode " + config.getTenantMode());
|
||||
this.runDdl = false;
|
||||
this.ddlAutoCommit = false;
|
||||
this.useMigrationStoredProcedures = false;
|
||||
} else {
|
||||
this.runDdl = config.isDdlRun();
|
||||
this.ddlAutoCommit = databasePlatform.isDdlAutoCommit();
|
||||
this.useMigrationStoredProcedures = config.getDatabasePlatform().isUseMigrationStoredProcedures();
|
||||
}
|
||||
this.scriptTransform = createScriptTransform(config);
|
||||
this.baseDir = initBaseDir();
|
||||
@@ -187,7 +189,7 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
|
||||
protected void runDropSql(Connection connection) throws IOException {
|
||||
if (!createOnly) {
|
||||
if (extraDdl && jaxbPresent) {
|
||||
if (extraDdl && jaxbPresent && useMigrationStoredProcedures) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(platform, true);
|
||||
if (extraApply != null) {
|
||||
runScript(connection, false, extraApply, "extra-ddl");
|
||||
@@ -268,7 +270,7 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
if (is == null) {
|
||||
log.warn("sql script {} was not found as a resource", sqlScript);
|
||||
} else {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
String content = readContent(IOUtils.newReader(is)); // 'is' is closed
|
||||
runScript(connection, false, content, sqlScript);
|
||||
}
|
||||
}
|
||||
@@ -337,7 +339,7 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
|
||||
protected void writeFile(String fileName, String fileContent) throws IOException {
|
||||
File f = new File(baseDir, fileName);
|
||||
try (FileWriter fw = new FileWriter(f)) {
|
||||
try (Writer fw = IOUtils.newWriter(f)) {
|
||||
fw.write(fileContent);
|
||||
fw.flush();
|
||||
}
|
||||
@@ -348,7 +350,9 @@ public class DdlGenerator implements SpiDdlGenerator {
|
||||
if (!f.exists()) {
|
||||
return null;
|
||||
}
|
||||
return readContent(new FileReader(f));
|
||||
try (Reader reader = IOUtils.newReader(f)) {
|
||||
return readContent(reader);
|
||||
}
|
||||
}
|
||||
|
||||
protected String readContent(Reader reader) throws IOException {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user