mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
85
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ac7ea6e60 | ||
|
|
b2e0271ce7 | ||
|
|
c7a7651602 | ||
|
|
1e6678cbb4 | ||
|
|
a6e4e181bf | ||
|
|
b8e1dffca2 | ||
|
|
7e8ed72b7b | ||
|
|
ec8dd30a04 | ||
|
|
f81d5c9f3a | ||
|
|
74b24b1db1 | ||
|
|
179ef8c8a0 | ||
|
|
f77f3c3d6e | ||
|
|
9a7646195c | ||
|
|
84d3578e33 | ||
|
|
ef05b81014 | ||
|
|
726c682984 | ||
|
|
0c22a99fdf | ||
|
|
c062c8e001 | ||
|
|
06f3c000f5 | ||
|
|
8c0232fdb8 | ||
|
|
2470486f20 | ||
|
|
8fbf0374dd | ||
|
|
dd8fff0277 | ||
|
|
73b3d1d977 | ||
|
|
2a76ae2192 | ||
|
|
39931a3a2c | ||
|
|
58eb49617b | ||
|
|
9b621d2ec7 | ||
|
|
f2e8b652fa | ||
|
|
f342d9d561 | ||
|
|
8009bdf099 | ||
|
|
7336ce81bd | ||
|
|
f805bec8f2 | ||
|
|
46cdc84e6b | ||
|
|
e908d16ba8 | ||
|
|
96e3f4138f | ||
|
|
977dd52890 | ||
|
|
a9b7bb2175 | ||
|
|
f83c7fdac8 | ||
|
|
a695fb59b2 | ||
|
|
e197bb2900 | ||
|
|
b54b8815d9 | ||
|
|
1d1fde61be | ||
|
|
683186cbe9 | ||
|
|
5e6be40454 | ||
|
|
c00febd95b | ||
|
|
4c8e1113bf | ||
|
|
6f854da70b | ||
|
|
3f5a162a4e | ||
|
|
d14529b602 | ||
|
|
f471961f06 | ||
|
|
457c1356a4 | ||
|
|
419ac2bf50 | ||
|
|
727d088577 | ||
|
|
972d2dcc6b | ||
|
|
a5bcd49ecb | ||
|
|
ff496ad2d3 | ||
|
|
24d438ba23 | ||
|
|
591067e39e | ||
|
|
38a95dc5c4 | ||
|
|
53426d8493 | ||
|
|
a06238558e | ||
|
|
b3a3b19d5d | ||
|
|
57231c80a8 | ||
|
|
dc96fb9ad5 | ||
|
|
567847b47a | ||
|
|
b74fed7073 | ||
|
|
cdaf8ee6d5 | ||
|
|
c6c5073e47 | ||
|
|
2a58b954e9 | ||
|
|
9772623880 | ||
|
|
27b6183aad | ||
|
|
55e19618f8 | ||
|
|
5c60ba4d7f | ||
|
|
b9cc584086 | ||
|
|
bf5a8c2fad | ||
|
|
66138e5ea2 | ||
|
|
75fb89ef76 | ||
|
|
a2cb468222 | ||
|
|
166bcd7c45 | ||
|
|
95aed6af54 | ||
|
|
7a697c12af | ||
|
|
23278ce30b | ||
|
|
36a5610e25 | ||
|
|
08263c4588 |
+152
@@ -0,0 +1,152 @@
|
||||
This is a short description how to run tests
|
||||
============================================
|
||||
|
||||
Run tests from maven
|
||||
--------------------
|
||||
|
||||
Just type `mvn test` - it will run the whole test suite with the settings
|
||||
from `src/test/resources/ebean.properties`.
|
||||
|
||||
Run tests from eclipse
|
||||
----------------------
|
||||
|
||||
Running tests requires, that the classes are enhanced. By default, the
|
||||
`BaseTestCase` tries to load the enhancer-agent in a very early stage. This
|
||||
also works for most test cases, but it does not work if you try to run a
|
||||
complete test-suite, as classes that are loaded before the enhancer-agent
|
||||
kicks in will not get enhanced. To solve this problem, you need either the
|
||||
[Eclipse-Enhancer-Plugin](https://github.com/ebean-orm-tools/ebean-eclipse-enhancer)
|
||||
or add the agent to your JVM arguments (Preferences->Installed JREs) or to
|
||||
your run configuration:
|
||||
`-javaagent:<USER_HOME>/.m2/repository/io/ebean/ebean-agent/<VERSION>/ebean-agent-<VERSION>.jar=debug=0`
|
||||
|
||||
|
||||
Platform Tests
|
||||
==============
|
||||
|
||||
By default, the `h2` platform is used. To test a different platform, add
|
||||
`-Ddatasource.default=xxx` to your maven command or JVM arguments.
|
||||
|
||||
'h2' platform
|
||||
-------------
|
||||
|
||||
Reqires no setup
|
||||
|
||||
run: `mvn clean test`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
|
||||
|
||||
'pg' platform
|
||||
-------------
|
||||
|
||||
Reqirements
|
||||
- a locally installed PostgreSQL server with PostGis.
|
||||
- Create a user "unit" with password "unit" and a database "unit"
|
||||
- Give the user all permissions to that db
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=pg`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 1986, Failures: 0, Errors: 0, Skipped: 16
|
||||
|
||||
|
||||
'mysql' platform
|
||||
-------------
|
||||
|
||||
Reqirements
|
||||
- a locally installed MySQL server.
|
||||
- Create a user "unit" with password "unit" and a database "unit"
|
||||
- Give the user all permissions to that db
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=mysql`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 2, Errors: 4, Skipped: 16
|
||||
|
||||
|
||||
|
||||
'sqlserver' platform
|
||||
--------------------
|
||||
|
||||
Reqires an installed sqlserver - e.g. https://hub.docker.com/r/microsoft/mssql-server-linux/
|
||||
|
||||
- Create a user "ebean" with password "ebean" and a database "ebean_unittest"
|
||||
- Adjust the connection string and/or username in ebean.properties
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=mssql`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 19, Errors: 43, Skipped: 16
|
||||
|
||||
|
||||
|
||||
'oracle' platform
|
||||
-----------------
|
||||
|
||||
Reqires a locally installed Oracle server.
|
||||
- Create a user "unit" with password "unit" with all permissions
|
||||
- The test wirtes directly to the "xe" SID - so do not use a productive server!
|
||||
- As the Oracle JDBC driver is only available on an oracle repository,
|
||||
you need a special maven setup as described [here](http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010)
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=ora -Poracle`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 17, Errors: 34, Skipped: 16
|
||||
|
||||
|
||||
|
||||
'db2' platform
|
||||
--------------
|
||||
|
||||
Reqires a locally installed DB2 Express-C server.
|
||||
- Create a user "unit" with password "unit" with all permissions
|
||||
- Install the db2jcc4 driver (see pom.xml)
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=db2 -Pdb2`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 33, Errors: 26, Skipped: 16
|
||||
|
||||
|
||||
'sqlite' platform
|
||||
-----------------
|
||||
|
||||
Reqires no setup
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=sqlite`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1986, Failures: 24, Errors: 86, Skipped: 16
|
||||
|
||||
After some time, the db locks up, every test takes 3 seconds and fails with
|
||||
"the database file is locked"
|
||||
|
||||
|
||||
|
||||
'hsqldb' platform
|
||||
-----------------
|
||||
|
||||
Reqires no setup
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=hsqldb`
|
||||
|
||||
Current status: FAIL
|
||||
Tests run: 1980, Failures: 3, Errors: 657, Skipped: 16
|
||||
|
||||
|
||||
@Rob FYI
|
||||
The problem is https://sourceforge.net/p/hsqldb/bugs/1364/
|
||||
In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
|
||||
'id' in lowercase. Changing that to uppercase gives me this result:
|
||||
`Tests run: 1986, Failures: 2, Errors: 18, Skipped: 16`
|
||||
(as quick hack I added temporary a toUpperCase to BindableIdScalar.getIdentityColumn)
|
||||
|
||||
|
||||
'sqlanywhere' platform
|
||||
----------------------
|
||||
|
||||
TODO: not yet tested
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>10.4.1</version>
|
||||
<version>10.4.6</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -17,17 +17,60 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-10.4.1</tag>
|
||||
<tag>ebean-10.4.6</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<!-- Note: to use this profile, you need credentials for the oracle repository in your
|
||||
settings.xml as described here:
|
||||
http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010 -->
|
||||
<id>oracle</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.oracle.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>12.2.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>maven.oracle.com</id>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<url>https://maven.oracle.com</url>
|
||||
<layout>default</layout>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
<profile>
|
||||
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
|
||||
After that, install it into your local maven repository:
|
||||
|
||||
mvn install:install-file \
|
||||
-Dfile=db2jcc4.jar \
|
||||
-DgroupId=com.ibm.jdbc \
|
||||
-DartifactId=db2jcc4 \
|
||||
-Dversion=4.23.42 \
|
||||
-Dpackaging=jar
|
||||
-->
|
||||
<id>db2</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.ibm.jdbc</groupId>
|
||||
<artifactId>db2jcc4</artifactId>
|
||||
<version>4.23.42</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
<dependencies>
|
||||
<!-- MS SQL Server dependency is now publicly available -->
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>6.1.3.jre8-preview</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
@@ -44,7 +87,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>2.3</version>
|
||||
<version>2.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -56,7 +99,7 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>2.1.1</version>
|
||||
<version>2.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -68,13 +111,13 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<version>2.2.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-dbmigration</artifactId>
|
||||
<version>10.1.10</version>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>10.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -93,14 +136,14 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.6.5</version>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.6.5</version>
|
||||
<version>2.9.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -167,7 +210,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>10.3.1</version>
|
||||
<version>10.4.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -194,10 +237,18 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>6.1.3.jre8-preview</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>6.0.5</version>
|
||||
<!-- Do not upgrade to 6.0.6: https://bugs.mysql.com/bug.php?id=82896 -->
|
||||
<version>5.1.43</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -232,7 +283,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>10.3.1</version>
|
||||
<version>10.4.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ebean;
|
||||
/**
|
||||
* Enum to control the different cache modes for queryCache (and maybe later) beanCache.
|
||||
*
|
||||
* If cache is enabled, you must be careful, what you do with the returned collection.
|
||||
* By default the returned collections are read only and you will get an exception if you try
|
||||
* to change them.
|
||||
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
|
||||
* one in the cache. That means, changing does not affect the cache
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public enum CacheMode {
|
||||
/**
|
||||
* Do not use cache.
|
||||
*/
|
||||
OFF(false, false),
|
||||
|
||||
/**
|
||||
* Use the cace (query & store the resut).
|
||||
*/
|
||||
ON(true, true),
|
||||
|
||||
/**
|
||||
* Do not read from cache, but write retrived value to cache.
|
||||
* Use this, if you want to get the fresh value from database and a CacheMode.ON query will follow.
|
||||
*/
|
||||
RECACHE(false, true),
|
||||
|
||||
/**
|
||||
* Query the cache for value. If it is there, use it, otherwise hit database but do NOT put the value
|
||||
* into the cache. (this mode is for completeness. There's probably no use case for this)
|
||||
*/
|
||||
QUERY_ONLY(true,false);
|
||||
|
||||
private boolean get;
|
||||
private boolean put;
|
||||
|
||||
private CacheMode(boolean get, boolean put) {
|
||||
this.get = get;
|
||||
this.put = put;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retruns <code>true</code> if value is read from cache.
|
||||
*/
|
||||
public boolean isGet() {
|
||||
return get;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if value (from database) is written to cache.
|
||||
*/
|
||||
public boolean isPut() {
|
||||
return put;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.ebean;
|
||||
|
||||
import io.ebean.RawSql.ColumnMapping;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -10,6 +11,8 @@ import java.util.ArrayList;
|
||||
*/
|
||||
final class DRawSqlColumnsParser {
|
||||
|
||||
private static final Pattern COLINFO_SPLIT = Pattern.compile("\\s(?=[^\\)]*(?:\\(|$))");
|
||||
|
||||
private final int end;
|
||||
|
||||
private final String sqlSelect;
|
||||
@@ -44,7 +47,7 @@ final class DRawSqlColumnsParser {
|
||||
String colInfo = sqlSelect.substring(start, pos++);
|
||||
colInfo = colInfo.trim();
|
||||
|
||||
String[] split = colInfo.split("\\s(?=[^\\)]*(?:\\(|$))");
|
||||
String[] split = COLINFO_SPLIT.split(colInfo);
|
||||
if (split.length > 1) {
|
||||
ArrayList<String> tmp = new ArrayList<>(split.length);
|
||||
for (String aSplit : split) {
|
||||
|
||||
@@ -56,7 +56,7 @@ public interface DocumentStore {
|
||||
* Return the bean by fetching it's content from the document store.
|
||||
* If the document is not found null is returned.
|
||||
* <p>
|
||||
* Typically this is called indirectly by findUnique() on the query.
|
||||
* Typically this is called indirectly by findOne() on the query.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -64,7 +64,7 @@ public interface DocumentStore {
|
||||
* server.find(Customer.class)
|
||||
* .setUseDocStore(true)
|
||||
* .setId(42)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -289,7 +290,7 @@ public final class Ebean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
*/
|
||||
protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) {
|
||||
EbeanServer originalPrimaryServer = serverMgr.defaultServer;
|
||||
@@ -371,9 +372,10 @@ public final class Ebean {
|
||||
/**
|
||||
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
|
||||
* <p>
|
||||
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
|
||||
* {@link #execute(TxScope, TxRunnable)}.
|
||||
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
|
||||
* {@link #execute(TxScope, Runnable)}.
|
||||
* </p>
|
||||
*
|
||||
* <h3>REQUIRES_NEW example:</h3>
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
@@ -822,7 +824,7 @@ public final class Ebean {
|
||||
* }</pre>
|
||||
* <p>
|
||||
* If you want more control over the query then you can use createQuery() and
|
||||
* Query.findUnique();
|
||||
* Query.findOne();
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -843,7 +845,8 @@ public final class Ebean {
|
||||
*
|
||||
* // traverse the object graph...
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* Customer customer = order.getCustomer();
|
||||
* Address shippingAddress = customer.getShippingAddress();
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -959,7 +962,7 @@ public final class Ebean {
|
||||
* predicates, order by, limits etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* You then use findList(), findSet(), findMap() and findUnique() to execute
|
||||
* You then use findList(), findSet(), findMap() and findOne() to execute
|
||||
* the query and return the collection or bean.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -991,7 +994,7 @@ public final class Ebean {
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, eql);
|
||||
* query.setParameter("orderId", 2);
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // This is the same as:
|
||||
*
|
||||
@@ -1000,7 +1003,7 @@ public final class Ebean {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -1181,20 +1184,20 @@ public final class Ebean {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static void execute(TxScope scope, TxRunnable r) {
|
||||
public static void execute(TxScope scope, Runnable r) {
|
||||
serverMgr.getDefaultServer().execute(scope, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with the default scope.
|
||||
* Execute a Runnable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* Ebean.execute(() -> {
|
||||
*
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
@@ -1203,17 +1206,17 @@ public final class Ebean {
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static void execute(TxRunnable r) {
|
||||
public static void execute(Runnable r) {
|
||||
serverMgr.getDefaultServer().execute(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with an explicit scope.
|
||||
* Execute a Callable in a Transaction with an explicit scope.
|
||||
* <p>
|
||||
* The scope can control the transaction type, isolation and rollback
|
||||
* semantics.
|
||||
@@ -1223,7 +1226,7 @@ public final class Ebean {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* Ebean.execute(scope, new TxCallable<String>() {
|
||||
* Ebean.executeCall(scope, new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1233,12 +1236,20 @@ public final class Ebean {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static <T> T execute(TxScope scope, TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().execute(scope, c);
|
||||
public static <T> T executeCall(TxScope scope, Callable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(scope, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with the default scope.
|
||||
* Deprecated - please migrate to executeCall().
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T execute(TxScope scope, TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(scope, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Callable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
@@ -1249,8 +1260,8 @@ public final class Ebean {
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxCallable<String>() {
|
||||
* public String call() {
|
||||
* Ebean.executeCall(() -> {
|
||||
*
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
@@ -1261,11 +1272,19 @@ public final class Ebean {
|
||||
* Ebean.save(u2);
|
||||
*
|
||||
* return u1.getEmail();
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static <T> T executeCall(Callable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated - please migrate to executeCall().
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T execute(TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().execute(c);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -238,7 +239,7 @@ public interface EbeanServer {
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, ormQuery);
|
||||
* query.setParameter("orderId", 2);
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // This is the same as:
|
||||
*
|
||||
@@ -247,7 +248,7 @@ public interface EbeanServer {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -265,7 +266,7 @@ public interface EbeanServer {
|
||||
* predicates, order by, limits etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* You then use findList(), findSet(), findMap() and findUnique() to execute
|
||||
* You then use findList(), findSet(), findMap() and findOne() to execute
|
||||
* the query and return the collection or bean.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -285,7 +286,7 @@ public interface EbeanServer {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // find some new orders ... with firstRow/maxRows
|
||||
* List<Order> orders =
|
||||
@@ -694,7 +695,7 @@ public interface EbeanServer {
|
||||
* <p>
|
||||
* <p>
|
||||
* If you want more control over the query then you can use createQuery() and
|
||||
* Query.findUnique();
|
||||
* Query.findOne();
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
@@ -714,7 +715,7 @@ public interface EbeanServer {
|
||||
* query.fetch("details.product", "name");
|
||||
*
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // traverse the object graph...
|
||||
*
|
||||
@@ -1088,14 +1089,17 @@ public interface EbeanServer {
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* Deprecated - please migrate to findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
<T> T findUnique(Query<T> query, Transaction transaction);
|
||||
default <T> T findUnique(Query<T> query, Transaction transaction) {
|
||||
return findOne(query, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
@@ -1166,7 +1170,7 @@ public interface EbeanServer {
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findUnique()} rather than
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
@@ -1174,20 +1178,23 @@ public interface EbeanServer {
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findUnique()
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* Deprecated - please migrate to findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
SqlRow findUnique(SqlQuery query, Transaction transaction);
|
||||
default SqlRow findUnique(SqlQuery query, Transaction transaction) {
|
||||
return findOne(query, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Either Insert or Update the bean depending on its state.
|
||||
@@ -1585,7 +1592,7 @@ public interface EbeanServer {
|
||||
int execute(CallableSql callableSql, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with an explicit scope.
|
||||
* Execute a Runnable in a Transaction with an explicit scope.
|
||||
* <p>
|
||||
* The scope can control the transaction type, isolation and rollback
|
||||
* semantics.
|
||||
@@ -1596,7 +1603,7 @@ public interface EbeanServer {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* ebeanServer.execute(scope, new TxRunnable() {
|
||||
* ebeanServer.execute(scope, new Runnable() {
|
||||
* public void run() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1605,10 +1612,10 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void execute(TxScope scope, TxRunnable runnable);
|
||||
void execute(TxScope scope, Runnable runnable);
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with the default scope.
|
||||
* Execute a Runnable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
@@ -1616,8 +1623,8 @@ public interface EbeanServer {
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* ebeanServer.execute(() -> {
|
||||
*
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* User u2 = ebeanServer.find(User.class, 2);
|
||||
*
|
||||
@@ -1626,12 +1633,12 @@ public interface EbeanServer {
|
||||
*
|
||||
* ebeanServer.save(u1);
|
||||
* ebeanServer.save(u2);
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void execute(TxRunnable runnable);
|
||||
void execute(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with an explicit scope.
|
||||
@@ -1645,7 +1652,7 @@ public interface EbeanServer {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* ebeanServer.execute(scope, new TxCallable<String>() {
|
||||
* ebeanServer.executeCall(scope, new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1655,22 +1662,24 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> T executeCall(TxScope scope, Callable<T> callable);
|
||||
|
||||
/**
|
||||
* Deprecated - please migrate to executeCall().
|
||||
*/
|
||||
@Deprecated
|
||||
<T> T execute(TxScope scope, TxCallable<T> callable);
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with the default scope.
|
||||
* Execute a Callable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is basically the same as TxRunnable except that it returns an Object
|
||||
* (and you specify the return type via generics).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.execute(new TxCallable<String>() {
|
||||
* ebeanServer.executeCall(new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* User u2 = ebeanServer.find(User.class, 2);
|
||||
@@ -1687,6 +1696,12 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> T executeCall(Callable<T> callable);
|
||||
|
||||
/**
|
||||
* Deprecated - please migrate to executeCall().
|
||||
*/
|
||||
@Deprecated
|
||||
<T> T execute(TxCallable<T> callable);
|
||||
|
||||
/**
|
||||
|
||||
@@ -264,7 +264,7 @@ public interface ExpressionList<T> {
|
||||
* </p>
|
||||
*
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findUnique()
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
@@ -275,14 +275,17 @@ public interface ExpressionList<T> {
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* Deprecated - please migrate to findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
T findUnique();
|
||||
default T findUnique() {
|
||||
return findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
@@ -442,12 +445,21 @@ public interface ExpressionList<T> {
|
||||
Query<T> setUseCache(boolean useCache);
|
||||
|
||||
/**
|
||||
* Set to true to use the query for executing this query.
|
||||
* Set the {@link CacheMode} to use the query for executing this query.
|
||||
*
|
||||
* @see Query#setUseQueryCache(boolean)
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useCache);
|
||||
Query<T> setUseQueryCache(CacheMode useCache);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*
|
||||
* @see Query#setUseQueryCache(CacheMode)
|
||||
*/
|
||||
default Query<T> setUseQueryCache(boolean enabled) {
|
||||
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.List;
|
||||
* // Add your customer finder methods ...
|
||||
*
|
||||
* public Customer byName(String name) {
|
||||
* return query().eq("name", name).findUnique();
|
||||
* return query().eq("name", name).findOne();
|
||||
* }
|
||||
*
|
||||
* public List<Customer> findNew() {
|
||||
|
||||
@@ -831,7 +831,7 @@ public interface Query<T> {
|
||||
* Product product =
|
||||
* ebeanServer.find(Product.class)
|
||||
* .where().eq("sku", "aa113")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
* ...
|
||||
* }</pre>
|
||||
* <p>
|
||||
@@ -845,7 +845,7 @@ public interface Query<T> {
|
||||
* ebeanServer.find(Order.class)
|
||||
* .setId(1)
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly loaded
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -863,14 +863,17 @@ public interface Query<T> {
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* Deprecated - please migrate to findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
T findUnique();
|
||||
default T findUnique() {
|
||||
return findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
@@ -1028,7 +1031,7 @@ public interface Query<T> {
|
||||
Query<T> setParameter(int position, Object value);
|
||||
|
||||
/**
|
||||
* Set the Id value to query. This is used with findUnique().
|
||||
* Set the Id value to query. This is used with findOne().
|
||||
* <p>
|
||||
* You can use this to have further control over the query. For example adding
|
||||
* fetch joins.
|
||||
@@ -1039,7 +1042,7 @@ public interface Query<T> {
|
||||
* ebeanServer.find(Order.class)
|
||||
* .setId(1)
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly fetched
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -1322,10 +1325,17 @@ public interface Query<T> {
|
||||
Query<T> setUseCache(boolean useCache);
|
||||
|
||||
/**
|
||||
* Set this to true to use the query cache.
|
||||
* Set the {@link CacheMode} to use the query for executing this query.
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useQueryCache);
|
||||
|
||||
Query<T> setUseQueryCache(CacheMode useQueryCache);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*/
|
||||
default Query<T> setUseQueryCache(boolean enabled) {
|
||||
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
|
||||
@@ -79,9 +79,12 @@ public interface SqlQuery extends Serializable {
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* Deprecated - please migrate to findOne().
|
||||
*/
|
||||
SqlRow findUnique();
|
||||
@Deprecated
|
||||
default SqlRow findUnique() {
|
||||
return findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Deprecated - please migrate to just using Callable instead with executeCall().
|
||||
*
|
||||
* Execute a TxCallable in a Transaction scope.
|
||||
* <p>
|
||||
* Use this with the {@link Ebean#execute(TxCallable)} method.
|
||||
* Use this with the {@link Ebean#executeCall(Callable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is basically the same as TxRunnable except that it returns an
|
||||
@@ -31,10 +35,9 @@ package io.ebean;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see TxRunnable
|
||||
*/
|
||||
public interface TxCallable<T> {
|
||||
@Deprecated
|
||||
public interface TxCallable<T> extends Callable<T> {
|
||||
|
||||
/**
|
||||
* Execute the method within a transaction scope returning the result.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Deprecated - please migrate to using just Runnable.
|
||||
*
|
||||
*
|
||||
* Execute a TxRunnable in a Transaction scope.
|
||||
* <p>
|
||||
* Use this with the {@link Ebean#execute(TxRunnable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* See also {@link TxCallable}.
|
||||
* Use this with the {@link Ebean#execute(Runnable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
@@ -31,7 +31,8 @@ package io.ebean;
|
||||
*
|
||||
* @see TxCallable
|
||||
*/
|
||||
public interface TxRunnable {
|
||||
@Deprecated
|
||||
public interface TxRunnable extends Runnable {
|
||||
|
||||
/**
|
||||
* Run the method in a transaction sope.
|
||||
|
||||
@@ -226,4 +226,9 @@ public interface BeanCollection<E> extends Serializable {
|
||||
* additions and removals have been processed.
|
||||
*/
|
||||
void modifyReset();
|
||||
|
||||
/**
|
||||
* Return a shallow copy of this collection that is modifiable.
|
||||
*/
|
||||
BeanCollection<E> getShallowCopy();
|
||||
}
|
||||
|
||||
@@ -1027,7 +1027,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
} else if (Double.compare(oldValue, newValue) != 0) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
@@ -1042,7 +1042,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
} else if (Float.compare(oldValue, newValue) != 0) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -213,4 +213,16 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
boolean holdsModifications() {
|
||||
return modifyHolder != null && modifyHolder.hasModifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
|
||||
* @param other
|
||||
*/
|
||||
protected void setFromOriginal(AbstractBeanCollection<E> other) {
|
||||
this.disableLazyLoad = other.disableLazyLoad;
|
||||
this.ebeanServerName = other.ebeanServerName;
|
||||
this.loader = other.loader;
|
||||
this.ownerBean = other.ownerBean;
|
||||
this.propertyName = other.propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,4 +541,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,4 +340,10 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,5 +377,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
throw new IllegalStateException("This collection is in ReadOnly mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
|
||||
* to modify lists from cache.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The underlying List implementation.
|
||||
*/
|
||||
private List<E> list;
|
||||
|
||||
|
||||
public CopyOnFirstWriteList(List<E> list) {
|
||||
super();
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
private volatile boolean copied = false;
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
checkCopyOnWrite();
|
||||
return list.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
checkCopyOnWrite();
|
||||
return list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return list.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<E> operator) {
|
||||
checkCopyOnWrite();
|
||||
list.replaceAll(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
checkCopyOnWrite();
|
||||
return list.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super E> c) {
|
||||
checkCopyOnWrite();
|
||||
list.sort(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (!copied) {
|
||||
list = new ArrayList<>();
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return list.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return list.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
checkCopyOnWrite();
|
||||
return list.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
checkCopyOnWrite();
|
||||
list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
checkCopyOnWrite();
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
private void checkCopyOnWrite() {
|
||||
if (!copied) {
|
||||
synchronized (this) {
|
||||
if (!copied) {
|
||||
list = new ArrayList<>(list);
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.Table;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* Provides some base implementation for NamingConventions.
|
||||
@@ -14,16 +16,15 @@ import java.lang.annotation.Annotation;
|
||||
*/
|
||||
public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
|
||||
|
||||
private static final Pattern COLUMN_REPLACE = Pattern.compile("{column}", Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* The Constant DEFAULT_SEQ_FORMAT.
|
||||
*/
|
||||
public static final String DEFAULT_SEQ_FORMAT = "{table}_seq";
|
||||
|
||||
/**
|
||||
* Sequence Format that includes the Primary Key column
|
||||
*/
|
||||
public static final String TABLE_PKCOLUMN_SEQ_FORMAT = "{table}_{column}_seq";
|
||||
|
||||
/**
|
||||
* The catalog.
|
||||
*/
|
||||
@@ -83,11 +84,11 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
|
||||
@Override
|
||||
public String getSequenceName(String tableName, String pkColumn) {
|
||||
String s = sequenceFormat.replace("{table}", tableName);
|
||||
String s = TABLE_REPLACE.matcher(sequenceFormat).replaceAll(Matcher.quoteReplacement(tableName));
|
||||
if (pkColumn == null) {
|
||||
pkColumn = "";
|
||||
}
|
||||
return s.replace("{column}", pkColumn);
|
||||
return COLUMN_REPLACE.matcher(s).replaceAll(Matcher.quoteReplacement(pkColumn));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,15 +219,10 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
* Return true if this class is part of entity inheritance.
|
||||
*/
|
||||
protected boolean hasInheritance(Class<?> supCls) {
|
||||
return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
|
||||
return AnnotationBase.findAnnotationRecursive(supCls, Inheritance.class) != null
|
||||
|| AnnotationBase.findAnnotation(supCls, DiscriminatorValue.class) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the class has the given annotation.
|
||||
*/
|
||||
protected boolean hasAnnotation(Class<?> supCls, Class<? extends Annotation> annotation) {
|
||||
return supCls.getAnnotation(annotation) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
|
||||
@@ -257,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
*/
|
||||
protected TableName getTableNameFromAnnotation(Class<?> beanClass) {
|
||||
|
||||
final Table t = findTableAnnotation(beanClass);
|
||||
final Table t = AnnotationBase.findAnnotationRecursive(beanClass, Table.class);
|
||||
|
||||
// Take the annotation if defined
|
||||
if (t != null && !isEmpty(t.name())) {
|
||||
@@ -270,22 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search recursively for an @Table in the class hierarchy.
|
||||
*/
|
||||
protected Table findTableAnnotation(Class<?> cls) {
|
||||
while (true) {
|
||||
if (cls.equals(Object.class)) {
|
||||
return null;
|
||||
}
|
||||
Table table = cls.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace back ticks (if they are used) with database platform specific
|
||||
* quoted identifiers.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.dbmigration.MigrationConfig;
|
||||
import io.ebean.dbmigration.MigrationRunner;
|
||||
import io.ebean.migration.MigrationConfig;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -18,6 +20,8 @@ final class PropertyMapLoader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
|
||||
|
||||
private static final Pattern OTHER_PROPS_REPLACE = Pattern.compile("\\", Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* Load the <code>test-ebean.properties</code>.
|
||||
*/
|
||||
@@ -109,7 +113,7 @@ final class PropertyMapLoader {
|
||||
otherProps = p.remove("load.properties.override");
|
||||
}
|
||||
if (otherProps != null) {
|
||||
otherProps = otherProps.replace("\\", "/");
|
||||
otherProps = OTHER_PROPS_REPLACE.matcher(otherProps).replaceAll(Matcher.quoteReplacement("/"));
|
||||
InputStream is = findInputStream(otherProps);
|
||||
if (is != null) {
|
||||
logger.debug("loading properties from {}", otherProps);
|
||||
|
||||
@@ -12,7 +12,8 @@ import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.dbmigration.MigrationRunner;
|
||||
import io.ebean.dbmigration.DbOffline;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
@@ -27,6 +28,8 @@ import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@@ -1629,6 +1632,16 @@ public class ServerConfig {
|
||||
this.encryptor = encryptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the instance is coming up in offline mode.
|
||||
* <p>
|
||||
* Offline mode is mostly used when generating DB migration.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isOfflineMode() {
|
||||
return dbOffline || DbOffline.isSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the EbeanServer instance should be created in offline mode.
|
||||
*/
|
||||
@@ -2633,10 +2646,9 @@ public class ServerConfig {
|
||||
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
|
||||
String[] split = classNames.split("[ ,;]");
|
||||
for (String aSplit : split) {
|
||||
String cn = aSplit.trim();
|
||||
if (!cn.isEmpty() && !"class".equalsIgnoreCase(cn)) {
|
||||
String[] split = StringHelper.splitNames(classNames);
|
||||
for (String cn : split) {
|
||||
if (!"class".equalsIgnoreCase(cn)) {
|
||||
try {
|
||||
classes.add(Class.forName(cn));
|
||||
} catch (ClassNotFoundException e) {
|
||||
@@ -2654,9 +2666,9 @@ public class ServerConfig {
|
||||
|
||||
if (searchPackages != null) {
|
||||
|
||||
String[] entries = searchPackages.split("[ ,;]");
|
||||
String[] entries = StringHelper.splitNames(searchPackages);
|
||||
for (String entry : entries) {
|
||||
hitList.add(entry.trim());
|
||||
hitList.add(entry);
|
||||
}
|
||||
}
|
||||
return hitList;
|
||||
|
||||
@@ -287,6 +287,9 @@ public class DatabasePlatform {
|
||||
* Create and return a DDL handler for generating DDL scripts.
|
||||
*/
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
if (platformDdl == null) {
|
||||
throw new IllegalStateException("Platform " + getName() + " has no DDL Handler");
|
||||
}
|
||||
return platformDdl.createDdlHandler(serverConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import io.ebean.annotation.DbDefault;
|
||||
|
||||
/**
|
||||
* DB Column default values mapping to database platform specific literals.
|
||||
*/
|
||||
@@ -22,6 +27,13 @@ public class DbDefaultValue {
|
||||
* The key for the NOW / current timestamp.
|
||||
*/
|
||||
public static final String NOW = "now";
|
||||
|
||||
/**
|
||||
* The 'null' literal.
|
||||
*/
|
||||
public static final String NULL = "null";
|
||||
|
||||
|
||||
|
||||
protected Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
@@ -63,8 +75,128 @@ public class DbDefaultValue {
|
||||
if (dbDefaultLiteral == null) {
|
||||
return null;
|
||||
}
|
||||
if (dbDefaultLiteral.startsWith("$RAW:")) {
|
||||
return dbDefaultLiteral.substring(5);
|
||||
}
|
||||
String val = map.get(dbDefaultLiteral);
|
||||
return val != null ? val : dbDefaultLiteral;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
|
||||
*
|
||||
* This is mainly to quote string literals and verify integer/dates for correctness.
|
||||
* <p>
|
||||
* Note: There are some special cases:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
|
||||
* returns: <code>default 'User''s default'</code><br/>
|
||||
* (the same on an integer property will throw a NumberFormatException)</li>
|
||||
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
|
||||
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
|
||||
* which gives you the <code>default 'null'</code> statement.</li>
|
||||
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
|
||||
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
|
||||
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
|
||||
if (propertyType == null
|
||||
|| defaultValue == null
|
||||
|| NULL.equals(defaultValue)
|
||||
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|
||||
|| (defaultValue.startsWith("$RAW:"))) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
|
||||
return toBooleanLiteral(defaultValue);
|
||||
}
|
||||
|
||||
if (Number.class.isAssignableFrom(propertyType)
|
||||
|| Byte.TYPE.equals(propertyType)
|
||||
|| Short.TYPE.equals(propertyType)
|
||||
|| Integer.TYPE.equals(propertyType)
|
||||
|| Long.TYPE.equals(propertyType)
|
||||
|| Float.TYPE.equals(propertyType)
|
||||
|| Double.TYPE.equals(propertyType)
|
||||
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
|
||||
Double.valueOf(defaultValue); // verify if it is a number
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// check if it is a date/time - in all other cases return quoted defaultValue
|
||||
switch (sqlType) {
|
||||
// date
|
||||
case Types.DATE:
|
||||
return toDateLiteral(defaultValue);
|
||||
// time
|
||||
case Types.TIME:
|
||||
case Types.TIME_WITH_TIMEZONE:
|
||||
return toTimeLiteral(defaultValue);
|
||||
// timestamp
|
||||
case Types.TIMESTAMP:
|
||||
case Types.TIMESTAMP_WITH_TIMEZONE:
|
||||
return toDateTimeLiteral(defaultValue);
|
||||
|
||||
default:
|
||||
return toTextLiteral(defaultValue); // do not check other datatypes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
|
||||
*/
|
||||
private static String toBooleanLiteral(String value) {
|
||||
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
|
||||
return value;
|
||||
}
|
||||
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds single qoutes around the <code>value</code> and doubles single quotes.
|
||||
* "User's home" will return "'User''s home'"
|
||||
*/
|
||||
private static String toTextLiteral(String value) {
|
||||
StringBuilder sb = new StringBuilder(value.length()+10);
|
||||
sb.append('\'');
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '\'') {
|
||||
sb.append("''");
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
sb.append('\'');
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
private static String toDateLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseDate(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
|
||||
private static String toTimeLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseTime(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
|
||||
private static String toDateTimeLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseDateTime(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.dbmigration.migration.IdentityType;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Defines the identity/sequence behaviour for the database.
|
||||
*/
|
||||
public class DbIdentity {
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* Set if this DB supports sequences. Note some DB's support both Sequences
|
||||
* and Identity.
|
||||
@@ -50,7 +54,8 @@ public class DbIdentity {
|
||||
if (selectLastInsertedIdTemplate == null) {
|
||||
return null;
|
||||
}
|
||||
return selectLastInsertedIdTemplate.replace("{table}", table);
|
||||
|
||||
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -130,7 +130,9 @@ public class DbPlatformType implements ExtraDbTypes {
|
||||
if (canHaveLength || !strict) {
|
||||
// see if there is a precision/scale to add (or not)
|
||||
int len = deployLength != 0 ? deployLength : defaultLength;
|
||||
if (len > 0) {
|
||||
if (len == Integer.MAX_VALUE) {
|
||||
sb.append("(max)"); // TODO: this is sqlserver specific
|
||||
} else if (len > 0) {
|
||||
sb.append("(");
|
||||
sb.append(len);
|
||||
int scale = deployScale != 0 ? deployScale : defaultScale;
|
||||
|
||||
@@ -30,7 +30,8 @@ class DbPlatformTypeParser {
|
||||
|
||||
} else {
|
||||
String type = columnDefinition.substring(0, openPos);
|
||||
int scale = Integer.parseInt(columnDefinition.substring(openPos + 1, closePos));
|
||||
String strScale = columnDefinition.substring(openPos + 1, closePos);
|
||||
int scale = strScale.equalsIgnoreCase("max") ? Integer.MAX_VALUE : Integer.parseInt(strScale);
|
||||
return new DbPlatformType(type, scale);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
@@ -30,15 +31,21 @@ public class DB2Platform extends DatabasePlatform {
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
//.addAcquireLock("")
|
||||
.addDuplicateKey("-803")
|
||||
.addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667")
|
||||
.addAcquireLock("40001","57033") // key -911/-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","23511","23512","23511","42917","23515")
|
||||
.build();
|
||||
|
||||
booleanDbType = Types.BOOLEAN;
|
||||
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint", false));
|
||||
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
|
||||
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
|
||||
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
|
||||
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("decimal", 15));
|
||||
|
||||
persistBatchOnCascade = PersistBatch.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ public class H2HistoryTrigger implements Trigger {
|
||||
|
||||
// build the insert into history table SQL
|
||||
StringBuilder insertSql = new StringBuilder(150);
|
||||
insertSql.append("insert into ").append(tableName).append(HISTORY_SUFFIX).append(" (");
|
||||
insertSql.append("insert into ").append(schemaName).append(".").append(tableName).append(HISTORY_SUFFIX).append(" (");
|
||||
|
||||
int count = 0;
|
||||
while (rs.next()) {
|
||||
|
||||
@@ -30,7 +30,7 @@ public class H2Platform extends DatabasePlatform {
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("50200")
|
||||
.addAcquireLock("50200","HYT00")
|
||||
.addDuplicateKey("23001","23505")
|
||||
.addDataIntegrity("22001","22003","22012","22018","22025","23000","23002","23003","23502","23503","23506","23507","23513")
|
||||
.build();
|
||||
|
||||
@@ -45,7 +45,7 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("1205")
|
||||
.addDuplicateKey("1062")
|
||||
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557")
|
||||
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557","23000")
|
||||
.build();
|
||||
|
||||
this.openQuote = "`";
|
||||
|
||||
@@ -2,9 +2,12 @@ package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.config.dbplatform.*;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.SqlServerDdl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -59,8 +62,8 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.TIME, new DbPlatformType("time"));
|
||||
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime2"));
|
||||
|
||||
dbTypeMap.put(DbType.JSON, new DbPlatformType("nvarchar(max)"));
|
||||
dbTypeMap.put(DbType.JSONB, new DbPlatformType("nvarchar(max)"));
|
||||
dbTypeMap.put(DbType.JSON, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
dbTypeMap.put(DbType.JSONB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,12 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebean.dbmigration.migration.Migration;
|
||||
@@ -196,8 +198,9 @@ public class DbMigration {
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
* @return the generated migration or null
|
||||
*/
|
||||
public void generateMigration() throws IOException {
|
||||
public String generateMigration() throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
if (!online) {
|
||||
@@ -218,9 +221,9 @@ public class DbMigration {
|
||||
|
||||
String pendingVersion = generatePendingDrop();
|
||||
if (pendingVersion != null) {
|
||||
generatePendingDrop(request, pendingVersion);
|
||||
return generatePendingDrop(request, pendingVersion);
|
||||
} else {
|
||||
generateDiff(request);
|
||||
return generateDiff(request);
|
||||
}
|
||||
|
||||
} finally {
|
||||
@@ -276,7 +279,7 @@ public class DbMigration {
|
||||
/**
|
||||
* Generate the diff migration.
|
||||
*/
|
||||
private void generateDiff(Request request) throws IOException {
|
||||
private String generateDiff(Request request) throws IOException {
|
||||
|
||||
List<String> pendingDrops = request.getPendingDrops();
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
@@ -286,25 +289,27 @@ public class DbMigration {
|
||||
Migration migration = request.createDiffMigration();
|
||||
if (migration == null) {
|
||||
logger.info("no changes detected - no migration written");
|
||||
return null;
|
||||
} else {
|
||||
// there were actually changes to write
|
||||
generateMigration(request, migration, null);
|
||||
return generateMigration(request, migration, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the migration based on the pendingDrops from a prior version.
|
||||
*/
|
||||
private void generatePendingDrop(Request request, String pendingVersion) throws IOException {
|
||||
private String generatePendingDrop(Request request, String pendingVersion) throws IOException {
|
||||
|
||||
Migration migration = request.migrationForPendingDrop(pendingVersion);
|
||||
|
||||
generateMigration(request, migration, pendingVersion);
|
||||
String version = generateMigration(request, migration, pendingVersion);
|
||||
|
||||
List<String> pendingDrops = request.getPendingDrops();
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
private Request createRequest() {
|
||||
@@ -358,14 +363,14 @@ public class DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private void generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
|
||||
private String generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
|
||||
|
||||
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
|
||||
|
||||
logger.info("generating migration:{}", fullVersion);
|
||||
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
|
||||
logger.warn("migration already exists, not generating DDL");
|
||||
|
||||
return null;
|
||||
} else {
|
||||
if (!platforms.isEmpty()) {
|
||||
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
|
||||
@@ -374,9 +379,10 @@ public class DbMigration {
|
||||
// writer needs the current model to provide table/column details for
|
||||
// history ddl generation (triggers, history tables etc)
|
||||
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
|
||||
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
|
||||
PlatformDdlWriter writer = createDdlWriter(databasePlatform);
|
||||
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
|
||||
}
|
||||
return fullVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +437,7 @@ public class DbMigration {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair);
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(writePath, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
|
||||
|
||||
@@ -439,12 +445,8 @@ public class DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(Pair pair) {
|
||||
return createDdlWriter(pair.platform, pair.prefix);
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform, String prefix) {
|
||||
return new PlatformDdlWriter(platform, serverConfig, prefix, migrationConfig);
|
||||
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform) {
|
||||
return new PlatformDdlWriter(platform, serverConfig, migrationConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,19 +521,25 @@ public class DbMigration {
|
||||
switch (platform) {
|
||||
case H2:
|
||||
return new H2Platform();
|
||||
case HSQLDB:
|
||||
return new HsqldbPlatform();
|
||||
case POSTGRES:
|
||||
return new PostgresPlatform();
|
||||
case MYSQL:
|
||||
return new MySqlPlatform();
|
||||
case ORACLE:
|
||||
return new OraclePlatform();
|
||||
case SQLANYWHERE:
|
||||
return new SqlAnywherePlatform();
|
||||
case SQLSERVER:
|
||||
return new SqlServerPlatform();
|
||||
case DB2:
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
return new SQLitePlatform();
|
||||
|
||||
case GENERIC:
|
||||
return new DatabasePlatform();
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Platform missing? " + platform);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import io.ebean.dbmigration.ddl.DdlRunner;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
for (Object change : changeSetChildren) {
|
||||
if (change instanceof CreateTable) {
|
||||
generate(writer, (CreateTable) change);
|
||||
}
|
||||
}
|
||||
for (Object change : changeSetChildren) {
|
||||
if (change instanceof CreateTable) {
|
||||
// ignore
|
||||
} else if (change instanceof DropTable) {
|
||||
generate(writer, (DropTable) change);
|
||||
} else if (change instanceof AddTableComment) {
|
||||
@@ -54,6 +59,8 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
generate(writer, (AddHistoryTable) change);
|
||||
} else if (change instanceof DropHistoryTable) {
|
||||
generate(writer, (DropHistoryTable) change);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported change: " + change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package io.ebean.dbmigration.ddlgeneration.platform;
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.NamingConvention;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DbHistorySupport;
|
||||
import io.ebean.config.dbplatform.DbIdentity;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -15,6 +17,7 @@ import io.ebean.dbmigration.migration.AlterColumn;
|
||||
import io.ebean.dbmigration.migration.Column;
|
||||
import io.ebean.dbmigration.migration.CreateIndex;
|
||||
import io.ebean.dbmigration.migration.CreateTable;
|
||||
import io.ebean.dbmigration.migration.DdlScript;
|
||||
import io.ebean.dbmigration.migration.DropColumn;
|
||||
import io.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebean.dbmigration.migration.DropIndex;
|
||||
@@ -27,6 +30,8 @@ import io.ebean.util.StringHelper;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -70,6 +75,121 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<>();
|
||||
|
||||
private boolean strict;
|
||||
|
||||
private final boolean sql2011History;
|
||||
|
||||
/**
|
||||
* Helper class that is used to execute the migration ddl before and after the migration action.
|
||||
*/
|
||||
private class DdlMigrationHelp {
|
||||
private List<String> before;
|
||||
private List<String> after;
|
||||
private String tableName;
|
||||
private String columnName;
|
||||
private String defaultValue;
|
||||
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when adding a NEW column.
|
||||
*/
|
||||
DdlMigrationHelp(String tableName, Column column) throws IOException {
|
||||
this.tableName = tableName;
|
||||
this.columnName = column.getName();
|
||||
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
|
||||
boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
|
||||
|
||||
if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
|
||||
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
|
||||
}
|
||||
|
||||
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
|
||||
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when altering a column.
|
||||
*/
|
||||
DdlMigrationHelp(AlterColumn alter) throws IOException {
|
||||
this.tableName = alter.getTableName();
|
||||
this.columnName = alter.getColumnName();
|
||||
|
||||
String tmp = alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue();
|
||||
this.defaultValue = platformDdl.convertDefaultValue(tmp);
|
||||
|
||||
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
|
||||
// here we add the platform's default update script
|
||||
if (alter.getBefore().isEmpty() && alterNotNull) {
|
||||
if (defaultValue == null) {
|
||||
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
|
||||
}
|
||||
before = Arrays.asList(platformDdl.getUpdateNullWithDefault());
|
||||
} else {
|
||||
before = getScriptsForPlatform(alter.getBefore(), platformDdl.getPlatform().getName());
|
||||
}
|
||||
|
||||
after = getScriptsForPlatform(alter.getAfter(), platformDdl.getPlatform().getName());
|
||||
|
||||
}
|
||||
|
||||
public void writeBefore(DdlBuffer buffer) throws IOException {
|
||||
if (!before.isEmpty()) {
|
||||
buffer.end();
|
||||
}
|
||||
for (String ddlScript : before) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
|
||||
buffer.endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeAfter(DdlBuffer buffer) throws IOException {
|
||||
// here we run postmigration scripts
|
||||
for (String ddlScript : after) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
|
||||
buffer.endOfStatement();
|
||||
}
|
||||
if (!after.isEmpty()) {
|
||||
buffer.end();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getScriptsForPlatform(List<DdlScript> scripts, String searchPlatform) {
|
||||
List<String> ret = Collections.emptyList();
|
||||
for (DdlScript script : scripts) {
|
||||
if (script.getPlatforms() == null || script.getPlatforms().isEmpty()) {
|
||||
ret = script.getDdl();
|
||||
} else for (String platform : StringHelper.splitNames(script.getPlatforms())) {
|
||||
if (platform.equals(searchPlatform)) {
|
||||
return script.getDdl();
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces Table name (${table}), Column name (${column}) and default value (${default}) in DDL.
|
||||
*/
|
||||
private String translate(String ddl, String tableName, String columnName, String defaultValue) {
|
||||
String ret = StringHelper.replaceString(ddl, "${table}", tableName);
|
||||
ret = StringHelper.replaceString(ret, "${column}", columnName);
|
||||
return StringHelper.replaceString(ret, "${default}", defaultValue);
|
||||
}
|
||||
|
||||
private void handleStrictError(String message) {
|
||||
if (strict) {
|
||||
throw new IllegalArgumentException(message);
|
||||
} else {
|
||||
System.err.println("Error in DDL: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with a naming convention and platform specific DDL.
|
||||
*/
|
||||
@@ -79,6 +199,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
this.historyTableSuffix = serverConfig.getHistoryTableSuffix();
|
||||
this.platformDdl = platformDdl;
|
||||
this.platformDdl.configure(serverConfig);
|
||||
this.strict = true; // TODO RPr serverConfig.getMigrationConfig().isStrict();
|
||||
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
|
||||
this.sql2011History = hist != null && hist.isStandardsBased();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,9 +330,12 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = createTable.getName();
|
||||
for (Column col : externalUnique) {
|
||||
String uqName = col.getUniqueOneToOne();
|
||||
if (uqName == null) {
|
||||
uqName = col.getUnique();
|
||||
}
|
||||
String[] columnNames = {col.getName()};
|
||||
write.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames))
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, Boolean.TRUE.equals(col.isNotnull())))
|
||||
.endOfStatement();
|
||||
|
||||
write.dropAllForeignKeys()
|
||||
@@ -221,7 +347,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
String uqName = constraint.getName();
|
||||
String[] columnNames = StringHelper.delimitedToArray(constraint.getColumnNames(), ",", false);
|
||||
write.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames))
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uqName, columnNames, false)) // TODO: check if nullable
|
||||
.endOfStatement();
|
||||
|
||||
write.dropAllForeignKeys()
|
||||
@@ -392,6 +518,14 @@ public class BaseTableDdl implements TableDdl {
|
||||
buffer.append(platformDdl.dropTable(tableName)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 'drop sequence' statement to the buffer.
|
||||
*/
|
||||
protected void dropSequence(DdlBuffer buffer, String sequenceName) throws IOException {
|
||||
|
||||
buffer.append(platformDdl.dropSequence(sequenceName)).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the check constraints.
|
||||
*/
|
||||
@@ -421,9 +555,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
|
||||
List<UniqueConstraint> uniqueConstraints = createTable.getUniqueConstraint();
|
||||
boolean inlineUniqueCompound = platformDdl.isInlineUniqueOneToOne();
|
||||
boolean inlineUniqueWhenNull = platformDdl.isInlineUniqueWhenNullable();
|
||||
for (UniqueConstraint uniqueConstraint : uniqueConstraints) {
|
||||
if (inlineUniqueCompound) {
|
||||
if (inlineUniqueWhenNull) {
|
||||
String uqName = uniqueConstraint.getName();
|
||||
String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames());
|
||||
apply.append(",").newLine();
|
||||
@@ -440,17 +574,18 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
|
||||
|
||||
boolean inlineUniqueOneToOne = platformDdl.isInlineUniqueOneToOne();
|
||||
boolean inlineUniqueWhenNullable = platformDdl.isInlineUniqueWhenNullable();
|
||||
|
||||
List<Column> columns = createTable.getColumn();
|
||||
for (Column column : columns) {
|
||||
if (hasValue(column.getUnique()) || (inlineUniqueOneToOne && hasValue(column.getUniqueOneToOne()))) {
|
||||
// normal mechanism for adding unique constraint
|
||||
inlineUniqueConstraintSingle(apply, column);
|
||||
|
||||
} else if (!inlineUniqueOneToOne && hasValue(column.getUniqueOneToOne())) {
|
||||
// MsSqlServer specific mechanism for adding unique constraints (that allow nulls)
|
||||
externalUnique.add(column);
|
||||
if (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne())) {
|
||||
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
|
||||
// normal mechanism for adding unique constraint
|
||||
inlineUniqueConstraintSingle(apply, column);
|
||||
} else {
|
||||
// MsSqlServer & DB2 specific mechanism for adding unique constraints (that allow nulls)
|
||||
externalUnique.add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,7 +729,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false);
|
||||
}
|
||||
|
||||
if (isTrue(addColumn.isWithHistory())) {
|
||||
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
for (Column column : columns) {
|
||||
@@ -620,6 +755,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
public void generate(DdlWrite writer, DropTable dropTable) throws IOException {
|
||||
|
||||
dropTable(writer.apply(), dropTable.getName());
|
||||
|
||||
if (hasValue(dropTable.getSequenceCol())
|
||||
&& platformDdl.getPlatform().getDbIdentity().isSupportsSequence()) {
|
||||
String sequenceName = dropTable.getSequenceName();
|
||||
if (!hasValue(sequenceName)) {
|
||||
sequenceName = namingConvention.getSequenceName(dropTable.getName(), dropTable.getSequenceCol());
|
||||
}
|
||||
dropSequence(writer.apply(), sequenceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -631,7 +775,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = dropColumn.getTableName();
|
||||
|
||||
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
|
||||
if (isTrue(dropColumn.isWithHistory())) {
|
||||
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
|
||||
// also drop from the history table
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
|
||||
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
|
||||
@@ -645,7 +789,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
|
||||
DdlMigrationHelp ddlHelp = new DdlMigrationHelp(alterColumn);
|
||||
ddlHelp.writeBefore(writer.apply());
|
||||
|
||||
if (isTrue(alterColumn.isHistoryExclude())) {
|
||||
regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.EXCLUDE, alterColumn.getColumnName());
|
||||
} else if (isFalse(alterColumn.isHistoryExclude())) {
|
||||
@@ -698,6 +844,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
// add constraint last (after potential type change)
|
||||
addCheckConstraint(writer, alterColumn);
|
||||
}
|
||||
ddlHelp.writeAfter(writer.apply());
|
||||
}
|
||||
|
||||
private void alterColumnComment(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
@@ -729,7 +876,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null) {
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
|
||||
// mysql and sql server column type change allowing nulls in the history table column
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
|
||||
@@ -780,7 +927,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
if (isTrue(alter.isWithHistory())) {
|
||||
if (isTrue(alter.isWithHistory()) && !sql2011History) {
|
||||
// apply same type change to matching column in the history table
|
||||
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
@@ -834,9 +981,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
protected void addUniqueConstraint(DdlWrite writer, AlterColumn alter, String uqName) throws IOException {
|
||||
|
||||
String[] cols = {alter.getColumnName()};
|
||||
|
||||
boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
|
||||
writer.apply()
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols))
|
||||
.append(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull))
|
||||
.endOfStatement();
|
||||
|
||||
writer.dropAllForeignKeys()
|
||||
@@ -846,16 +993,19 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
|
||||
protected void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
|
||||
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
|
||||
.endOfStatement();
|
||||
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
|
||||
}
|
||||
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
|
||||
String ddl = platformDdl.alterTableAddColumn(tableName, column, onHistoryTable);
|
||||
if (hasValue(ddl)) {
|
||||
buffer.append(ddl);
|
||||
buffer.endOfStatement();
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
|
||||
if (!onHistoryTable) {
|
||||
help.writeBefore(buffer);
|
||||
}
|
||||
|
||||
platformDdl.alterTableAddColumn(buffer, tableName, column, onHistoryTable, help.getDefaultValue());
|
||||
|
||||
if (!onHistoryTable) {
|
||||
help.writeAfter(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,21 @@ public class DB2Ddl extends PlatformDdl {
|
||||
|
||||
public DB2Ddl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.dropTableIfExists = "drop table ";
|
||||
this.dropSequenceIfExists = "drop sequence ";
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
this.dropIndexIfExists = "drop index ";
|
||||
this.identitySuffix = " generated by default as identity";
|
||||
this.inlineUniqueWhenNullable = false;
|
||||
}
|
||||
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
|
||||
if (notNull) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
|
||||
} 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, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
public class DdlHelp {
|
||||
public static final String DROP_DEFAULT = "DROP DEFAULT";
|
||||
|
||||
public static final String DROP_COMMENT = "DROP COMMENT";
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP DEFAULT value.
|
||||
*/
|
||||
public static boolean isDropDefault(String defaultValue) {
|
||||
return DROP_DEFAULT.equals(defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP DEFAULT value.
|
||||
*/
|
||||
public static boolean isDropComment(String comment) {
|
||||
return DROP_COMMENT.equals(comment);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public class MySqlDdl extends PlatformDdl {
|
||||
@Override
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
|
||||
String suffix = isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
|
||||
|
||||
// use alter
|
||||
return "alter table " + tableName + " alter " + columnName + " " + suffix;
|
||||
@@ -67,7 +67,9 @@ public class MySqlDdl extends PlatformDdl {
|
||||
|
||||
@Override
|
||||
public String alterColumnBaseAttributes(AlterColumn alter) {
|
||||
|
||||
if (DdlHelp.isDropDefault(alter.getDefaultValue())) {
|
||||
return null;
|
||||
}
|
||||
String tableName = alter.getTableName();
|
||||
String columnName = alter.getColumnName();
|
||||
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
|
||||
|
||||
@@ -18,6 +18,8 @@ import io.ebean.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebean.dbmigration.migration.IdentityType;
|
||||
import io.ebean.dbmigration.model.MTable;
|
||||
import io.ebean.util.StringHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -27,6 +29,8 @@ import java.util.List;
|
||||
*/
|
||||
public class PlatformDdl {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PlatformDdl.class);
|
||||
|
||||
protected final DatabasePlatform platform;
|
||||
|
||||
protected PlatformHistoryDdl historyDdl = new NoHistorySupportDdl();
|
||||
@@ -75,7 +79,7 @@ public class PlatformDdl {
|
||||
protected String dropUniqueConstraint = "drop constraint";
|
||||
|
||||
protected String addConstraint = "add constraint";
|
||||
|
||||
|
||||
protected String addColumn = "add column";
|
||||
|
||||
protected String columnSetType = "";
|
||||
@@ -88,10 +92,12 @@ public class PlatformDdl {
|
||||
|
||||
protected String columnSetNull = "set null";
|
||||
|
||||
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
|
||||
|
||||
/**
|
||||
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
|
||||
*/
|
||||
protected boolean inlineUniqueOneToOne = true;
|
||||
protected boolean inlineUniqueWhenNullable = true;
|
||||
|
||||
protected DbConstraintNaming naming;
|
||||
|
||||
@@ -205,7 +211,7 @@ public class PlatformDdl {
|
||||
/**
|
||||
* Convert the DB column default literal to platform specific.
|
||||
*/
|
||||
private String convertDefaultValue(String dbDefault) {
|
||||
public String convertDefaultValue(String dbDefault) {
|
||||
return dbDefaultValue.convert(dbDefault);
|
||||
}
|
||||
|
||||
@@ -371,41 +377,60 @@ public class PlatformDdl {
|
||||
* <p>
|
||||
* Overridden by MsSqlServer for specific null handling on unique constraints.
|
||||
*/
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns) {
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(90);
|
||||
buffer.append("alter table ").append(tableName).append(" add constraint ").append(uqName).append(" unique ");
|
||||
appendColumns(columns, buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String alterTableAddColumn(String tableName, Column column, boolean onHistoryTable) throws IOException {
|
||||
|
||||
public void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, String defaultValue) throws IOException {
|
||||
|
||||
String convertedType = convert(column.getType(), false);
|
||||
|
||||
StringBuilder buffer = new StringBuilder(90);
|
||||
|
||||
buffer.append("alter table ").append(tableName)
|
||||
.append(' ').append(addColumn).append(' ').append(column.getName())
|
||||
.append(' ').append(convertedType);
|
||||
.append(" ").append(addColumn).append(" ").append(column.getName())
|
||||
.append(" ").append(convertedType);
|
||||
|
||||
if (!onHistoryTable) {
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
if (!StringHelper.isNull(column.getCheckConstraint())) {
|
||||
buffer.append(" constraint ").append(column.getCheckConstraintName());
|
||||
buffer.append(" ").append(column.getCheckConstraint());
|
||||
|
||||
if (defaultValue != null) {
|
||||
if (typeContainsDefault(convertedType)) {
|
||||
logger.error("Cannot set default value for '" + tableName + "." + column.getName() + "'");
|
||||
} else {
|
||||
buffer.append(" default ");
|
||||
buffer.append(defaultValue);
|
||||
}
|
||||
}
|
||||
buffer.endOfStatement();
|
||||
|
||||
// check constraints cannot be added in one statement for h2
|
||||
if (!StringHelper.isNull(column.getCheckConstraint())) {
|
||||
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
|
||||
buffer.append(ddl).endOfStatement();
|
||||
}
|
||||
} else {
|
||||
buffer.endOfStatement();
|
||||
}
|
||||
return buffer.toString();
|
||||
|
||||
}
|
||||
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if unique constraints for OneToOne can be inlined as normal.
|
||||
* Returns false for MsSqlServer due to it's null handling for unique constraints.
|
||||
* Return true if unique constraints for nullable columns can be inlined as normal.
|
||||
* Returns false for MsSqlServer & DB2 due to it's not possible to to put a constraint
|
||||
* on a nullable column
|
||||
*/
|
||||
public boolean isInlineUniqueOneToOne() {
|
||||
return inlineUniqueOneToOne;
|
||||
public boolean isInlineUniqueWhenNullable() {
|
||||
return inlineUniqueWhenNullable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,19 +464,11 @@ public class PlatformDdl {
|
||||
return "alter table " + tableName + " " + addConstraint + " " + checkConstraintName + " " + checkConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the default value is the special DROP DEFAULT value.
|
||||
*/
|
||||
public boolean isDropDefault(String defaultValue) {
|
||||
return "DROP DEFAULT".equals(defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter column setting the default value.
|
||||
*/
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
|
||||
String suffix = isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
|
||||
}
|
||||
|
||||
@@ -504,6 +521,13 @@ public class PlatformDdl {
|
||||
return naming.lowerColumnName(name);
|
||||
}
|
||||
|
||||
public DatabasePlatform getPlatform() {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public String getUpdateNullWithDefault() {
|
||||
return updateNullWithDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null safe Boolean true test.
|
||||
@@ -523,15 +547,19 @@ public class PlatformDdl {
|
||||
* Add table comment as a separate statement (from the create table statement).
|
||||
*/
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
|
||||
|
||||
if (DdlHelp.isDropComment(tableComment)) {
|
||||
tableComment = "";
|
||||
}
|
||||
apply.append(String.format("comment on table %s is '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add column comment as a separate statement.
|
||||
*/
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
|
||||
if (DdlHelp.isDropComment(comment)) {
|
||||
comment = "";
|
||||
}
|
||||
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
this.foreignKeyRestrict = "";
|
||||
this.alterTableIfExists = "";
|
||||
this.addColumn = "add";
|
||||
this.inlineUniqueOneToOne = false;
|
||||
this.inlineUniqueWhenNullable = false;
|
||||
this.columnSetDefault = "add default";
|
||||
this.dropConstraintIfExists = "drop constraint";
|
||||
this.historyDdl = new SqlServerHistoryDdl();
|
||||
@@ -25,7 +25,12 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
|
||||
@Override
|
||||
public String dropTable(String tableName) {
|
||||
return "IF OBJECT_ID('" + tableName + "', 'U') IS NOT NULL drop table " + tableName;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("IF OBJECT_ID('");
|
||||
buffer.append(tableName);
|
||||
buffer.append("', 'U') IS NOT NULL drop table ");
|
||||
buffer.append(tableName);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,8 +56,13 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
* MsSqlServer specific null handling on unique constraints.
|
||||
*/
|
||||
@Override
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns) {
|
||||
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
|
||||
if (notNull) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, notNull);
|
||||
}
|
||||
if (uqName == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
// issues#233
|
||||
String start = "create unique nonclustered index " + uqName + " on " + tableName + "(";
|
||||
StringBuilder sb = new StringBuilder(start);
|
||||
@@ -71,7 +81,24 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public String alterTableDropConstraint(String tableName, String constraintName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("IF (OBJECT_ID('").append(constraintName).append("', 'C') IS NOT NULL) ");
|
||||
sb.append(super.alterTableDropConstraint(tableName, constraintName));
|
||||
return sb.toString();
|
||||
}
|
||||
/**
|
||||
* Drop a unique constraint from the table (Sometimes this is an index).
|
||||
*/
|
||||
@Override
|
||||
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("IF (OBJECT_ID('").append(uniqueConstraintName).append("', 'UQ') IS NOT NULL) ");
|
||||
sb.append(super.alterTableDropUniqueConstraint(tableName, uniqueConstraintName)).append(";\n");
|
||||
sb.append(dropIndex(uniqueConstraintName, tableName));
|
||||
return sb.toString();
|
||||
}
|
||||
/**
|
||||
* Generate and return the create sequence DDL.
|
||||
*/
|
||||
@@ -97,17 +124,31 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
|
||||
@Override
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
|
||||
if (isDropDefault(defaultValue)) {
|
||||
return "-- alter table " + tableName + " drop constraint <unknown> -- find the appropriate constraint for default value on column " + columnName;
|
||||
// Unfortunately, the SqlServer creates default values with a random name.
|
||||
// You can specify a name in DDL, but this does not work in conjunction with
|
||||
// temporal tables in certain cases. So we have to delete the constraint with
|
||||
// a rather complex statement.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (DdlHelp.isDropDefault(defaultValue)) {
|
||||
sb.append("delimiter $$\n");
|
||||
sb.append("DECLARE @Tmp nvarchar(200);");
|
||||
sb.append("select @Tmp = t1.name from sys.default_constraints t1\n");
|
||||
sb.append(" join sys.columns t2 on t1.object_id = t2.default_object_id\n");
|
||||
sb.append(" where t1.parent_object_id = OBJECT_ID('").append(tableName)
|
||||
.append("') and t2.name = '").append(columnName).append("';\n");
|
||||
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
|
||||
} else {
|
||||
return "alter table " + tableName + " add default " + defaultValue + " for " + columnName;
|
||||
sb.append("alter table ").append(tableName);
|
||||
sb.append(" add default ").append(defaultValue).append(" for ").append(columnName);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnBaseAttributes(AlterColumn alter) {
|
||||
|
||||
if (DdlHelp.isDropDefault(alter.getDefaultValue())) {
|
||||
return null;
|
||||
}
|
||||
String tableName = alter.getTableName();
|
||||
String columnName = alter.getColumnName();
|
||||
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
|
||||
@@ -149,4 +190,28 @@ public class SqlServerDdl extends PlatformDdl {
|
||||
|
||||
// do nothing for MS SQL Server (cause it requires stored procedures etc)
|
||||
}
|
||||
|
||||
/**
|
||||
* It is rather complex to delete a column on SqlServer as there must not exist any references
|
||||
* (constraints, default values, indices and foreign keys). The list is not yet complete, as
|
||||
* indices over multiple columns will not yet deleted.
|
||||
* (This may be changed to delete all refering objects by using the sys.* tables later)
|
||||
*/
|
||||
@Override
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("-- drop column ").append(tableName).append(".").append(columnName).endOfStatement();
|
||||
|
||||
buffer.append(alterTableDropUniqueConstraint(tableName, naming.uniqueConstraintName(tableName, columnName)));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(alterColumnDefaultValue(tableName, columnName, DdlHelp.DROP_DEFAULT));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(alterTableDropConstraint(tableName, naming.checkConstraintName(tableName, columnName)));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(dropIndex(naming.indexName(tableName, columnName), tableName));
|
||||
buffer.endOfStatement();
|
||||
buffer.append(alterTableDropForeignKey(tableName, naming.foreignKeyConstraintName(tableName, columnName)));
|
||||
buffer.endOfStatement();
|
||||
super.alterTableDropColumn(buffer, tableName, columnName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -16,11 +17,13 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
private String systemPeriodStart;
|
||||
private String systemPeriodEnd;
|
||||
private PlatformDdl platformDdl;
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "From";
|
||||
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "To";
|
||||
this.platformDdl = platformDdl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,7 +36,7 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
apply.append("alter table ").append(baseTable).newLine()
|
||||
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
|
||||
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL,").newLine()
|
||||
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
|
||||
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
String historyTable = baseTable + "_history"; // history must contain schema, otherwise you'll get
|
||||
@@ -52,9 +55,19 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
String baseTable = dropHistoryTable.getBaseTable();
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
|
||||
// drop default constraints
|
||||
|
||||
apply.append(platformDdl.alterColumnDefaultValue(baseTable, systemPeriodStart, DdlHelp.DROP_DEFAULT)).endOfStatement();
|
||||
apply.append(platformDdl.alterColumnDefaultValue(baseTable, systemPeriodEnd, DdlHelp.DROP_DEFAULT)).endOfStatement();
|
||||
// switch of versioning & period
|
||||
apply.append("alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
|
||||
apply.append("alter table ").append(baseTable).append(" drop period for system_time").endOfStatement();
|
||||
// now drop tables & columns
|
||||
apply.append("alter table ").append(baseTable).append(" drop column ").append(systemPeriodStart).endOfStatement();
|
||||
apply.append("alter table ").append(baseTable).append(" drop column ").append(systemPeriodEnd).endOfStatement();
|
||||
apply.append("IF OBJECT_ID('").append(baseTable).append("_history', 'U') IS NOT NULL drop table ").append(baseTable).append("_history").endOfStatement();
|
||||
apply.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package io.ebean.dbmigration.migration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
@@ -44,10 +48,17 @@ import javax.xml.bind.annotation.XmlType;
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "")
|
||||
@XmlType(name = "", propOrder = {
|
||||
"before", "after"
|
||||
})
|
||||
@XmlRootElement(name = "alterColumn")
|
||||
public class AlterColumn {
|
||||
|
||||
@XmlElement(required = false)
|
||||
protected List<DdlScript> before;
|
||||
@XmlElement(required = false)
|
||||
protected List<DdlScript> after;
|
||||
|
||||
@XmlAttribute(name = "columnName", required = true)
|
||||
protected String columnName;
|
||||
@XmlAttribute(name = "tableName", required = true)
|
||||
@@ -533,4 +544,17 @@ public class AlterColumn {
|
||||
this.dropForeignKeyIndex = value;
|
||||
}
|
||||
|
||||
public List<DdlScript> getBefore() {
|
||||
if (before == null) {
|
||||
before = new ArrayList<>();
|
||||
}
|
||||
return before;
|
||||
}
|
||||
|
||||
public List<DdlScript> getAfter() {
|
||||
if (after == null) {
|
||||
after = new ArrayList<>();
|
||||
}
|
||||
return after;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package io.ebean.dbmigration.migration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
|
||||
/**
|
||||
@@ -39,13 +42,16 @@ import javax.xml.bind.annotation.XmlValue;
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"content"
|
||||
"before", "after"
|
||||
})
|
||||
@XmlRootElement(name = "column")
|
||||
public class Column {
|
||||
|
||||
@XmlValue
|
||||
protected String content;
|
||||
|
||||
@XmlElement(required = false)
|
||||
protected List<DdlScript> before;
|
||||
@XmlElement(required = false)
|
||||
protected List<DdlScript> after;
|
||||
|
||||
@XmlAttribute(name = "name", required = true)
|
||||
protected String name;
|
||||
@XmlAttribute(name = "type", required = true)
|
||||
@@ -76,27 +82,22 @@ public class Column {
|
||||
protected String foreignKeyIndex;
|
||||
@XmlAttribute(name = "comment")
|
||||
protected String comment;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of the content property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
public List<DdlScript> getBefore() {
|
||||
if (before == null) {
|
||||
before = new ArrayList<>();
|
||||
}
|
||||
return before;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the content property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setContent(String value) {
|
||||
this.content = value;
|
||||
|
||||
public List<DdlScript> getAfter() {
|
||||
if (after == null) {
|
||||
after = new ArrayList<>();
|
||||
}
|
||||
return after;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value of the name property.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package io.ebean.dbmigration.migration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* TODO @Rob: Can this generated automatically?
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"ddl"
|
||||
})
|
||||
@XmlRootElement(name = "ddl-script")
|
||||
public class DdlScript {
|
||||
|
||||
@XmlValue
|
||||
protected List<String> ddl;
|
||||
|
||||
@XmlAttribute(name = "platforms")
|
||||
protected String platforms;
|
||||
|
||||
/**
|
||||
* Gets the value of the value property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public List<String> getDdl() {
|
||||
if (ddl == null) {
|
||||
ddl = new ArrayList<>();
|
||||
}
|
||||
return ddl;
|
||||
}
|
||||
|
||||
public void setDdl(List<String> ddl) {
|
||||
this.ddl = ddl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the platforms property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getPlatforms() {
|
||||
return platforms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the platforms property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setPlatforms(String value) {
|
||||
this.platforms = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,10 @@ public class DropTable {
|
||||
|
||||
@XmlAttribute(name = "name", required = true)
|
||||
protected String name;
|
||||
@XmlAttribute(name = "sequenceCol")
|
||||
protected String sequenceCol;
|
||||
@XmlAttribute(name = "sequenceName")
|
||||
protected String sequenceName;
|
||||
|
||||
/**
|
||||
* Gets the value of the name property.
|
||||
@@ -50,4 +54,44 @@ public class DropTable {
|
||||
this.name = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the sequenceCol property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getSequenceCol() {
|
||||
return sequenceCol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the sequenceName property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getSequenceName() {
|
||||
return sequenceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the sequenceCol property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setSequenceCol(String value) {
|
||||
this.sequenceCol = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the sequenceName property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setSequenceName(String value) {
|
||||
this.sequenceName = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -186,5 +186,13 @@ public class ObjectFactory {
|
||||
public Migration createMigration() {
|
||||
return new Migration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DdlScript }
|
||||
*/
|
||||
public DdlScript createDdlScript() {
|
||||
return new DdlScript();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package io.ebean.dbmigration.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
|
||||
import io.ebean.dbmigration.migration.AlterColumn;
|
||||
import io.ebean.dbmigration.migration.Column;
|
||||
import io.ebean.dbmigration.migration.DdlScript;
|
||||
import io.ebeaninternal.server.deploy.DbMigrationInfo;
|
||||
|
||||
/**
|
||||
* A column in the logical model.
|
||||
@@ -37,6 +42,8 @@ public class MColumn {
|
||||
private AlterColumn alterColumn;
|
||||
|
||||
private boolean draftOnly;
|
||||
|
||||
private List<DbMigrationInfo> dbMigrationInfos;
|
||||
|
||||
public MColumn(Column column) {
|
||||
this.name = column.getName();
|
||||
@@ -77,6 +84,7 @@ public class MColumn {
|
||||
copy.checkConstraint = checkConstraint;
|
||||
copy.checkConstraintName = checkConstraintName;
|
||||
copy.defaultValue = defaultValue;
|
||||
copy.dbMigrationInfos = dbMigrationInfos;
|
||||
copy.references = references;
|
||||
copy.comment = comment;
|
||||
copy.foreignKeyName = foreignKeyName;
|
||||
@@ -256,6 +264,24 @@ public class MColumn {
|
||||
c.setComment(comment);
|
||||
c.setUnique(unique);
|
||||
c.setUniqueOneToOne(uniqueOneToOne);
|
||||
|
||||
if (dbMigrationInfos != null) {
|
||||
for (DbMigrationInfo info : dbMigrationInfos) {
|
||||
if (!info.getPreAdd().isEmpty()) {
|
||||
DdlScript script = new DdlScript();
|
||||
script.setDdl(info.getPreAdd());
|
||||
script.setPlatforms(info.joinPlatforms());
|
||||
c.getBefore().add(script);
|
||||
}
|
||||
|
||||
if (!info.getPostAdd().isEmpty()) {
|
||||
DdlScript script = new DdlScript();
|
||||
script.setDdl(info.getPostAdd());
|
||||
script.setPlatforms(info.joinPlatforms());
|
||||
c.getAfter().add(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
@@ -280,6 +306,24 @@ public class MColumn {
|
||||
if (tableWithHistory) {
|
||||
alterColumn.setWithHistory(Boolean.TRUE);
|
||||
}
|
||||
|
||||
if (dbMigrationInfos != null) {
|
||||
for (DbMigrationInfo info : dbMigrationInfos) {
|
||||
if (!info.getPreAlter().isEmpty()) {
|
||||
DdlScript script = new DdlScript();
|
||||
script.setDdl(info.getPreAlter());
|
||||
script.setPlatforms(info.joinPlatforms());
|
||||
alterColumn.getBefore().add(script);
|
||||
}
|
||||
|
||||
if (!info.getPostAlter().isEmpty()) {
|
||||
DdlScript script = new DdlScript();
|
||||
script.setDdl(info.getPostAlter());
|
||||
script.setPlatforms(info.joinPlatforms());
|
||||
alterColumn.getAfter().add(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return alterColumn;
|
||||
}
|
||||
@@ -290,6 +334,8 @@ public class MColumn {
|
||||
*/
|
||||
public void compare(ModelDiff modelDiff, MTable table, MColumn newColumn) {
|
||||
|
||||
this.dbMigrationInfos = newColumn.dbMigrationInfos;
|
||||
|
||||
boolean tableWithHistory = table.isWithHistory();
|
||||
String tableName = table.getName();
|
||||
|
||||
@@ -313,14 +359,18 @@ public class MColumn {
|
||||
if (different(defaultValue, newColumn.defaultValue)) {
|
||||
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
|
||||
if (newColumn.defaultValue == null) {
|
||||
alter.setDefaultValue("DROP DEFAULT");
|
||||
alter.setDefaultValue(DdlHelp.DROP_DEFAULT);
|
||||
} else {
|
||||
alter.setDefaultValue(newColumn.defaultValue);
|
||||
}
|
||||
}
|
||||
if (different(comment, newColumn.comment)) {
|
||||
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
|
||||
alter.setComment(newColumn.comment);
|
||||
if (newColumn.comment == null) {
|
||||
alter.setComment(DdlHelp.DROP_COMMENT);
|
||||
} else {
|
||||
alter.setComment(newColumn.comment);
|
||||
}
|
||||
}
|
||||
if (different(checkConstraint, newColumn.checkConstraint)) {
|
||||
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
|
||||
@@ -378,6 +428,10 @@ public class MColumn {
|
||||
}
|
||||
}
|
||||
|
||||
public void setDbMigrationInfos(List<DbMigrationInfo> dbMigrationInfos) {
|
||||
this.dbMigrationInfos = dbMigrationInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply changes based on the AlterColumn request.
|
||||
*/
|
||||
@@ -388,6 +442,7 @@ public class MColumn {
|
||||
}
|
||||
if (hasValue(alterColumn.getDropForeignKey())) {
|
||||
foreignKeyName = null;
|
||||
references = null;
|
||||
}
|
||||
if (hasValue(alterColumn.getDropForeignKeyIndex())) {
|
||||
foreignKeyIndex = null;
|
||||
@@ -405,6 +460,9 @@ public class MColumn {
|
||||
}
|
||||
if (hasValue(alterColumn.getDefaultValue())) {
|
||||
defaultValue = alterColumn.getDefaultValue();
|
||||
if (DdlHelp.isDropDefault(defaultValue)) {
|
||||
defaultValue = null;
|
||||
}
|
||||
}
|
||||
if (hasValue(alterColumn.getCheckConstraint())) {
|
||||
checkConstraint = alterColumn.getCheckConstraint();
|
||||
@@ -427,6 +485,12 @@ public class MColumn {
|
||||
if (hasValue(alterColumn.getForeignKeyIndex())) {
|
||||
foreignKeyIndex = alterColumn.getForeignKeyIndex();
|
||||
}
|
||||
if (hasValue(alterColumn.getComment())) {
|
||||
comment = alterColumn.getComment();
|
||||
if (DdlHelp.isDropComment(comment)) {
|
||||
comment = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean.dbmigration.model;
|
||||
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
|
||||
import io.ebean.dbmigration.migration.AddColumn;
|
||||
import io.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebean.dbmigration.migration.AddTableComment;
|
||||
@@ -180,6 +181,24 @@ public class MTable {
|
||||
public DropTable dropTable() {
|
||||
DropTable dropTable = new DropTable();
|
||||
dropTable.setName(name);
|
||||
// we must add pk col name & sequence name, as we have to delete the sequence also.
|
||||
if (identityType != IdentityType.GENERATOR && identityType != IdentityType.EXTERNAL) {
|
||||
String pkCol = null;
|
||||
for (MColumn column : columns.values()) {
|
||||
if (column.isPrimaryKey()) {
|
||||
if (pkCol == null) {
|
||||
pkCol = column.getName();
|
||||
} else { // multiple pk cols -> no sequence
|
||||
pkCol = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pkCol != null) {
|
||||
dropTable.setSequenceCol(pkCol);
|
||||
dropTable.setSequenceName(sequenceName);
|
||||
}
|
||||
}
|
||||
return dropTable;
|
||||
}
|
||||
|
||||
@@ -288,7 +307,11 @@ public class MTable {
|
||||
if (MColumn.different(comment, newTable.comment)) {
|
||||
AddTableComment addTableComment = new AddTableComment();
|
||||
addTableComment.setName(name);
|
||||
addTableComment.setComment(newTable.comment);
|
||||
if (newTable.comment == null) {
|
||||
addTableComment.setComment(DdlHelp.DROP_COMMENT);
|
||||
} else {
|
||||
addTableComment.setComment(newTable.comment);
|
||||
}
|
||||
modelDiff.addTableComment(addTableComment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package io.ebean.dbmigration.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* The version of a migration used so that migrations are processed in order.
|
||||
*/
|
||||
public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
|
||||
private static final Pattern SECTION_SPLITTER = Pattern.compile("[\\.-]");
|
||||
|
||||
private static final int[] REPEAT_ORDERING = {Integer.MAX_VALUE};
|
||||
|
||||
private static final boolean[] REPEAT_UNDERSCORES = {false};
|
||||
@@ -169,7 +172,7 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
|
||||
value = value.replace('_', '.');
|
||||
|
||||
String[] sections = value.split("[\\.-]");
|
||||
String[] sections = SECTION_SPLITTER.split(value);
|
||||
|
||||
if ("r".equalsIgnoreCase(sections[0])) {
|
||||
// a "repeatable" version (does not have a version number)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.ebean.dbmigration.model;
|
||||
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
|
||||
import io.ebean.dbmigration.migration.AddColumn;
|
||||
import io.ebean.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebean.dbmigration.migration.AddTableComment;
|
||||
import io.ebean.dbmigration.migration.AlterColumn;
|
||||
import io.ebean.dbmigration.migration.ChangeSet;
|
||||
import io.ebean.dbmigration.migration.ChangeSetType;
|
||||
@@ -132,6 +134,10 @@ public class ModelContainer {
|
||||
applyChange((AddHistoryTable) change);
|
||||
} else if (change instanceof DropHistoryTable) {
|
||||
applyChange((DropHistoryTable) change);
|
||||
} else if (change instanceof AddTableComment) {
|
||||
applyChange((AddTableComment) change);
|
||||
} else {
|
||||
throw new IllegalArgumentException("No rule for " + change);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,6 +165,18 @@ public class ModelContainer {
|
||||
}
|
||||
table.setWithHistory(false);
|
||||
}
|
||||
|
||||
private void applyChange(AddTableComment change) {
|
||||
MTable table = tables.get(change.getName());
|
||||
if (table == null) {
|
||||
throw new IllegalStateException("Table [" + change.getName() + "] does not exist in model?");
|
||||
}
|
||||
if (DdlHelp.isDropComment(change.getComment())) {
|
||||
table.setComment(null);
|
||||
} else {
|
||||
table.setComment(change.getComment());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a CreateTable change to the model.
|
||||
|
||||
@@ -29,14 +29,11 @@ public class PlatformDdlWriter {
|
||||
|
||||
private final DatabasePlatform platform;
|
||||
|
||||
private final String platformPrefix;
|
||||
|
||||
private final DbMigrationConfig config;
|
||||
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix, DbMigrationConfig config) {
|
||||
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, DbMigrationConfig config) {
|
||||
this.platform = platform;
|
||||
this.serverConfig = serverConfig;
|
||||
this.platformPrefix = platformPrefix;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
String refColumn = importedProperty.getDbColumn();
|
||||
|
||||
MColumn col = table.addColumn(dbCol, columnDefn, !p.isNullable());
|
||||
|
||||
col.setDbMigrationInfos(p.getDbMigrationInfos());
|
||||
col.setDefaultValue(p.getDbColumnDefault());
|
||||
if (columns.length == 1) {
|
||||
// single references column (put it on the column)
|
||||
String refTable = importedProperty.getBeanDescriptor().getBaseTable();
|
||||
@@ -232,6 +233,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
}
|
||||
} else {
|
||||
col.setDefaultValue(p.getDbColumnDefault());
|
||||
col.setDbMigrationInfos(p.getDbMigrationInfos());
|
||||
if (!p.isNullable() || p.isDDLNotNull()) {
|
||||
col.setNotnull(true);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ Customer customer = Ebean.find(Customer.class)
|
||||
.fetch("billingAddress");
|
||||
.fetch("shippingAddress");
|
||||
.setId(7)
|
||||
.findUnique();
|
||||
.findOne();
|
||||
|
||||
|
||||
Address billAddr = customer.getBillingAddress();
|
||||
|
||||
@@ -28,7 +28,7 @@ Customer customer =
|
||||
.setId(7)
|
||||
.fetch("billingAddress")
|
||||
.fetch("shippingAddress")
|
||||
.findUnique();
|
||||
.findOne();
|
||||
|
||||
Address billAddr = customer.getBillingAddress();
|
||||
Address shipAddr = customer.getShippingAddress();
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.ebean.util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility String class that supports String manipulation functions.
|
||||
@@ -12,6 +13,10 @@ public class StringHelper {
|
||||
private static final char SINGLE_QUOTE = '\'';
|
||||
|
||||
private static final char DOUBLE_QUOTE = '"';
|
||||
|
||||
private static final Pattern SPLIT_NAMES = Pattern.compile("[\\s,;]+");
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
/**
|
||||
* parses a String of the form name1='value1' name2='value2'. Note that you
|
||||
@@ -430,6 +435,9 @@ public class StringHelper {
|
||||
* }</pre>
|
||||
*/
|
||||
public static String replaceStringMulti(String source, String[] match, String replace) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
return replaceStringMulti(source, match, replace, 30, 0, source.length());
|
||||
}
|
||||
|
||||
@@ -443,7 +451,9 @@ public class StringHelper {
|
||||
*/
|
||||
public static String replaceStringMulti(String source, String[] match, String replace,
|
||||
int additionalSize, int startPos, int endPos) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
int shortestMatch = match[0].length();
|
||||
|
||||
char[] match0 = new char[match.length];
|
||||
@@ -547,4 +557,28 @@ public class StringHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits at any whitespace "," or ";" and trims the result.
|
||||
* It does not return empty entries.
|
||||
*/
|
||||
public static String[] splitNames(String names) {
|
||||
if (names == null || names.isEmpty()) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
String[] result = SPLIT_NAMES.split(names);
|
||||
if (result.length == 0) {
|
||||
return EMPTY_STRING_ARRAY; // don't know if this ever can happen
|
||||
}
|
||||
if ("".equals(result[0])) { // = input string starts with whitespace
|
||||
if (result.length == 1) { // = input string contains only whitespace
|
||||
return EMPTY_STRING_ARRAY;
|
||||
} else {
|
||||
String ret[] = new String[result.length-1]; // remove first entry
|
||||
System.arraycopy(result, 1, ret, 0, ret.length);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class BindParams implements Serializable {
|
||||
* Bind hash and count used to detect when the bind values have changed such
|
||||
* that the generated SQL (with named parameters) needs to be recalculated.
|
||||
*/
|
||||
private int[] bindHash;
|
||||
private String bindHash;
|
||||
|
||||
public BindParams() {
|
||||
}
|
||||
@@ -56,32 +56,29 @@ public class BindParams implements Serializable {
|
||||
* taken into account when calculating the query hash.
|
||||
* </p>
|
||||
*/
|
||||
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
|
||||
int[] vals = calcQueryPlanHash();
|
||||
builder.add(vals[0]).bind(vals[1]);
|
||||
public String calcQueryPlanHash() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
buildQueryPlanHash(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and return a query plan bind hash with total bind count.
|
||||
*/
|
||||
public int[] calcQueryPlanHash() {
|
||||
public void buildQueryPlanHash(StringBuilder builder) {
|
||||
int tempBindCount;
|
||||
int bc = 0;
|
||||
int hc = 92821;
|
||||
for (Param param : positionedParameters) {
|
||||
tempBindCount = param.queryBindCount();
|
||||
bc += tempBindCount;
|
||||
hc = hc * 92821 + tempBindCount;
|
||||
builder.append("p").append(bc).append(" ?:").append(tempBindCount).append(",");
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
|
||||
tempBindCount = entry.getValue().queryBindCount();
|
||||
bc += tempBindCount;
|
||||
hc = hc * 92821 + entry.getKey().hashCode();
|
||||
hc = hc * 92821 + tempBindCount;
|
||||
builder.append("n").append(bc).append(" k:").append(entry.getKey()).append(" ?:").append(tempBindCount).append(",");
|
||||
}
|
||||
|
||||
return new int[]{hc, bc};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,9 +274,9 @@ public class BindParams implements Serializable {
|
||||
bindHash = calcQueryPlanHash();
|
||||
return false;
|
||||
}
|
||||
int[] oldPlan = bindHash;
|
||||
String oldPlan = bindHash;
|
||||
bindHash = calcQueryPlanHash();
|
||||
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
|
||||
return bindHash.equals(oldPlan);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Plugin API available to invoke something prior to container bootup.
|
||||
* <p>
|
||||
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
|
||||
* we have a test DB docker container running and setup ready to go.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiContainerBootup {
|
||||
|
||||
/**
|
||||
* Run something at bootup prior to the container starting.
|
||||
*
|
||||
* For example, start DB docker container(s).
|
||||
*/
|
||||
void bootup();
|
||||
}
|
||||
@@ -48,18 +48,13 @@ public interface SpiExpression extends Expression {
|
||||
* from an AutoTune perspective and get different tuning.
|
||||
* </p>
|
||||
*/
|
||||
void queryPlanHash(HashQueryPlanBuilder builder);
|
||||
void queryPlanHash(StringBuilder builder);
|
||||
|
||||
/**
|
||||
* Return the hash value for the values that will be bound.
|
||||
*/
|
||||
int queryBindHash();
|
||||
|
||||
/**
|
||||
* Return true if the expression is the same without taking into account bind values.
|
||||
*/
|
||||
boolean isSameByPlan(SpiExpression other);
|
||||
|
||||
/**
|
||||
* Return true if the expression is the same with respect to bind values.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.OrderBy;
|
||||
@@ -583,9 +584,9 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
boolean isUseBeanCache();
|
||||
|
||||
/**
|
||||
* Return true if this query should use/check the query cache.
|
||||
* Return the cache mode if this query should use/check the query cache.
|
||||
*/
|
||||
boolean isUseQueryCache();
|
||||
CacheMode getUseQueryCache();
|
||||
|
||||
/**
|
||||
* Return true if the beans from this query should be loaded into the bean
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
@@ -44,9 +47,9 @@ public class ExtraDdlXmlReader {
|
||||
if (platforms == null || platforms.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String[] names = platforms.split("[,;]");
|
||||
for (String name : names) {
|
||||
if (name.trim().toLowerCase().contains(platformName)) {
|
||||
|
||||
for (String name : StringHelper.splitNames(platforms)) {
|
||||
if (name.toLowerCase().contains(platformName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -82,7 +83,7 @@ class DefaultCacheHolder {
|
||||
}
|
||||
|
||||
private ServerCacheOptions getQueryOptions(Class<?> cls) {
|
||||
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
|
||||
CacheQueryTuning tuning = AnnotationBase.findAnnotation(cls, CacheQueryTuning.class);
|
||||
if (tuning != null) {
|
||||
return new ServerCacheOptions(tuning).applyDefaults(queryDefault);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public class DefaultChangeLogRegister implements ChangeLogRegister {
|
||||
* Find and return the ChangeLog annotation in the inheritance hierarchy.
|
||||
*/
|
||||
private ChangeLog getChangeLog(Class<?> beanType) {
|
||||
return AnnotationBase.findAnnotation(beanType, ChangeLog.class);
|
||||
return AnnotationBase.findAnnotationRecursive(beanType, ChangeLog.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,9 @@ package io.ebeaninternal.server.core;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.server.lib.DaemonExecutorService;
|
||||
import io.ebeaninternal.server.lib.DaemonScheduleThreadPool;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -28,12 +30,38 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
|
||||
*/
|
||||
@Override
|
||||
public void execute(Runnable r) {
|
||||
pool.execute(r);
|
||||
final Map<String, String> map = MDC.getCopyOfContextMap();
|
||||
|
||||
if (map == null) {
|
||||
pool.execute(r);
|
||||
} else {
|
||||
pool.execute(() -> {
|
||||
MDC.setContextMap(map);
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executePeriodically(Runnable r, long delay, TimeUnit unit) {
|
||||
schedulePool.scheduleWithFixedDelay(r, delay, delay, unit);
|
||||
final Map<String, String> map = MDC.getCopyOfContextMap();
|
||||
|
||||
if (map == null) {
|
||||
schedulePool.scheduleWithFixedDelay(r, delay, delay, unit);
|
||||
} else {
|
||||
schedulePool.scheduleWithFixedDelay(() -> {
|
||||
MDC.setContextMap(map);
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}, delay, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -173,7 +173,7 @@ public class DefaultBeanLoader {
|
||||
query.setReadOnly(true);
|
||||
}
|
||||
|
||||
server.findUnique(query, t);
|
||||
server.findOne(query, t);
|
||||
|
||||
if (beanCollection != null) {
|
||||
if (beanCollection.checkEmptyLazyLoad()) {
|
||||
@@ -316,7 +316,7 @@ public class DefaultBeanLoader {
|
||||
query.select("*");
|
||||
}
|
||||
|
||||
Object dbBean = query.findUnique();
|
||||
Object dbBean = query.findOne();
|
||||
if (dbBean == null) {
|
||||
String msg = "Bean not found during lazy load or refresh." + " id[" + id + "] type[" + desc.getBeanType() + "]";
|
||||
throw new EntityNotFoundException(msg);
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.dbmigration.DbOffline;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiContainerBootup;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCachePlugin;
|
||||
@@ -51,6 +52,8 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
public DefaultContainer(ContainerConfig containerConfig) {
|
||||
|
||||
invokeBootupPlugin();
|
||||
|
||||
this.clusterManager = new ClusterManager(containerConfig);
|
||||
this.jndiDataSourceFactory = new JndiDataSourceLookup();
|
||||
|
||||
@@ -59,6 +62,13 @@ public class DefaultContainer implements SpiContainer {
|
||||
ShutdownManager.registerContainer(this);
|
||||
}
|
||||
|
||||
private void invokeBootupPlugin() {
|
||||
Iterator<SpiContainerBootup> it = ServiceLoader.load(SpiContainerBootup.class).iterator();
|
||||
while (it.hasNext()) {
|
||||
it.next().bootup();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
clusterManager.shutdown();
|
||||
@@ -268,7 +278,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
private DataSource getDataSourceFromConfig(ServerConfig config) {
|
||||
|
||||
if (config.isDbOffline() || DbOffline.isSet()) {
|
||||
if (config.isOfflineMode()) {
|
||||
logger.debug("... DbOffline using platform [{}]", DbOffline.getPlatform());
|
||||
return null;
|
||||
}
|
||||
@@ -334,7 +344,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
*/
|
||||
private boolean checkDataSource(ServerConfig serverConfig) {
|
||||
|
||||
if (serverConfig.isDbOffline() || DbOffline.isSet()) {
|
||||
if (serverConfig.isOfflineMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -349,7 +359,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
Connection c = null;
|
||||
try {
|
||||
c = serverConfig.getDataSource().getConnection();
|
||||
if (c.getAutoCommit()) {
|
||||
if (!serverConfig.isAutoCommitMode() && c.getAutoCommit()) {
|
||||
logger.warn("DataSource [{}] has autoCommit defaulting to true!", serverConfig.getName());
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -35,6 +35,7 @@ import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.bean.PersistenceContext.WithOption;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.common.CopyOnFirstWriteList;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.EncryptKeyManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
@@ -99,6 +100,7 @@ import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
@@ -106,6 +108,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
@@ -624,7 +627,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
// we actually need to do a query because we don't know the type without the discriminator
|
||||
// value, just select the id property and discriminator column (auto added)
|
||||
return find(type).select(idProp.getName()).setId(id).findUnique();
|
||||
return find(type).select(idProp.getName()).setId(id).findOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -665,6 +668,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public <T> T execute(TxScope scope, TxCallable<T> c) {
|
||||
return executeCall(scope, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T executeCall(Callable<T> c) {
|
||||
return executeCall(null, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T executeCall(TxScope scope, Callable<T> c) {
|
||||
ScopeTrans scopeTrans = createScopeTrans(scope);
|
||||
try {
|
||||
return c.call();
|
||||
@@ -672,8 +685,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
} catch (Error e) {
|
||||
throw scopeTrans.caughtError(e);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
throw scopeTrans.caughtThrowable(e);
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException(scopeTrans.caughtThrowable(e));
|
||||
|
||||
} finally {
|
||||
scopeTrans.onFinally();
|
||||
@@ -681,12 +694,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(TxRunnable r) {
|
||||
public void execute(Runnable r) {
|
||||
execute(null, r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(TxScope scope, TxRunnable r) {
|
||||
public void execute(TxScope scope, Runnable r) {
|
||||
ScopeTrans scopeTrans = createScopeTrans(scope);
|
||||
try {
|
||||
r.run();
|
||||
@@ -694,8 +707,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
} catch (Error e) {
|
||||
throw scopeTrans.caughtError(e);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
throw scopeTrans.caughtThrowable(e);
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException(scopeTrans.caughtThrowable(e));
|
||||
|
||||
} finally {
|
||||
scopeTrans.onFinally();
|
||||
@@ -1034,17 +1047,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public SqlQuery createSqlQuery(String sql) {
|
||||
return new DefaultRelationalQuery(this, sql);
|
||||
return new DefaultRelationalQuery(this, sql.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlUpdate createSqlUpdate(String sql) {
|
||||
return new DefaultSqlUpdate(this, sql);
|
||||
return new DefaultSqlUpdate(this, sql.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallableSql createCallableSql(String sql) {
|
||||
return new DefaultCallableSql(this, sql);
|
||||
return new DefaultCallableSql(this, sql.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1179,11 +1192,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T findUnique(Query<T> query, Transaction transaction) {
|
||||
return findOne(query, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction) {
|
||||
return Optional.ofNullable(findOne(query, transaction));
|
||||
@@ -1300,6 +1308,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
public <T> int findCountWithCopy(Query<T> query, Transaction t) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.COUNT, query, t);
|
||||
Integer result = request.getFromQueryCache();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
return request.findCount();
|
||||
@@ -1315,10 +1327,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return findIdsWithCopy(((SpiQuery<T>) query).copy(), t);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <A, T> List<A> findIdsWithCopy(Query<T> query, Transaction t) {
|
||||
|
||||
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query, t);
|
||||
Object result = request.getFromQueryCache();
|
||||
if (result != null) {
|
||||
if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
|
||||
return new CopyOnFirstWriteList<>((List<A>) result);
|
||||
} else {
|
||||
return (List<A>) result;
|
||||
}
|
||||
}
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
return request.findIds();
|
||||
@@ -1506,11 +1527,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlRow findUnique(SqlQuery query, Transaction t) {
|
||||
return findOne(query, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlRow findOne(SqlQuery query, Transaction t) {
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.common.CopyOnFirstWriteList;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
@@ -32,6 +34,8 @@ import javax.persistence.PersistenceException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -96,7 +100,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
public String getDBLikeClause() {
|
||||
return ebeanServer.getDatabasePlatform().getLikeClause();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the database platform escaped like string.
|
||||
*/
|
||||
@@ -301,7 +305,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
*/
|
||||
@Override
|
||||
public int delete() {
|
||||
return queryEngine.delete(this);
|
||||
return notifyCache(queryEngine.delete(this), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +313,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
*/
|
||||
@Override
|
||||
public int update() {
|
||||
return queryEngine.update(this);
|
||||
return notifyCache(queryEngine.update(this), true);
|
||||
}
|
||||
|
||||
private int notifyCache(int rows, boolean update) {
|
||||
if (rows > 0 && beanDescriptor.isCaching()) {
|
||||
transaction.getEvent().add(beanDescriptor.getBaseTable(), false, update, !update);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -461,26 +472,45 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
* Try to get the query result from the query cache.
|
||||
*/
|
||||
@Override
|
||||
public BeanCollection<T> getFromQueryCache() {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getFromQueryCache() {
|
||||
|
||||
if (!query.isUseQueryCache()) {
|
||||
if (query.getUseQueryCache() == CacheMode.OFF) {
|
||||
return null;
|
||||
} else {
|
||||
cacheKey = query.queryHash();
|
||||
}
|
||||
|
||||
if (!query.getUseQueryCache().isGet()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
cacheKey = query.queryHash();
|
||||
|
||||
BeanCollection<T> cached = beanDescriptor.queryCacheGet(cacheKey);
|
||||
Object cached = beanDescriptor.queryCacheGet(cacheKey);
|
||||
|
||||
if (cached != null && isAuditReads() && readAuditQueryType()) {
|
||||
// raw sql can't use L2 cache so normal queries only in here
|
||||
Collection<T> actualDetails = cached.getActualDetails();
|
||||
List<Object> ids = new ArrayList<>(actualDetails.size());
|
||||
for (T bean : actualDetails) {
|
||||
ids.add(beanDescriptor.getIdForJson(bean));
|
||||
if (cached instanceof BeanCollection) {
|
||||
// raw sql can't use L2 cache so normal queries only in here
|
||||
Collection<T> actualDetails = ((BeanCollection<T>)cached).getActualDetails();
|
||||
List<Object> ids = new ArrayList<>(actualDetails.size());
|
||||
for (T bean : actualDetails) {
|
||||
ids.add(beanDescriptor.getIdForJson(bean));
|
||||
}
|
||||
beanDescriptor.readAuditMany(queryPlanKey.getPartialKey(), "l2-query-cache", ids);
|
||||
}
|
||||
beanDescriptor.readAuditMany(queryPlanKey.getPartialKey(), "l2-query-cache", ids);
|
||||
}
|
||||
|
||||
if (Boolean.FALSE.equals(query.isReadOnly())) {
|
||||
// return shallow copies if readonly is explicitly set to false
|
||||
if (cached instanceof BeanCollection) {
|
||||
cached = ((BeanCollection<?>)cached).getShallowCopy();
|
||||
} else if (cached instanceof List) {
|
||||
cached = new CopyOnFirstWriteList<>((List)cached);
|
||||
} else if (cached instanceof Set) {
|
||||
cached = new LinkedHashSet<>((Set)cached);
|
||||
} else if (cached instanceof Map) {
|
||||
cached = new LinkedHashMap<>((Map)cached);
|
||||
}
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
@@ -502,7 +532,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
}
|
||||
}
|
||||
|
||||
public void putToQueryCache(BeanCollection<T> queryResult) {
|
||||
public void putToQueryCache(Object queryResult) {
|
||||
beanDescriptor.queryCachePut(cacheKey, queryResult);
|
||||
}
|
||||
|
||||
@@ -543,7 +573,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
* </p>
|
||||
*/
|
||||
public boolean isAuditReads() {
|
||||
return !query.isDisableReadAudit() && beanDescriptor.isReadAuditing();
|
||||
return beanDescriptor.isReadAuditing() && !query.isDisableReadAudit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -524,7 +524,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
/**
|
||||
* Prepare the update after potential modifications in a BeanPersistController.
|
||||
*/
|
||||
public void postControllerPrepareUpdate() {
|
||||
private void postControllerPrepareUpdate() {
|
||||
if (intercept.isNew() && controller != null) {
|
||||
// 'stateless update' - set dirty properties modified in controller preUpdate
|
||||
intercept.setNewBeanForUpdate();
|
||||
@@ -1072,31 +1072,38 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return version;
|
||||
}
|
||||
|
||||
public void executeInsert() {
|
||||
private void setTenantId() {
|
||||
Object tenantId = transaction.getTenantId();
|
||||
if (tenantId != null) {
|
||||
beanDescriptor.setTenantId(entityBean, tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeInsert() {
|
||||
setTenantId();
|
||||
if (controller == null || controller.preInsert(this)) {
|
||||
beanManager.getBeanPersister().insert(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeUpdate() {
|
||||
private void executeUpdate() {
|
||||
setTenantId();
|
||||
if (controller == null || controller.preUpdate(this)) {
|
||||
postControllerPrepareUpdate();
|
||||
beanManager.getBeanPersister().update(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void executeSoftDelete() {
|
||||
private void executeSoftDelete() {
|
||||
setTenantId();
|
||||
if (controller == null || controller.preSoftDelete(this)) {
|
||||
postControllerPrepareUpdate();
|
||||
beanManager.getBeanPersister().update(this);
|
||||
}
|
||||
}
|
||||
|
||||
public int executeDelete() {
|
||||
private int executeDelete() {
|
||||
setTenantId();
|
||||
if (controller == null || controller.preDelete(this)) {
|
||||
return beanManager.getBeanPersister().delete(this);
|
||||
}
|
||||
|
||||
@@ -111,6 +111,8 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
case SQL_DELETE:
|
||||
transaction.getEvent().add(tableName, false, false, true);
|
||||
break;
|
||||
case SQL_UNKNOWN:
|
||||
transaction.markNotQueryOnly();
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.ebeaninternal.api.SpiSqlQuery;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.server.lib.util.Str;
|
||||
import io.ebeaninternal.server.persist.Binder;
|
||||
import io.ebeaninternal.server.persist.TrimLogSql;
|
||||
import io.ebeaninternal.server.query.DefaultSqlRow;
|
||||
import io.ebeaninternal.server.transaction.TransactionManager;
|
||||
import io.ebeaninternal.server.util.BindParamsParser;
|
||||
@@ -247,7 +248,7 @@ public final class RelationalQueryRequest {
|
||||
}
|
||||
|
||||
if (isLogSql()) {
|
||||
String logSql = sql;
|
||||
String logSql = TrimLogSql.trim(sql);
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeanservice.docstore.api.DocQueryRequest;
|
||||
@@ -117,13 +116,13 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
|
||||
/**
|
||||
* Try to get the query result from the query cache.
|
||||
*/
|
||||
BeanCollection<T> getFromQueryCache();
|
||||
<A> A getFromQueryCache();
|
||||
|
||||
/**
|
||||
* Return the Database platform like clause.
|
||||
*/
|
||||
String getDBLikeClause();
|
||||
|
||||
|
||||
/**
|
||||
* Escapes a string to use it as exact match in Like clause.
|
||||
*/
|
||||
|
||||
@@ -16,6 +16,7 @@ import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import org.avaje.classpath.scanner.ClassFilter;
|
||||
import org.slf4j.Logger;
|
||||
@@ -470,7 +471,10 @@ public class BootupClasses implements ClassFilter {
|
||||
return has(cls, Embeddable.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this class has the annotation (or meta annotation). Does not search recursively.
|
||||
*/
|
||||
private boolean has(Class<?> cls, Class<? extends Annotation> ann) {
|
||||
return cls.getAnnotation(ann) != null;
|
||||
return AnnotationBase.findAnnotation(cls, ann) != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package io.ebeaninternal.server.core.bootup;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
@@ -73,12 +76,8 @@ class ManifestReader {
|
||||
* Collect each individual package splitting by delimiters.
|
||||
*/
|
||||
private void add(String packages) {
|
||||
String[] split = packages.split("[,; ]");
|
||||
for (String aSplit : split) {
|
||||
String pkg = aSplit.trim();
|
||||
if (!pkg.isEmpty()) {
|
||||
packageSet.add(pkg);
|
||||
}
|
||||
for (String pkg : StringHelper.splitNames(packages)) {
|
||||
packageSet.add(pkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,7 +713,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
|
||||
void registerColumn(String dbColumn, String path) {
|
||||
columnPath.put(dbColumn.toLowerCase(), path);
|
||||
String key = dbColumn.toLowerCase();
|
||||
// check for clash with imported OneToOne PK
|
||||
if (!columnPath.containsKey(key)) {
|
||||
columnPath.put(key, path);
|
||||
}
|
||||
}
|
||||
|
||||
void registerTable(String baseTable, BeanPropertyAssoc<?> assocProperty) {
|
||||
@@ -1042,6 +1046,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full name taking into account inheritance.
|
||||
*/
|
||||
public String rootName() {
|
||||
if (inheritInfo != null && !inheritInfo.isRoot()) {
|
||||
return inheritInfo.getRoot().desc().getName();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the named ORM query.
|
||||
*/
|
||||
@@ -1116,6 +1130,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is bean or query caching for this type.
|
||||
*/
|
||||
public boolean isCaching() {
|
||||
return cacheHelp.isCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is currently bean caching for this type of bean.
|
||||
*/
|
||||
@@ -1163,15 +1184,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
/**
|
||||
* Get a query result from the query cache.
|
||||
*/
|
||||
public BeanCollection<T> queryCacheGet(Object id) {
|
||||
public Object queryCacheGet(Object id) {
|
||||
return cacheHelp.queryCacheGet(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query result into the query cache.
|
||||
*/
|
||||
public void queryCachePut(Object id, BeanCollection<T> query) {
|
||||
cacheHelp.queryCachePut(id, query);
|
||||
public void queryCachePut(Object id, Object queryResult) {
|
||||
cacheHelp.queryCachePut(id, queryResult);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3007,6 +3028,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return versionProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tenant property when multi-tenant partitioning support is used.
|
||||
*/
|
||||
public BeanProperty getTenantProperty() {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scalar properties without the unique id or secondary table properties.
|
||||
*/
|
||||
|
||||
@@ -155,6 +155,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return beanCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is bean or query caching on this type.
|
||||
*/
|
||||
boolean isCaching() {
|
||||
return beanCache != null || queryCache != null;
|
||||
}
|
||||
|
||||
CacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
@@ -183,33 +190,32 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Get a query result from the query cache.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
BeanCollection<T> queryCacheGet(Object id) {
|
||||
Object queryCacheGet(Object id) {
|
||||
if (queryCache == null) {
|
||||
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
|
||||
}
|
||||
BeanCollection<T> list = (BeanCollection<T>) queryCache.get(id);
|
||||
Object queryResult = queryCache.get(id);
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
if (list == null) {
|
||||
if (queryResult == null) {
|
||||
queryLog.debug(" GET {}({}) - cache miss", cacheName, id);
|
||||
} else {
|
||||
queryLog.debug(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
return queryResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query result into the query cache.
|
||||
*/
|
||||
void queryCachePut(Object id, BeanCollection<T> query) {
|
||||
void queryCachePut(Object id, Object queryResult) {
|
||||
if (queryCache == null) {
|
||||
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
|
||||
}
|
||||
if (queryLog.isDebugEnabled()) {
|
||||
queryLog.debug(" PUT {}({})", cacheName, id);
|
||||
}
|
||||
queryCache.put(id, query);
|
||||
queryCache.put(id, queryResult);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
|
||||
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
|
||||
import io.ebeaninternal.server.deploy.parse.DeployInherit;
|
||||
@@ -1451,8 +1452,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
* If so it is ok for it not to be enhanced.
|
||||
*/
|
||||
private boolean isMappedSuperWithNoProperties(Class<?> beanClass) {
|
||||
// Attention: do not use AnnotationBase.findAnnotation(cls,...) here.
|
||||
MappedSuperclass annotation = beanClass.getAnnotation(MappedSuperclass.class);
|
||||
// do not search recursive here
|
||||
MappedSuperclass annotation = AnnotationBase.findAnnotation(beanClass, MappedSuperclass.class);
|
||||
if (annotation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
* DB Column default value for DDL definition (FALSE, NOW etc).
|
||||
*/
|
||||
final String dbColumnDefault;
|
||||
final List<DbMigrationInfo> dbMigrationInfos;
|
||||
|
||||
/**
|
||||
* Database DDL column comment.
|
||||
@@ -303,7 +304,8 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
this.dbLength = deploy.getDbLength();
|
||||
this.dbScale = deploy.getDbScale();
|
||||
this.dbColumnDefn = InternString.intern(deploy.getDbColumnDefn());
|
||||
this.dbColumnDefault = deploy.getDbColumnDefault();
|
||||
this.dbColumnDefault = deploy.getDbColumnDefaultSqlLiteral();
|
||||
this.dbMigrationInfos = deploy.getDbMigrationInfos();
|
||||
|
||||
this.inherited = false;// deploy.isInherited();
|
||||
this.owningType = deploy.getOwningType();
|
||||
@@ -414,6 +416,7 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
this.dbScale = source.getDbScale();
|
||||
this.dbColumnDefn = InternString.intern(source.getDbColumnDefn());
|
||||
this.dbColumnDefault = source.dbColumnDefault;
|
||||
this.dbMigrationInfos = source.dbMigrationInfos;
|
||||
|
||||
this.inherited = source.isInherited();
|
||||
this.owningType = source.owningType;
|
||||
@@ -1033,6 +1036,13 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
return dbColumnDefn != null ? null : dbColumnDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL-Migration Infos
|
||||
*/
|
||||
public List<DbMigrationInfo> getDbMigrationInfos() {
|
||||
return dbMigrationInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean Field associated with this property.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.ebean.Platform;
|
||||
/**
|
||||
* Class to hold the DDL-migration information that is needed to do correct alters.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class DbMigrationInfo {
|
||||
|
||||
private final List<String> preAdd;
|
||||
private final List<String> postAdd;
|
||||
private final List<String> preAlter;
|
||||
private final List<String> postAlter;
|
||||
private final List<Platform> platforms;
|
||||
|
||||
public DbMigrationInfo(String[] preAdd, String[] postAdd, String[] preAlter, String[] postAlter, Platform[] platforms) {
|
||||
this.preAdd = toList(preAdd);
|
||||
this.postAdd = toList(postAdd);
|
||||
this.preAlter = toList(preAlter);
|
||||
this.postAlter = toList(postAlter);
|
||||
this.platforms = toList(platforms);
|
||||
}
|
||||
|
||||
private <T> List<T> toList(T[] scripts) {
|
||||
if (scripts.length == 0) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return Collections.unmodifiableList(Arrays.asList(scripts));
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getPreAdd() {
|
||||
return preAdd;
|
||||
}
|
||||
public List<String> getPostAdd() {
|
||||
return postAdd;
|
||||
}
|
||||
public List<String> getPreAlter() {
|
||||
return preAlter;
|
||||
}
|
||||
public List<String> getPostAlter() {
|
||||
return postAlter;
|
||||
}
|
||||
public List<Platform> getPlatforms() {
|
||||
return platforms;
|
||||
}
|
||||
|
||||
public String joinPlatforms() {
|
||||
if (platforms.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Platform p : platforms) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(p.name().toLowerCase());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public class DeployDocPropertyOptions {
|
||||
}
|
||||
|
||||
private void setBoost(float boost) {
|
||||
if (boost != 1) {
|
||||
if (Float.compare(boost, 1.0F) != 0) {
|
||||
mapping.setBoost(boost);
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -45,6 +45,10 @@ public class GeneratedPropertyFactory {
|
||||
if (currentUserProvider != null) {
|
||||
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
|
||||
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
|
||||
} else if (serverConfig.isOfflineMode()) {
|
||||
currentUserProvider = new DummyCurrentUser();
|
||||
generatedWhoCreated = new GeneratedWhoCreated(currentUserProvider);
|
||||
generatedWhoModified = new GeneratedWhoModified(currentUserProvider);
|
||||
} else {
|
||||
generatedWhoCreated = null;
|
||||
generatedWhoModified = null;
|
||||
@@ -150,4 +154,11 @@ public class GeneratedPropertyFactory {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private static class DummyCurrentUser implements CurrentUserProvider {
|
||||
@Override
|
||||
public Object currentUser() {
|
||||
throw new RuntimeException("never called");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.DeployDocPropertyOptions;
|
||||
import io.ebeaninternal.server.deploy.DbMigrationInfo;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
@@ -34,7 +35,9 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -221,6 +224,8 @@ public class DeployBeanProperty {
|
||||
|
||||
private String dbColumnDefault;
|
||||
|
||||
private List<DbMigrationInfo> dbMigrationInfos;
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
@@ -949,7 +954,9 @@ public class DeployBeanProperty {
|
||||
public void checkPrimitiveBoolean() {
|
||||
if (boolean.class.equals(propertyType) && !softDelete) {
|
||||
this.nullable = false;
|
||||
this.dbColumnDefault = DbDefaultValue.FALSE;
|
||||
if (dbColumnDefault == null) {
|
||||
this.dbColumnDefault = DbDefaultValue.FALSE;
|
||||
}
|
||||
|
||||
} else if (!id && !versionColumn && PRIMITIVE_NUMBER_TYPES.contains(propertyType)) {
|
||||
this.nullable = false;
|
||||
@@ -999,8 +1006,15 @@ public class DeployBeanProperty {
|
||||
return docMapping.create();
|
||||
}
|
||||
|
||||
public String getDbColumnDefault() {
|
||||
return dbColumnDefault;
|
||||
/**
|
||||
* Return the DB Column default taking into account literal translation.
|
||||
*/
|
||||
public String getDbColumnDefaultSqlLiteral() {
|
||||
return DbDefaultValue.toSqlLiteral(dbColumnDefault, propertyType, dbType);
|
||||
}
|
||||
|
||||
public void setDbColumnDefault(String dbColumnDefault) {
|
||||
this.dbColumnDefault = dbColumnDefault;
|
||||
}
|
||||
|
||||
public void setTenantId() {
|
||||
@@ -1013,4 +1027,16 @@ public class DeployBeanProperty {
|
||||
public boolean isTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void addDbMigrationInfo(DbMigrationInfo info) {
|
||||
if (dbMigrationInfos == null) {
|
||||
dbMigrationInfos = new ArrayList<>();
|
||||
}
|
||||
dbMigrationInfos.add(info);
|
||||
}
|
||||
|
||||
public List<DbMigrationInfo> getDbMigrationInfos() {
|
||||
return dbMigrationInfos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,29 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Provides some base methods for processing deployment annotations.
|
||||
* Provides some base methods for processing deployment annotations. All findAnnotation* methods
|
||||
* are capable to search for meta-annotations (annotation that has an other annotation)
|
||||
*
|
||||
* <p>search algorithm for ONE annotation:</p>
|
||||
* <ul>
|
||||
* <li>Check if annotation is direct on the property</li>
|
||||
* <li>if not found: Check all annotations at the annotateElement
|
||||
* if they have the needed annotation as meta annotation</li>
|
||||
* <li>if not found: go up to super class and try again
|
||||
* (only findAnnotationRecursive)</li>
|
||||
* </ul>
|
||||
* DFS (Depth-First-Search) is used. The algorithm is the same as it is used in Spring-Framework,
|
||||
* as the code is taken from there.
|
||||
*
|
||||
* <p>search algoritm for a Set<Annotation> works a litte bit different, as it does not stop
|
||||
* on the first match, but continues searching down to the last corner to find all annotations.</p>
|
||||
*
|
||||
* <p>To prevent endless recursion, the search algoritm tracks all visited annotations</p>
|
||||
*
|
||||
* <p>Supports also "java 1.6 repeatable containers" like{@link JoinColumn} / {@link JoinColumns}.</p>
|
||||
*
|
||||
* <p>This means, searching for <code>JoinColumn</code> will find them also if they are inside a
|
||||
* <code>JoinColumn<b>s</b></code> annotation</p>
|
||||
*/
|
||||
public abstract class AnnotationBase {
|
||||
|
||||
@@ -51,12 +73,13 @@ public abstract class AnnotationBase {
|
||||
/**
|
||||
* Return the annotation for the property.
|
||||
* <p>
|
||||
* Looks first at the field and then at the getter method.
|
||||
* Looks first at the field and then at the getter method. It searches for meta-annotations, but not
|
||||
* recursively in the class hierarchy.
|
||||
* </p>
|
||||
* <p>
|
||||
* If a <code>repeatable</code> annotation class is specified and the annotation is platform
|
||||
* specific(see {@link #getPlatformMatchingAnnotation(Set, Platform)}), then the platform specific
|
||||
* annotation is returned. Otherwise the first annotation is returned. Note that you must no longer
|
||||
* annotation is returned. Otherwise the first annotation is returned. Note that you need no longer
|
||||
* handle "java 1.6 repeatable containers" like {@link JoinColumn} / {@link JoinColumns} yourself.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -101,6 +124,7 @@ public abstract class AnnotationBase {
|
||||
* Return the annotation for the property.
|
||||
* <p>
|
||||
* Looks first at the field and then at the getter method. then at class level.
|
||||
* (This is used for SequenceGenerator e.g.)
|
||||
* </p>
|
||||
*/
|
||||
protected <T extends Annotation> T find(DeployBeanProperty prop, Class<T> annClass) {
|
||||
@@ -134,7 +158,7 @@ public abstract class AnnotationBase {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
// check if directly present, if not, start recursive traversal
|
||||
// check if directly present, if not, start search for meta-annotations.
|
||||
A ann = annotatedElement.getAnnotation(annotationType);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
@@ -149,11 +173,11 @@ public abstract class AnnotationBase {
|
||||
* the supplied element.
|
||||
* <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)!
|
||||
*/
|
||||
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
|
||||
public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
// check if directly present, if not, start recursive traversal
|
||||
// check if directly present, if not, start search for meta-annotations.
|
||||
A ann = clazz.getAnnotation(annotationType);
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
@@ -163,7 +187,7 @@ public abstract class AnnotationBase {
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
// not present at this class - traverse to superclass
|
||||
// no meta-annotation present at this class - traverse to superclass
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
@@ -182,6 +206,22 @@ public abstract class AnnotationBase {
|
||||
return getPlatformMatchingAnnotation(anns, platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all annotations recusively for a class and its superclasses.
|
||||
*/
|
||||
public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
Set<A> ret = new LinkedHashSet<>();
|
||||
Set<Annotation> visited = new HashSet<>();
|
||||
while (clazz != null && clazz != Object.class) {
|
||||
findMetaAnnotations(clazz, annotationType, ret, visited);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm avoiding endless recursion by tracking which
|
||||
* annotations have already been visited.
|
||||
@@ -265,6 +305,7 @@ public abstract class AnnotationBase {
|
||||
}
|
||||
|
||||
private static final ConcurrentMap<Annotation, Method> valueMethods = new ConcurrentHashMap<>();
|
||||
// only a non-null-marker the valueMethods - Cache
|
||||
private static final Method nullMethod = getNullMethod();
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ public class AnnotationClass extends AnnotationParser {
|
||||
public void parseAttributeOverride() {
|
||||
|
||||
Class<?> cls = descriptor.getBeanType();
|
||||
AttributeOverride override = AnnotationBase.findAnnotation(cls, AttributeOverride.class);
|
||||
AttributeOverride override = AnnotationBase.findAnnotationRecursive(cls, AttributeOverride.class);
|
||||
if (override != null) {
|
||||
String propertyName = override.name();
|
||||
Column column = override.column();
|
||||
@@ -109,14 +109,14 @@ public class AnnotationClass extends AnnotationParser {
|
||||
private void read(Class<?> cls) {
|
||||
|
||||
// maybe doc store only so check for this before @Entity
|
||||
DocStore docStore = AnnotationBase.findAnnotation(cls, DocStore.class);
|
||||
DocStore docStore = AnnotationBase.findAnnotationRecursive(cls, DocStore.class);
|
||||
if (docStore != null) {
|
||||
descriptor.readDocStore(docStore);
|
||||
descriptor.setEntityType(EntityType.DOC);
|
||||
descriptor.setName(cls.getSimpleName());
|
||||
}
|
||||
|
||||
Entity entity = AnnotationBase.findAnnotation(cls, Entity.class);
|
||||
Entity entity = AnnotationBase.findAnnotationRecursive(cls, Entity.class);
|
||||
if (entity != null) {
|
||||
descriptor.setEntityType(EntityType.ORM);
|
||||
if (entity.name().isEmpty()) {
|
||||
@@ -126,27 +126,27 @@ public class AnnotationClass extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
Embeddable embeddable = AnnotationBase.findAnnotation(cls, Embeddable.class);
|
||||
Embeddable embeddable = AnnotationBase.findAnnotationRecursive(cls, Embeddable.class);
|
||||
if (embeddable != null) {
|
||||
descriptor.setEntityType(EntityType.EMBEDDED);
|
||||
descriptor.setName("Embeddable:" + cls.getSimpleName());
|
||||
}
|
||||
|
||||
Set<Index> indices = AnnotationBase.findAnnotations(cls, Index.class);
|
||||
Set<Index> indices = AnnotationBase.findAnnotationsRecursive(cls, Index.class);
|
||||
for (Index index : indices) {
|
||||
descriptor.addIndex(new IndexDefinition(index.columnNames(), index.name(), index.unique()));
|
||||
}
|
||||
|
||||
UniqueConstraint uc = AnnotationBase.findAnnotation(cls, UniqueConstraint.class);
|
||||
UniqueConstraint uc = AnnotationBase.findAnnotationRecursive(cls, UniqueConstraint.class);
|
||||
if (uc != null) {
|
||||
descriptor.addIndex(new IndexDefinition(uc.columnNames()));
|
||||
}
|
||||
|
||||
View view = AnnotationBase.findAnnotation(cls, View.class);
|
||||
View view = AnnotationBase.findAnnotationRecursive(cls, View.class);
|
||||
if (view != null) {
|
||||
descriptor.setView(view.name(), view.dependentTables());
|
||||
}
|
||||
Table table = AnnotationBase.findAnnotation(cls, Table.class);
|
||||
Table table = AnnotationBase.findAnnotationRecursive(cls, Table.class);
|
||||
if (table != null) {
|
||||
UniqueConstraint[] uniqueConstraints = table.uniqueConstraints();
|
||||
for (UniqueConstraint c : uniqueConstraints) {
|
||||
@@ -154,42 +154,42 @@ public class AnnotationClass extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
Draftable draftable = AnnotationBase.findAnnotation(cls, Draftable.class);
|
||||
Draftable draftable = AnnotationBase.findAnnotationRecursive(cls, Draftable.class);
|
||||
if (draftable != null) {
|
||||
descriptor.setDraftable();
|
||||
}
|
||||
|
||||
DraftableElement draftableElement = AnnotationBase.findAnnotation(cls, DraftableElement.class);
|
||||
DraftableElement draftableElement = AnnotationBase.findAnnotationRecursive(cls, DraftableElement.class);
|
||||
if (draftableElement != null) {
|
||||
descriptor.setDraftableElement();
|
||||
}
|
||||
|
||||
ReadAudit readAudit = AnnotationBase.findAnnotation(cls, ReadAudit.class);
|
||||
ReadAudit readAudit = AnnotationBase.findAnnotationRecursive(cls, ReadAudit.class);
|
||||
if (readAudit != null) {
|
||||
descriptor.setReadAuditing();
|
||||
}
|
||||
|
||||
History history = AnnotationBase.findAnnotation(cls, History.class);
|
||||
History history = AnnotationBase.findAnnotationRecursive(cls, History.class);
|
||||
if (history != null) {
|
||||
descriptor.setHistorySupport();
|
||||
}
|
||||
|
||||
DbComment comment = AnnotationBase.findAnnotation(cls, DbComment.class);
|
||||
DbComment comment = AnnotationBase.findAnnotationRecursive(cls, DbComment.class);
|
||||
if (comment != null) {
|
||||
descriptor.setDbComment(comment.value());
|
||||
}
|
||||
|
||||
UpdateMode updateMode = AnnotationBase.findAnnotation(cls, UpdateMode.class);
|
||||
UpdateMode updateMode = AnnotationBase.findAnnotationRecursive(cls, UpdateMode.class);
|
||||
if (updateMode != null) {
|
||||
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
|
||||
}
|
||||
|
||||
Cache cache = AnnotationBase.findAnnotation(cls, Cache.class);
|
||||
Cache cache = AnnotationBase.findAnnotationRecursive(cls, Cache.class);
|
||||
if (cache != null && !disableL2Cache) {
|
||||
descriptor.setCache(cache);
|
||||
}
|
||||
|
||||
Set<NamedQuery> namedQueries = AnnotationBase.findAnnotations(cls, NamedQuery.class);
|
||||
Set<NamedQuery> namedQueries = AnnotationBase.findAnnotationsRecursive(cls, NamedQuery.class);
|
||||
for (NamedQuery namedQuery : namedQueries) {
|
||||
descriptor.addNamedQuery(namedQuery.name(), namedQuery.query());
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebeaninternal.server.deploy.DbMigrationInfo;
|
||||
import io.ebeaninternal.server.deploy.IndexDefinition;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
@@ -131,6 +132,7 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
|
||||
initWhoProperties(prop);
|
||||
readDbMigration(prop);
|
||||
}
|
||||
|
||||
private void initWhoProperties(DeployBeanProperty prop) {
|
||||
@@ -287,14 +289,20 @@ public class AnnotationFields extends AnnotationParser {
|
||||
if (get(prop, HistoryExclude.class) != null) {
|
||||
prop.setExcludedFromHistory();
|
||||
}
|
||||
|
||||
Length length = get(prop, Length.class);
|
||||
if (length != null) {
|
||||
prop.setDbLength(length.value());
|
||||
}
|
||||
|
||||
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
|
||||
if (nonNull != null) {
|
||||
prop.setNullable(false);
|
||||
}
|
||||
|
||||
readDbMigration(prop);
|
||||
|
||||
|
||||
if (validationAnnotations) {
|
||||
NotNull notNull = get(prop, NotNull.class);
|
||||
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
|
||||
@@ -346,6 +354,17 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void readDbMigration(DeployBeanProperty prop) {
|
||||
DbDefault dbDefault = get(prop, DbDefault.class);
|
||||
if (dbDefault != null) {
|
||||
prop.setDbColumnDefault(dbDefault.value());
|
||||
}
|
||||
|
||||
Set<DbMigration> dbMigration = getAll(prop, DbMigration.class);
|
||||
dbMigration.forEach(ann -> prop.addDbMigrationInfo(
|
||||
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
|
||||
}
|
||||
|
||||
private void addIndex(DeployBeanProperty prop, Index index) {
|
||||
String[] columnNames;
|
||||
if (index.columnNames().length == 0) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import io.ebean.annotation.EbeanDDL;
|
||||
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
@@ -94,19 +93,13 @@ public abstract class AnnotationParser extends AnnotationBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the validation groups are {@link Default} (respectively empty) or contains {@link EbeanDDL}
|
||||
* Return true if the validation groups are {@link Default} (respectively empty)
|
||||
* can be applied to DDL generation.
|
||||
*/
|
||||
protected boolean isEbeanValidationGroups(Class<?>[] groups) {
|
||||
if (groups.length == 0
|
||||
|| groups.length == 1 && javax.validation.groups.Default.class.isAssignableFrom(groups[0])) {
|
||||
return true;
|
||||
} else {
|
||||
for (Class<?> group : groups) {
|
||||
if (EbeanDDL.class.isAssignableFrom(group)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class AnnotationSql extends AnnotationParser {
|
||||
@Override
|
||||
public void parse() {
|
||||
Class<?> cls = descriptor.getBeanType();
|
||||
Sql sql = AnnotationBase.findAnnotation(cls, Sql.class);
|
||||
Sql sql = AnnotationBase.findAnnotationRecursive(cls, Sql.class);
|
||||
if (sql != null) {
|
||||
descriptor.setEntityType(BeanDescriptor.EntityType.SQL);
|
||||
}
|
||||
|
||||
@@ -109,11 +109,11 @@ public class DeployInherit {
|
||||
info.setParent(parent);
|
||||
}
|
||||
|
||||
Inheritance ia = AnnotationBase.findAnnotation(cls, Inheritance.class);
|
||||
Inheritance ia = AnnotationBase.findAnnotationRecursive(cls, Inheritance.class);
|
||||
if (ia != null) {
|
||||
ia.strategy();
|
||||
}
|
||||
DiscriminatorColumn da = AnnotationBase.findAnnotation(cls, DiscriminatorColumn.class);
|
||||
DiscriminatorColumn da = AnnotationBase.findAnnotationRecursive(cls, DiscriminatorColumn.class);
|
||||
if (da != null) {
|
||||
// lowercase the discriminator column for RawSql and JSON
|
||||
info.setColumnName(da.name().toLowerCase());
|
||||
@@ -122,7 +122,7 @@ public class DeployInherit {
|
||||
info.setColumnDefn(da.columnDefinition());
|
||||
}
|
||||
|
||||
DiscriminatorValue dv = AnnotationBase.findAnnotation(cls, DiscriminatorValue.class);
|
||||
DiscriminatorValue dv = AnnotationBase.findAnnotationRecursive(cls, DiscriminatorValue.class);
|
||||
if (dv != null) {
|
||||
info.setDiscriminatorValue(dv.value());
|
||||
}
|
||||
@@ -144,7 +144,7 @@ public class DeployInherit {
|
||||
if (cls.equals(Object.class)) {
|
||||
return false;
|
||||
}
|
||||
Annotation a = AnnotationBase.findAnnotation(cls, Inheritance.class);
|
||||
Annotation a = AnnotationBase.findAnnotationRecursive(cls, Inheritance.class);
|
||||
if (a != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package io.ebeaninternal.server.el;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Comparator for use with the expression objects.
|
||||
*/
|
||||
public interface ElComparator<T> extends Comparator<T> {
|
||||
public interface ElComparator<T> extends Comparator<T>, Serializable {
|
||||
|
||||
/**
|
||||
* Compare given 2 beans.
|
||||
|
||||
@@ -10,6 +10,8 @@ import java.util.Comparator;
|
||||
*/
|
||||
public final class ElComparatorCompound<T> implements Comparator<T>, ElComparator<T> {
|
||||
|
||||
private static final long serialVersionUID = -1523163475050929750L;
|
||||
|
||||
private final ElComparator<T>[] array;
|
||||
|
||||
public ElComparatorCompound(ElComparator<T>[] array) {
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.util.Comparator;
|
||||
*/
|
||||
public final class ElComparatorProperty<T> implements Comparator<T>, ElComparator<T> {
|
||||
|
||||
private static final long serialVersionUID = -2735738237263956073L;
|
||||
|
||||
private final ElPropertyValue elGetValue;
|
||||
|
||||
private final int nullOrder;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
@@ -12,7 +11,7 @@ import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
*/
|
||||
public abstract class AbstractTextExpression extends AbstractExpression {
|
||||
|
||||
protected AbstractTextExpression(String propName) {
|
||||
AbstractTextExpression(String propName) {
|
||||
super(propName);
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ public abstract class AbstractTextExpression extends AbstractExpression {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
// do nothing, only execute against document store
|
||||
}
|
||||
|
||||
@@ -42,12 +41,6 @@ public abstract class AbstractTextExpression extends AbstractExpression {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
// do not compare by plan / bind values (this way)
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
// do not compare by plan / bind values (this way)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
@@ -105,16 +104,21 @@ class AllEqualsExpression extends NonPrepareExpression {
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
|
||||
builder.add(AllEqualsExpression.class);
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
|
||||
builder.append("AllEquals[");
|
||||
for (Entry<String, Object> entry : propMap.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
String propName = entry.getKey();
|
||||
builder.add(propName).add(value == null ? 0 : 1);
|
||||
builder.bindIfNotNull(value);
|
||||
builder.append(propName);
|
||||
if (value == null) {
|
||||
builder.append(" isNull");
|
||||
} else {
|
||||
builder.append(" =?");
|
||||
}
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,16 +132,6 @@ class AllEqualsExpression extends NonPrepareExpression {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof AllEqualsExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AllEqualsExpression that = (AllEqualsExpression) other;
|
||||
return isSameByValue(that, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
if (!(other instanceof AllEqualsExpression)) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
@@ -15,7 +14,7 @@ public class ArrayContainsExpression extends AbstractExpression {
|
||||
|
||||
private final Object[] values;
|
||||
|
||||
protected ArrayContainsExpression(String propName, boolean contains, Object... values) {
|
||||
ArrayContainsExpression(String propName, boolean contains, Object... values) {
|
||||
super(propName);
|
||||
this.contains = contains;
|
||||
this.values = values;
|
||||
@@ -43,9 +42,10 @@ public class ArrayContainsExpression extends AbstractExpression {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(ArrayContainsExpression.class).add(propName).add(contains);
|
||||
builder.bind(values.length);
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
builder.append("ArrayContains[").append(propName)
|
||||
.append(" b:").append(contains)
|
||||
.append(" ?:").append(values.length).append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,17 +57,6 @@ public class ArrayContainsExpression extends AbstractExpression {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof ArrayContainsExpression)) {
|
||||
return false;
|
||||
}
|
||||
ArrayContainsExpression that = (ArrayContainsExpression) other;
|
||||
return this.propName.equals(that.propName)
|
||||
&& this.contains == that.contains
|
||||
&& this.values.length == that.values.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
ArrayContainsExpression that = (ArrayContainsExpression) other;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
@@ -13,7 +12,7 @@ public class ArrayIsEmptyExpression extends AbstractExpression {
|
||||
|
||||
private final boolean empty;
|
||||
|
||||
protected ArrayIsEmptyExpression(String propName, boolean empty) {
|
||||
ArrayIsEmptyExpression(String propName, boolean empty) {
|
||||
super(propName);
|
||||
this.empty = empty;
|
||||
}
|
||||
@@ -24,8 +23,13 @@ public class ArrayIsEmptyExpression extends AbstractExpression {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(ArrayIsEmptyExpression.class).add(propName);
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
if (empty) {
|
||||
builder.append("ArrayIsEmpty[");
|
||||
} else {
|
||||
builder.append("ArrayIsNotEmpty[");
|
||||
}
|
||||
builder.append(propName).append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,15 +37,6 @@ public class ArrayIsEmptyExpression extends AbstractExpression {
|
||||
return empty ? 0 : 92821;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof ArrayIsEmptyExpression)) {
|
||||
return false;
|
||||
}
|
||||
ArrayIsEmptyExpression that = (ArrayIsEmptyExpression) other;
|
||||
return this.propName.equals(that.propName) && this.empty == that.empty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
@@ -41,14 +40,12 @@ class BetweenExpression extends AbstractExpression {
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
|
||||
request.append(propName).append(BETWEEN).append(" ? and ? ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(BetweenExpression.class).add(propName);
|
||||
builder.bind(2);
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
builder.append("Between[").append(propName).append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,16 +55,6 @@ class BetweenExpression extends AbstractExpression {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof BetweenExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BetweenExpression that = (BetweenExpression) other;
|
||||
return this.propName.equals(that.propName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
BetweenExpression that = (BetweenExpression) other;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
@@ -85,9 +84,8 @@ class BetweenPropertyExpression extends NonPrepareExpression {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(BetweenPropertyExpression.class).add(lowProperty).add(highProperty);
|
||||
builder.bind(1);
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
builder.append("BetweenProperties[").append("low:").append(lowProperty).append(" high:").append(highProperty).append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,16 +93,6 @@ class BetweenPropertyExpression extends NonPrepareExpression {
|
||||
return val().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof BetweenPropertyExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
|
||||
return lowProperty.equals(that.lowProperty) && highProperty.equals(that.highProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
BetweenPropertyExpression that = (BetweenPropertyExpression) other;
|
||||
|
||||
+2
-14
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
@@ -51,9 +50,8 @@ class CaseInsensitiveEqualExpression extends AbstractValueExpression {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(CaseInsensitiveEqualExpression.class).add(propName);
|
||||
builder.bind(1);
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
builder.append("Ieq[").append(propName).append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,16 +59,6 @@ class CaseInsensitiveEqualExpression extends AbstractValueExpression {
|
||||
return val().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByPlan(SpiExpression other) {
|
||||
if (!(other instanceof CaseInsensitiveEqualExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
|
||||
return this.propName.equals(that.propName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
CaseInsensitiveEqualExpression that = (CaseInsensitiveEqualExpression) other;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user