mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
162
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 | ||
|
|
860aad11ce | ||
|
|
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.1</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) {
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -23,6 +23,7 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
super();
|
||||
this.platform = Platform.ORACLE;
|
||||
this.supportsDeleteTableAlias = true;
|
||||
this.maxInBinding = 1000;
|
||||
this.maxTableNameLength = 30;
|
||||
this.maxConstraintNameLength = 30;
|
||||
this.dbEncrypt = new OracleDbEncrypt();
|
||||
|
||||
+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;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.1</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.1</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.1</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.1</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.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-xml</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-autotune</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.1</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.1</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.1</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.1</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.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.14.1</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;
|
||||
|
||||
@@ -8,15 +8,15 @@ 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.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,27 +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?");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = UrlHelper.openNoCache(resource);
|
||||
Reader reader = IOUtils.newReader(inputStream)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -85,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);
|
||||
@@ -110,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;
|
||||
|
||||
@@ -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;
|
||||
@@ -21,12 +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.*;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Create a DatabasePlatform from the configuration.
|
||||
@@ -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")) {
|
||||
@@ -192,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:
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -15,6 +15,8 @@ public final class DbExpressionHandlerFactory {
|
||||
case H2:
|
||||
return new H2DbExpression();
|
||||
case POSTGRES:
|
||||
case YUGABYTE:
|
||||
case COCKROACH:
|
||||
return new PostgresDbExpression();
|
||||
case MARIADB:
|
||||
return new MariaDbExpression();
|
||||
@@ -22,6 +24,8 @@ public final class DbExpressionHandlerFactory {
|
||||
return new MySqlDbExpression();
|
||||
case ORACLE:
|
||||
return new OracleDbExpression();
|
||||
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();
|
||||
|
||||
@@ -181,6 +181,8 @@ final class InsertMeta {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -198,8 +200,13 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,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
|
||||
@@ -740,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);
|
||||
}
|
||||
@@ -755,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));
|
||||
@@ -795,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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean ddl generation</name>
|
||||
@@ -28,14 +28,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.14.1</version>
|
||||
<version>12.15.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -49,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;
|
||||
@@ -70,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();
|
||||
@@ -186,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");
|
||||
|
||||
+20
-7
@@ -24,7 +24,10 @@ import io.ebean.config.PropertiesWrapper;
|
||||
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;
|
||||
@@ -397,19 +400,22 @@ public class DefaultDbMigration implements DbMigration {
|
||||
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException {
|
||||
if (dbPlatform != null) {
|
||||
if (tablePartitioning && includeBuiltInPartitioning) {
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning());
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning(), false);
|
||||
}
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
|
||||
// skip built-in migration stored procedures based on isUseMigrationStoredProcedures
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin(), true);
|
||||
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
|
||||
private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl, boolean checkSkip) throws IOException {
|
||||
if (extraDdl != null) {
|
||||
List<DdlScript> ddlScript = extraDdl.getDdlScript();
|
||||
for (DdlScript script : ddlScript) {
|
||||
if (!script.isDrop() && matchPlatform(dbPlatform.getPlatform(), script.getPlatforms())) {
|
||||
writeExtraDdl(migrationDir, script);
|
||||
if (!checkSkip || dbPlatform.isUseMigrationStoredProcedures()) {
|
||||
writeExtraDdl(migrationDir, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -828,7 +834,14 @@ public class DefaultDbMigration implements DbMigration {
|
||||
case SQLSERVER:
|
||||
throw new IllegalArgumentException("Please choose the more specific SQLSERVER16 or SQLSERVER17 platform. Refer to issue #1340 for details");
|
||||
case DB2:
|
||||
return new DB2Platform();
|
||||
logger.warn("Using DB2LegacyPlatform. It is recommended to migrate to db2luw/db2zos/db2fori. Refer to issue #2514 for details");
|
||||
return new DB2LegacyPlatform();
|
||||
case DB2LUW:
|
||||
return new DB2LuwPlatform();
|
||||
case DB2ZOS:
|
||||
return new DB2ZosPlatform();
|
||||
case DB2FORI:
|
||||
return new DB2ForIPlatform();
|
||||
case SQLITE:
|
||||
return new SQLitePlatform();
|
||||
case HANA:
|
||||
|
||||
+6
-15
@@ -1,21 +1,7 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.ClickHouseDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HanaColumnStoreDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MariaDbDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MySqlDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.NuoDbDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.OracleDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.Postgres9Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.PostgresDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SQLiteDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SqlServerDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.*;
|
||||
|
||||
/**
|
||||
* Builds platform specific DDL handler.
|
||||
@@ -31,6 +17,9 @@ public class PlatformDdlBuilder {
|
||||
case H2:
|
||||
return new H2Ddl(platform);
|
||||
case DB2:
|
||||
case DB2LUW:
|
||||
case DB2FORI:
|
||||
case DB2ZOS:
|
||||
return new DB2Ddl(platform);
|
||||
case MARIADB:
|
||||
return new MariaDbDdl(platform);
|
||||
@@ -50,6 +39,8 @@ public class PlatformDdlBuilder {
|
||||
return new Postgres9Ddl(platform);
|
||||
case POSTGRES:
|
||||
return new PostgresDdl(platform);
|
||||
case YUGABYTE:
|
||||
return new YugabyteDdl(platform);
|
||||
case COCKROACH:
|
||||
return new CockroachDdl(platform);
|
||||
case SQLSERVER16:
|
||||
|
||||
+17
-3
@@ -15,6 +15,8 @@ public class DB2Ddl extends PlatformDdl {
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
this.dropIndexIfExists = "drop index ";
|
||||
this.identitySuffix = " generated by default as identity";
|
||||
this.columnSetNull = "drop not null";
|
||||
this.columnSetType = "set data type ";
|
||||
this.inlineUniqueWhenNullable = false;
|
||||
}
|
||||
|
||||
@@ -22,10 +24,22 @@ public class DB2Ddl extends PlatformDdl {
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
if (nullableColumns == null || nullableColumns.length == 0) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
} else {
|
||||
// Hmm: Complex workaround: https://www.ibm.com/developerworks/mydeveloperworks/blogs/SQLTips4DB2LUW/entry/unique_where_not_null_indexes26?lang=en
|
||||
return "-- NOT SUPPORTED " + super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
}
|
||||
|
||||
if (uqName == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("create unique index ");
|
||||
sb.append(uqName).append(" on ").append(tableName).append('(');
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(columns[i]);
|
||||
}
|
||||
sb.append(") exclude null keys");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+8
-3
@@ -174,16 +174,21 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
}
|
||||
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
createHistoryTableAs(apply, table);
|
||||
createHistoryTableWithPeriod(apply);
|
||||
}
|
||||
|
||||
protected void createHistoryTableAs(DdlBuffer apply, MTable table) throws IOException {
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(table.getName()).append(historySuffix).append("(").newLine();
|
||||
|
||||
Collection<MColumn> cols = table.allColumns();
|
||||
for (MColumn column : cols) {
|
||||
for (MColumn column : table.allColumns()) {
|
||||
if (!column.isDraftOnly()) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType());
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void createHistoryTableWithPeriod(DdlBuffer apply) throws IOException {
|
||||
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
|
||||
apply.append(",").newLine();
|
||||
writeColumnDefinition(apply, sysPeriodEnd, sysPeriodType);
|
||||
|
||||
+12
-1
@@ -7,6 +7,8 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
*/
|
||||
public class H2Ddl extends PlatformDdl {
|
||||
|
||||
private static boolean useV1Syntax = Boolean.getBoolean("ebean.h2.useV1Syntax");
|
||||
|
||||
public H2Ddl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.historyDdl = new H2HistoryDdl();
|
||||
@@ -22,6 +24,15 @@ public class H2Ddl extends PlatformDdl {
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
return "array";
|
||||
if (useV1Syntax) {
|
||||
return "array";
|
||||
}
|
||||
int pos = logicalArrayType.indexOf('[');
|
||||
if (pos == -1) {
|
||||
return logicalArrayType;
|
||||
} else {
|
||||
// trim of the fallback varchar length
|
||||
return logicalArrayType.substring(0, pos) + " array";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -18,12 +18,15 @@ public class MySqlDdl extends PlatformDdl {
|
||||
// this flag is for compatibility. Use it with care.
|
||||
private static final boolean USE_CHECK_CONSTRAINT = Boolean.getBoolean("ebean.mysql.useCheckConstraint");
|
||||
|
||||
private final boolean useMigrationStoredProcedures;
|
||||
|
||||
public MySqlDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.alterColumn = "modify";
|
||||
this.dropUniqueConstraint = "drop index";
|
||||
this.historyDdl = new MySqlHistoryDdl();
|
||||
this.inlineComments = true;
|
||||
this.useMigrationStoredProcedures = platform.isUseMigrationStoredProcedures();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,6 +37,15 @@ public class MySqlDdl extends PlatformDdl {
|
||||
return "drop index " + maxConstraintName(indexName) + " on " + tableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alterTableDropColumn(final DdlBuffer buffer, final String tableName, final String columnName) throws IOException {
|
||||
if (this.useMigrationStoredProcedures) {
|
||||
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
|
||||
} else {
|
||||
super.alterTableDropColumn(buffer, tableName, columnName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the drop foreign key clause.
|
||||
*/
|
||||
|
||||
+2
-10
@@ -22,11 +22,8 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
*/
|
||||
@Override
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
|
||||
String baseTable = table.getName();
|
||||
apply
|
||||
.append("create table ").append(baseTable).append(historySuffix)
|
||||
.append("(like ").append(baseTable).append(")").endOfStatement();
|
||||
apply.append("create table ").append(table.getName()).append(historySuffix)
|
||||
.append("(like ").append(table.getName()).append(")").endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +54,6 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
@Override
|
||||
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
String procedureName = procedureName(baseTableName);
|
||||
String triggerName = triggerName(baseTableName);
|
||||
@@ -110,7 +106,6 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
@Override
|
||||
protected void createStoredFunction(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String procedureName = procedureName(table.getName());
|
||||
String historyTable = historyTableName(table.getName());
|
||||
|
||||
@@ -120,16 +115,13 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
@Override
|
||||
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
String procedureName = procedureName(update.getBaseTable());
|
||||
|
||||
recreateHistoryView(update);
|
||||
createOrReplaceFunction(update.historyTriggerBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
|
||||
|
||||
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
|
||||
appendColumnNames(buffer, columns, "");
|
||||
buffer.append(") values (tstzrange(lowerTs,upperTs), ");
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
public class YugabyteDdl extends PostgresDdl {
|
||||
|
||||
public YugabyteDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.historyDdl = new YugabyteHistoryDdl();
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class YugabyteHistoryDdl extends PostgresHistoryDdl {
|
||||
|
||||
@Override
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
createHistoryTableAs(apply, table);
|
||||
writeColumnDefinition(apply, sysPeriod, "tstzrange");
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
}
|
||||
}
|
||||
@@ -561,6 +561,16 @@ public class MTable {
|
||||
return newCol;
|
||||
}
|
||||
|
||||
public MColumn addColumnScalar(String dbColumn, String columnDefn) {
|
||||
MColumn existingColumn = getColumn(dbColumn);
|
||||
if (existingColumn != null) {
|
||||
return existingColumn;
|
||||
}
|
||||
MColumn newCol = new MColumn(dbColumn, columnDefn);
|
||||
addColumn(newCol);
|
||||
return newCol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 'new column' to the AddColumn migration object.
|
||||
*/
|
||||
|
||||
+2
-6
@@ -246,14 +246,13 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
lastColumn = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// using non-strict mode to render the DB type such that we have a
|
||||
// "logical" type like jsonb(200) that can map to JSONB or VARCHAR(200)
|
||||
MColumn col = new MColumn(p.dbColumn(), ctx.getColumnDefn(p, false));
|
||||
MColumn col = table.addColumnScalar(p.dbColumn(), ctx.getColumnDefn(p, false));
|
||||
//MColumn col = new MColumn(p.dbColumn(), ctx.getColumnDefn(p, false));
|
||||
col.setComment(p.dbComment());
|
||||
col.setDraftOnly(p.isDraftOnly());
|
||||
col.setHistoryExclude(p.isExcludedFromHistory());
|
||||
|
||||
if (p.isId() || p.isImportedPrimaryKey()) {
|
||||
col.setPrimaryKey(true);
|
||||
if (p.descriptor().isUseIdGenerator()) {
|
||||
@@ -279,7 +278,6 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
}
|
||||
|
||||
col.setDbMigrationInfos(p.dbMigrationInfos());
|
||||
|
||||
if (p.isUnique() && !p.isId()) {
|
||||
col.setUnique(uniqueConstraintName(col.getName()));
|
||||
indexSetAdd(col.getName());
|
||||
@@ -293,9 +291,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
col.setCheckConstraint(buildCheckConstraint(p.dbColumn(), checkConstraintValues));
|
||||
col.setCheckConstraintName(checkConstraintName(col.getName()));
|
||||
}
|
||||
|
||||
lastColumn = col;
|
||||
table.addColumn(col);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+83
@@ -112,5 +112,88 @@ BEGIN
|
||||
END
|
||||
GO
|
||||
</ddl-script>
|
||||
<ddl-script name="create procs" platforms="mysql" init="true">-- Inital script to create stored procedures etc for mysql platform
|
||||
DROP PROCEDURE IF EXISTS usp_ebean_drop_foreign_keys;
|
||||
|
||||
delimiter $$
|
||||
--
|
||||
-- PROCEDURE: usp_ebean_drop_foreign_keys TABLE, COLUMN
|
||||
-- deletes all constraints and foreign keys referring to TABLE.COLUMN
|
||||
--
|
||||
CREATE PROCEDURE usp_ebean_drop_foreign_keys(IN p_table_name VARCHAR(255), IN p_column_name VARCHAR(255))
|
||||
BEGIN
|
||||
DECLARE done INT DEFAULT FALSE;
|
||||
DECLARE c_fk_name CHAR(255);
|
||||
DECLARE curs CURSOR FOR SELECT CONSTRAINT_NAME from information_schema.KEY_COLUMN_USAGE
|
||||
WHERE TABLE_SCHEMA = DATABASE() and TABLE_NAME = p_table_name and COLUMN_NAME = p_column_name
|
||||
AND REFERENCED_TABLE_NAME IS NOT NULL;
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
|
||||
|
||||
OPEN curs;
|
||||
|
||||
read_loop: LOOP
|
||||
FETCH curs INTO c_fk_name;
|
||||
IF done THEN
|
||||
LEAVE read_loop;
|
||||
END IF;
|
||||
SET @sql = CONCAT('ALTER TABLE ', p_table_name, ' DROP FOREIGN KEY ', c_fk_name);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
END LOOP;
|
||||
|
||||
CLOSE curs;
|
||||
END
|
||||
$$
|
||||
|
||||
DROP PROCEDURE IF EXISTS usp_ebean_drop_column;
|
||||
|
||||
delimiter $$
|
||||
--
|
||||
-- PROCEDURE: usp_ebean_drop_column TABLE, COLUMN
|
||||
-- deletes the column and ensures that all indices and constraints are dropped first
|
||||
--
|
||||
CREATE PROCEDURE usp_ebean_drop_column(IN p_table_name VARCHAR(255), IN p_column_name VARCHAR(255))
|
||||
BEGIN
|
||||
CALL usp_ebean_drop_foreign_keys(p_table_name, p_column_name);
|
||||
SET @sql = CONCAT('ALTER TABLE ', p_table_name, ' DROP COLUMN ', p_column_name);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
END
|
||||
$$
|
||||
</ddl-script>
|
||||
|
||||
<ddl-script name="create procs" platforms="hana" init="true">-- Inital script to create stored procedures etc for the hana platform
|
||||
delimiter $$
|
||||
--
|
||||
-- PROCEDURE: usp_ebean_drop_foreign_keys TABLE, COLUMN
|
||||
-- deletes all constraints and foreign keys referring to TABLE.COLUMN
|
||||
--
|
||||
CREATE OR REPLACE PROCEDURE usp_ebean_drop_foreign_keys(IN table_name NVARCHAR(256), IN column_name NVARCHAR(256))
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE foreign_key_names TABLE(CONSTRAINT_NAME NVARCHAR(256), TABLE_NAME NVARCHAR(256));
|
||||
DECLARE i INT;
|
||||
|
||||
foreign_key_names = SELECT CONSTRAINT_NAME, TABLE_NAME FROM SYS.REFERENTIAL_CONSTRAINTS WHERE SCHEMA_NAME=CURRENT_SCHEMA AND TABLE_NAME=UPPER(:table_name) AND COLUMN_NAME=UPPER(:column_name);
|
||||
|
||||
FOR I IN 1 .. RECORD_COUNT(:foreign_key_names) DO
|
||||
EXEC 'ALTER TABLE "' || ESCAPE_DOUBLE_QUOTES(:foreign_key_names.TABLE_NAME[i]) || '" DROP CONSTRAINT "' || ESCAPE_DOUBLE_QUOTES(:foreign_key_names.CONSTRAINT_NAME[i]) || '"';
|
||||
END FOR;
|
||||
|
||||
END;
|
||||
$$
|
||||
|
||||
delimiter $$
|
||||
--
|
||||
-- PROCEDURE: usp_ebean_drop_column TABLE, COLUMN
|
||||
-- deletes the column and ensures that all indices and constraints are dropped first
|
||||
--
|
||||
CREATE OR REPLACE PROCEDURE usp_ebean_drop_column(IN table_name NVARCHAR(256), IN column_name NVARCHAR(256))
|
||||
AS
|
||||
BEGIN
|
||||
CALL usp_ebean_drop_foreign_keys(table_name, column_name);
|
||||
EXEC 'ALTER TABLE "' || UPPER(ESCAPE_DOUBLE_QUOTES(table_name)) || '" DROP ("' || UPPER(ESCAPE_DOUBLE_QUOTES(column_name)) || '")';
|
||||
END;
|
||||
$$
|
||||
</ddl-script>
|
||||
</extra-ddl>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user