mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
172
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2a69714ca | ||
|
|
f3213e7b90 | ||
|
|
55de997297 | ||
|
|
65890f9777 | ||
|
|
41c1e7260c | ||
|
|
7d35789db4 | ||
|
|
d266d6d440 | ||
|
|
6d5374e66a | ||
|
|
8410de8831 | ||
|
|
4b0230e862 | ||
|
|
fa96f81f8c | ||
|
|
a5fea5e9be | ||
|
|
bafba0babf | ||
|
|
e4088c5fbe | ||
|
|
0405ceb96c | ||
|
|
ebf96c87bf | ||
|
|
fcaf11f262 | ||
|
|
05df47c0d7 | ||
|
|
a8892292fb | ||
|
|
e876ef8d87 | ||
|
|
315b39813b | ||
|
|
6d5e0c4766 | ||
|
|
a2ce17aec0 | ||
|
|
fcffa92c55 | ||
|
|
7fda8079c2 | ||
|
|
cca9bb4427 | ||
|
|
f45089240a | ||
|
|
8252a51545 | ||
|
|
c440c768cf | ||
|
|
c6ae501354 | ||
|
|
70e2b1c2ae | ||
|
|
211f28eea3 | ||
|
|
174fe46485 | ||
|
|
890cc6f66d | ||
|
|
2f0e5e6e46 | ||
|
|
54ba78218b | ||
|
|
ba16cea405 | ||
|
|
e81a61378c | ||
|
|
b4154031cc | ||
|
|
ecf5b56652 | ||
|
|
426c6c8111 | ||
|
|
9dbb69f4f4 | ||
|
|
83776a1eae | ||
|
|
6bc72ae6bb | ||
|
|
4fe2016891 | ||
|
|
7be4675d3e | ||
|
|
06b71781f5 | ||
|
|
55e4c7bc26 | ||
|
|
b3584a81b4 | ||
|
|
68da8944ed | ||
|
|
903ee35d63 | ||
|
|
658ee3d39f | ||
|
|
c074b7c457 | ||
|
|
528ad7c7fb | ||
|
|
c3f41f4157 | ||
|
|
33d104e6aa | ||
|
|
4bd3a0f293 | ||
|
|
54a4fd75f1 | ||
|
|
105f08cca1 | ||
|
|
01d9dd3fe8 | ||
|
|
f09b38c58c | ||
|
|
cb6d2e7ec0 | ||
|
|
89c0324e42 | ||
|
|
7b4215f4f8 | ||
|
|
e4cb5f0443 | ||
|
|
ec24374d72 | ||
|
|
cfba4d2deb | ||
|
|
0ab019ca2c | ||
|
|
8c9dae0f88 | ||
|
|
88a0c34d37 | ||
|
|
ce403e72a7 | ||
|
|
3c6a52da31 | ||
|
|
2806972032 | ||
|
|
acd6ba0a42 | ||
|
|
b386c9a5a8 | ||
|
|
c5536aaeec | ||
|
|
1b2bfc5df8 | ||
|
|
808004165c | ||
|
|
61eedb4e9a | ||
|
|
662ffdd070 | ||
|
|
13a5511318 | ||
|
|
0f9c98964e | ||
|
|
b87e6d90f6 | ||
|
|
166906a814 | ||
|
|
1733d2382c | ||
|
|
a6f9120905 | ||
|
|
32b599512b | ||
|
|
34fd24d620 | ||
|
|
f928923b5b | ||
|
|
1f928e65a4 | ||
|
|
d920d8eb76 | ||
|
|
23d2c64046 | ||
|
|
19d29cd836 | ||
|
|
f14c3e8cb4 | ||
|
|
b9ca8be95a | ||
|
|
d627bb47e2 | ||
|
|
d395c666fc | ||
|
|
939397acfc | ||
|
|
fecb5fa731 | ||
|
|
bdc9917811 | ||
|
|
afcd6859e0 | ||
|
|
fef87323b3 | ||
|
|
6909b20a9e | ||
|
|
c1a479e1f4 | ||
|
|
e06967ff4a | ||
|
|
156469ae0f | ||
|
|
1d1d479dda | ||
|
|
d185ea5962 | ||
|
|
2dbb5ea80d | ||
|
|
158ee9a081 | ||
|
|
b1410cc660 | ||
|
|
4c2e9ccacd | ||
|
|
29aa395cd4 | ||
|
|
8160d1f01c | ||
|
|
6fd76ff697 | ||
|
|
249cc5df49 | ||
|
|
2646e8c27f | ||
|
|
3eaf47fd70 | ||
|
|
9d64352771 | ||
|
|
aa7325b249 | ||
|
|
2839434773 | ||
|
|
de9823b69b | ||
|
|
15cab24848 | ||
|
|
b87e360ac7 | ||
|
|
82e68d7756 | ||
|
|
0371043914 | ||
|
|
54b5684628 | ||
|
|
da95ec0c05 | ||
|
|
b5eec93995 | ||
|
|
fc26765a7e | ||
|
|
9a6d339449 | ||
|
|
5b09443cdf | ||
|
|
31093babe3 | ||
|
|
f8b9ca2034 | ||
|
|
c27749ae2d | ||
|
|
2c04430185 | ||
|
|
dcde47daf6 | ||
|
|
57056d7abd | ||
|
|
3409f264ec | ||
|
|
6a61614430 | ||
|
|
9734ae5787 | ||
|
|
b817d5ee4d | ||
|
|
0036473971 | ||
|
|
a020bb15a1 | ||
|
|
efa86043bf | ||
|
|
eb1ea5ddd2 | ||
|
|
a83a4fa1c4 | ||
|
|
fe71c7cc4e | ||
|
|
5522d56e8d | ||
|
|
7e995efb62 | ||
|
|
ef46bc86cc | ||
|
|
0e75c50be5 | ||
|
|
cd1cdb027f | ||
|
|
66703a3a29 | ||
|
|
56e65d7463 | ||
|
|
eb7277aa13 | ||
|
|
42f9e74d91 | ||
|
|
be24e11b91 | ||
|
|
42d3e9eb91 | ||
|
|
bb164a0763 | ||
|
|
3a50c264b1 | ||
|
|
0e7dc4209c | ||
|
|
72845acbad | ||
|
|
29d88d7de8 | ||
|
|
c81d329100 | ||
|
|
7c1c144af5 | ||
|
|
7809858634 | ||
|
|
bd3cb3c547 | ||
|
|
db25cb7bfb | ||
|
|
55a9ba7170 | ||
|
|
2df2224286 | ||
|
|
e1e8092144 |
@@ -151,3 +151,18 @@ In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
|
||||
TODO: not yet tested
|
||||
|
||||
|
||||
|
||||
'hana' platform
|
||||
--------------------
|
||||
|
||||
Requires an installed SAP HANA server - e.g. https://store.docker.com/images/sap-hana-express-edition
|
||||
|
||||
- Create a user `EBEAN_TEST` with password `Eb3an_test` in the tenant database `HXE`
|
||||
- Adjust the connection string and/or username in `ebean.properties`
|
||||
|
||||
run: `mvn clean test -Ddatasource.default=hana`
|
||||
|
||||
Current status: PASS
|
||||
Tests run: 2486, Failures: 0, Errors: 0, Skipped: 84
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.22.2</version>
|
||||
<version>11.29.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.22.2</tag>
|
||||
<tag>ebean-11.29.1</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -117,13 +117,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>4.2</version>
|
||||
<version>4.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-datasource</artifactId>
|
||||
<version>4.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -135,7 +135,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.9.1</version>
|
||||
<version>11.12.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -229,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.11.1</version>
|
||||
<version>11.25.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.196</version>
|
||||
<version>1.4.197</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -271,6 +271,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sap.cloud.db.jdbc</groupId>
|
||||
<artifactId>ngdbc</artifactId>
|
||||
<version>2.3.48</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje.composite</groupId>
|
||||
<artifactId>avaje-composite-testing</artifactId>
|
||||
@@ -302,7 +309,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.11.1</version>
|
||||
<version>11.25.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -2,7 +2,9 @@ package io.ebean;
|
||||
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.BeanNotEnhancedException;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.datasource.DataSourceConfigurationException;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
@@ -160,6 +162,15 @@ public final class Ebean {
|
||||
defaultServer = getWithCreate(defaultName.trim());
|
||||
}
|
||||
}
|
||||
} catch (BeanNotEnhancedException e) {
|
||||
throw e;
|
||||
|
||||
} catch (DataSourceConfigurationException e) {
|
||||
String msg = "Configuration error creating DataSource for the default EbeanServer." +
|
||||
" This typically means a missing application-test.yaml or missing ebean-test-config dependency." +
|
||||
" See https://ebean.io/docs/trouble-shooting#datasource";
|
||||
throw new DataSourceConfigurationException(msg, e);
|
||||
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error trying to create the default EbeanServer", e);
|
||||
throw new RuntimeException(e);
|
||||
|
||||
@@ -11,17 +11,13 @@ import io.ebean.text.json.JsonContext;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Provides the API for fetching and saving beans to a particular DataSource.
|
||||
@@ -1446,6 +1442,13 @@ public interface EbeanServer {
|
||||
*/
|
||||
JsonContext json();
|
||||
|
||||
/**
|
||||
* Return a ScriptRunner for running SQL or DDL scripts.
|
||||
* <p/>
|
||||
* Intended to use mostly in testing to run seed SQL scripts or truncate table scripts etc.
|
||||
*/
|
||||
ScriptRunner script();
|
||||
|
||||
/**
|
||||
* Return the Document store.
|
||||
*/
|
||||
|
||||
@@ -133,11 +133,22 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression ieq(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Not Equal To - property not equal to the given value (typically
|
||||
* using a lower() function to make it case insensitive).
|
||||
*/
|
||||
Expression ine(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Equal To that allows for named parameter use.
|
||||
*/
|
||||
Expression ieqObject(String propertyName, Object value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Not Equal To that allows for named parameter use.
|
||||
*/
|
||||
Expression ineObject(String propertyName, Object value);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
|
||||
@@ -166,6 +166,18 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
int delete();
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of rows that were deleted.
|
||||
*/
|
||||
int delete(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a update query.
|
||||
*
|
||||
@@ -174,6 +186,14 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
int update();
|
||||
|
||||
/**
|
||||
* Execute as a update query with the given transaction.
|
||||
*
|
||||
* @return the number of rows that were updated.
|
||||
* @see UpdateQuery
|
||||
*/
|
||||
int update(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query iterating over the results.
|
||||
*
|
||||
@@ -729,6 +749,12 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> ieq(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Case Insensitive Not Equal To - property not equal to the given value (typically
|
||||
* using a lower() function to make it case insensitive).
|
||||
*/
|
||||
ExpressionList<T> ine(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@ import javax.annotation.Nonnull;
|
||||
* that can be defined separately from the query and combined with other FetchGroups.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Select example</h3>*
|
||||
* <h3>Select example</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
|
||||
@@ -78,6 +78,7 @@ public class MergeOptionsBuilder {
|
||||
this.deletePermanent = deletePermanent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> paths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import javax.persistence.MappedSuperclass;
|
||||
|
||||
/**
|
||||
* A MappedSuperclass base class that provides convenience methods for inserting, updating and
|
||||
@@ -74,7 +73,6 @@ import javax.persistence.MappedSuperclass;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class Model {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an Order By for a Query.
|
||||
@@ -74,6 +77,15 @@ public final class OrderBy<T> implements Serializable {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with ascending order to this OrderBy.
|
||||
*/
|
||||
public Query<T> asc(String propertyName, String collation) {
|
||||
|
||||
list.add(new Property(propertyName, true, collation));
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with descending order to this OrderBy.
|
||||
*/
|
||||
@@ -83,6 +95,16 @@ public final class OrderBy<T> implements Serializable {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property with descending order to this OrderBy.
|
||||
*/
|
||||
public Query<T> desc(String propertyName, String collation) {
|
||||
|
||||
list.add(new Property(propertyName, false, collation));
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the property is known to be contained in the order by clause.
|
||||
*/
|
||||
@@ -231,6 +253,8 @@ public final class OrderBy<T> implements Serializable {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
private String collation;
|
||||
|
||||
private String nulls;
|
||||
|
||||
private String highLow;
|
||||
@@ -247,17 +271,32 @@ public final class OrderBy<T> implements Serializable {
|
||||
this.highLow = highLow;
|
||||
}
|
||||
|
||||
public Property(String property, boolean ascending, String collation) {
|
||||
this.property = property;
|
||||
this.ascending = ascending;
|
||||
this.collation = collation;
|
||||
}
|
||||
|
||||
public Property(String property, boolean ascending, String collation, String nulls, String highLow) {
|
||||
this.property = property;
|
||||
this.ascending = ascending;
|
||||
this.collation = collation;
|
||||
this.nulls = nulls;
|
||||
this.highLow = highLow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this Property with the path trimmed.
|
||||
*/
|
||||
public Property copyWithTrim(String path) {
|
||||
return new Property(property.substring(path.length() + 1), ascending, nulls, highLow);
|
||||
return new Property(property.substring(path.length() + 1), ascending, collation, nulls, highLow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = property.hashCode();
|
||||
hc = hc * 92821 + (ascending ? 0 : 1);
|
||||
hc = hc * 92821 + (collation == null ? 0 : collation.hashCode());
|
||||
hc = hc * 92821 + (nulls == null ? 0 : nulls.hashCode());
|
||||
hc = hc * 92821 + (highLow == null ? 0 : highLow.hashCode());
|
||||
return hc;
|
||||
@@ -274,8 +313,9 @@ public final class OrderBy<T> implements Serializable {
|
||||
Property e = (Property) obj;
|
||||
if (ascending != e.ascending) return false;
|
||||
if (!property.equals(e.property)) return false;
|
||||
if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false;
|
||||
return highLow != null ? highLow.equals(e.highLow) : e.highLow == null;
|
||||
if (!Objects.equals(collation, e.collation)) return false;
|
||||
if (!Objects.equals(nulls, e.nulls)) return false;
|
||||
return Objects.equals(highLow, e.highLow);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,7 +324,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
}
|
||||
|
||||
public String toStringFormat() {
|
||||
if (nulls == null) {
|
||||
if (nulls == null && collation == null) {
|
||||
if (ascending) {
|
||||
return property;
|
||||
} else {
|
||||
@@ -292,11 +332,23 @@ public final class OrderBy<T> implements Serializable {
|
||||
}
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(property);
|
||||
if (collation != null) {
|
||||
if (collation.contains("${}")) {
|
||||
// this is a complex collation, e.g. DB2 - we must replace the property
|
||||
sb.append(StringHelper.replaceString(collation, "${}", property));
|
||||
} else {
|
||||
sb.append(property);
|
||||
sb.append(" collate ").append(collation);
|
||||
}
|
||||
} else {
|
||||
sb.append(property);
|
||||
}
|
||||
if (!ascending) {
|
||||
sb.append(" ").append("desc");
|
||||
}
|
||||
sb.append(" ").append(nulls).append(" ").append(highLow);
|
||||
if (nulls != null) {
|
||||
sb.append(" ").append(nulls).append(" ").append(highLow);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -319,7 +371,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
* Return a copy of this property.
|
||||
*/
|
||||
public Property copy() {
|
||||
return new Property(property, ascending, nulls, highLow);
|
||||
return new Property(property, ascending, collation, nulls, highLow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -125,7 +126,7 @@ public class Pairs {
|
||||
* Return all the value pairs.
|
||||
*/
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
return Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -924,11 +924,31 @@ public interface Query<T> {
|
||||
*/
|
||||
int delete();
|
||||
|
||||
/**
|
||||
* Execute as a delete query returning the number of rows deleted using the given transaction.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @return the number of beans/rows that were deleted.
|
||||
*/
|
||||
int delete(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the UpdateQuery returning the number of rows updated.
|
||||
*
|
||||
* @return the number of beans/rows updated.
|
||||
*/
|
||||
int update();
|
||||
|
||||
/**
|
||||
* Execute the UpdateQuery returning the number of rows updated using the given transaction.
|
||||
*
|
||||
* @return the number of beans/rows updated.
|
||||
*/
|
||||
int update(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the count of entities this query should return.
|
||||
* <p>
|
||||
@@ -1485,15 +1505,6 @@ public interface Query<T> {
|
||||
*/
|
||||
String getGeneratedSql();
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to forUpdate().
|
||||
*
|
||||
* executed the select with "for update" which should lock the record
|
||||
* "on read"
|
||||
*/
|
||||
@Deprecated
|
||||
Query<T> setForUpdate(boolean forUpdate);
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Runs DDL and SQL scripts.
|
||||
* <p/>
|
||||
* Typically these are scripts used for testing such as seed SQL scripts or truncate SQL scripts.
|
||||
* <p/>
|
||||
* Scripts are executed in their own transaction and committed on successful completion.
|
||||
*
|
||||
* <h3>Example of simple use</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* EbeanServer server = Ebean.getDefaultServer();
|
||||
* server.script().run("/scripts/test-script.sql");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*
|
||||
* <h3>Example using place holders in the script</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Map<String,String> placeholders = new HashMap<>();
|
||||
* placeholders.put("tableName", "e_basic");
|
||||
*
|
||||
* EbeanServer server = Ebean.getDefaultServer();
|
||||
* server.script().run("/scripts/test-script.sql");
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ScriptRunner {
|
||||
|
||||
/**
|
||||
* Run a script given the resource path (that should start with "/").
|
||||
*/
|
||||
void run(String path);
|
||||
|
||||
/**
|
||||
* Run a script given the resource path (that should start with "/") and place holders.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* Map<String,String> placeholders = new HashMap<>();
|
||||
* placeholders.put("tableName", "e_basic");
|
||||
*
|
||||
* EbeanServer server = Ebean.getDefaultServer();
|
||||
* server.script().run("/scripts/test-script.sql");
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void run(String path, Map<String, String> placeholderMap);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the resource.
|
||||
*/
|
||||
void run(URL resource);
|
||||
|
||||
/**
|
||||
* Run a DDL or SQL script given the resource and place holders.
|
||||
*/
|
||||
void run(URL resource, Map<String, String> placeholderMap);
|
||||
|
||||
}
|
||||
@@ -98,6 +98,11 @@ public interface SqlUpdate {
|
||||
/**
|
||||
* Execute the update returning the number of rows modified.
|
||||
* <p>
|
||||
* Note that if the transaction has batch mode on then this update will use JDBC batch and may not execute until
|
||||
* later - at commit time or a transaction flush. In this case this method returns -1 indicating that the
|
||||
* update has been batched for later execution.
|
||||
* </p>
|
||||
* <p>
|
||||
* After you have executed the SqlUpdate you can bind new variables using
|
||||
* {@link #setParameter(String, Object)} etc and then execute the SqlUpdate
|
||||
* again.
|
||||
@@ -112,6 +117,11 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Execute the statement now regardless of the JDBC batch mode of the transaction.
|
||||
*/
|
||||
int executeNow();
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
|
||||
@@ -84,7 +84,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* This is similar to commit() but leaves the transaction "Active".
|
||||
* </p>
|
||||
* <h3>Functions/h3>
|
||||
* <h3>Functions</h3>
|
||||
* <ul>
|
||||
* <li>Flush the JDBC batch buffer</li>
|
||||
* <li>Call commit on the underlying JDBC connection</li>
|
||||
@@ -100,7 +100,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* This performs commit and completes the transaction closing underlying resources and
|
||||
* marking the transaction as "In active".
|
||||
* </p>
|
||||
* <h3>Functions/h3>
|
||||
* <h3>Functions</h3>
|
||||
* <ul>
|
||||
* <li>Flush the JDBC batch buffer</li>
|
||||
* <li>Call commit on the underlying JDBC connection</li>
|
||||
@@ -117,7 +117,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* This performs rollback, closes underlying resources and marks the transaction as "In active".
|
||||
* </p>
|
||||
* <h3>Functions/h3>
|
||||
* <h3>Functions</h3>
|
||||
* <ul>
|
||||
* <li>Call rollback on the underlying JDBC connection</li>
|
||||
* <li>Trigger any registered TransactionCallbacks</li>
|
||||
@@ -358,29 +358,6 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void setBatchMode(boolean useBatch);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #setBatchMode(boolean)}.
|
||||
* <p>
|
||||
* Set the JDBC batch mode to use for this transaction.
|
||||
* </p>
|
||||
* <p>
|
||||
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
|
||||
* and this would be useful if the request cascades to detail beans.
|
||||
* </p>
|
||||
*
|
||||
* @param persistBatchMode the batch mode to use for this transaction
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
|
||||
*/
|
||||
@Deprecated
|
||||
void setBatch(PersistBatch persistBatchMode);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #isBatchMode()}.
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
@Deprecated
|
||||
PersistBatch getBatch();
|
||||
|
||||
/**
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
@@ -405,22 +382,6 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void setBatchOnCascade(boolean batchMode);
|
||||
|
||||
/**
|
||||
* Set the batch mode when cascading.
|
||||
* <p>
|
||||
* Deprecated in favour of {@link #setBatchOnCascade(boolean)}
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #isBatchMode()}.
|
||||
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
|
||||
*/
|
||||
@Deprecated
|
||||
PersistBatch getBatchOnCascade();
|
||||
|
||||
/**
|
||||
* Return the batch mode at the request level.
|
||||
*/
|
||||
|
||||
@@ -432,4 +432,11 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isBatchMode() {
|
||||
return PersistBatch.ALL.equals(batch);
|
||||
}
|
||||
|
||||
public boolean isBatchOnCascade() {
|
||||
return PersistBatch.ALL.equals(batchOnCascade);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,21 +71,30 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
/**
|
||||
* Used when a bean is partially filled.
|
||||
*/
|
||||
private final boolean[] loadedProps;
|
||||
|
||||
private boolean fullyLoadedBean;
|
||||
private static final byte FLAG_LOADED_PROP = 1;
|
||||
|
||||
/**
|
||||
* Set of changed properties.
|
||||
*/
|
||||
private boolean[] changedProps;
|
||||
private static final byte FLAG_CHANGED_PROP = 2;
|
||||
|
||||
/**
|
||||
* Flags indicating if a property is a dirty embedded bean. Used to distingush
|
||||
* between an embedded bean being completely overwritten and one of its
|
||||
* embedded properties being made dirty.
|
||||
*/
|
||||
private boolean[] embeddedDirty;
|
||||
private static final byte FLAG_EMBEDDED_DIRTY = 4;
|
||||
|
||||
/**
|
||||
* Flags indicating if a property is a dirty embedded bean. Used to distingush
|
||||
* between an embedded bean being completely overwritten and one of its
|
||||
* embedded properties being made dirty.
|
||||
*/
|
||||
private static final byte FLAG_ORIG_VALUE_SET = 8;
|
||||
|
||||
private final byte[] flags;
|
||||
|
||||
private boolean fullyLoadedBean;
|
||||
|
||||
private Object[] origValues;
|
||||
|
||||
@@ -102,7 +111,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
public EntityBeanIntercept(Object ownerBean) {
|
||||
this.owner = (EntityBean) ownerBean;
|
||||
this.loadedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
this.flags = new byte[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,8 +222,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Check each property to see if the bean is partially loaded.
|
||||
*/
|
||||
public boolean isPartial() {
|
||||
for (boolean loadedProp : loadedProps) {
|
||||
if (!loadedProp) {
|
||||
for (byte flag : flags) {
|
||||
if ((flag & FLAG_LOADED_PROP) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -259,10 +268,10 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Return true if only the Id property has been loaded.
|
||||
*/
|
||||
public boolean hasIdOnly(int idIndex) {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if (i == idIndex) {
|
||||
if (!loadedProps[i]) return false;
|
||||
} else if (loadedProps[i]) {
|
||||
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
|
||||
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -284,9 +293,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
if (idPos > -1) {
|
||||
// For cases where properties are set on constructor
|
||||
// set every non Id property to unloaded (for lazy loading)
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if (i != idPos) {
|
||||
loadedProps[i] = false;
|
||||
flags[i] &= ~FLAG_LOADED_PROP;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +361,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.owner._ebean_setEmbeddedLoaded();
|
||||
this.lazyLoadProperty = -1;
|
||||
this.origValues = null;
|
||||
this.changedProps = null;
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
|
||||
}
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
@@ -475,7 +486,11 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
if (position == -1) {
|
||||
throw new IllegalArgumentException("Property " + propertyName + " not found");
|
||||
}
|
||||
loadedProps[position] = loaded;
|
||||
if (loaded) {
|
||||
flags[position] |= FLAG_LOADED_PROP;
|
||||
} else {
|
||||
flags[position] &= ~FLAG_LOADED_PROP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -483,22 +498,22 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* constructor.
|
||||
*/
|
||||
public void setPropertyUnloaded(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = false;
|
||||
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property to be loaded.
|
||||
*/
|
||||
public void setLoadedProperty(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all properties to be loaded (post insert).
|
||||
*/
|
||||
public void setLoadedPropertyAll() {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
loadedProps[i] = true;
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
flags[i] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,14 +521,14 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Return true if the property is loaded.
|
||||
*/
|
||||
public boolean isLoadedProperty(int propertyIndex) {
|
||||
return loadedProps[propertyIndex];
|
||||
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is considered changed.
|
||||
*/
|
||||
public boolean isChangedProperty(int propertyIndex) {
|
||||
return (changedProps != null && changedProps[propertyIndex]);
|
||||
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,8 +536,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* embedded properties is dirty.
|
||||
*/
|
||||
public boolean isDirtyProperty(int propertyIndex) {
|
||||
return (changedProps != null && changedProps[propertyIndex]
|
||||
|| embeddedDirty != null && embeddedDirty[propertyIndex]);
|
||||
return (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,27 +548,22 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
|
||||
public void setChangedProperty(int propertyIndex) {
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
changedProps[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_CHANGED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that an embedded bean has had one of its properties changed.
|
||||
*/
|
||||
private void setEmbeddedPropertyDirty(int propertyIndex) {
|
||||
if (embeddedDirty == null) {
|
||||
embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
embeddedDirty[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
|
||||
}
|
||||
|
||||
private void setOriginalValue(int propertyIndex, Object value) {
|
||||
if (origValues == null) {
|
||||
origValues = new Object[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
if (origValues[propertyIndex] == null) {
|
||||
if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
|
||||
flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
|
||||
origValues[propertyIndex] = value;
|
||||
}
|
||||
}
|
||||
@@ -574,13 +583,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
public void setNewBeanForUpdate() {
|
||||
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
if (loadedProps[i]) {
|
||||
changedProps[i] = true;
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
||||
flags[i] |= FLAG_CHANGED_PROP;
|
||||
}
|
||||
}
|
||||
setDirty(true);
|
||||
@@ -594,8 +599,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return null;
|
||||
}
|
||||
Set<String> props = new LinkedHashSet<>();
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
if (loadedProps[i]) {
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
|
||||
props.add(getProperty(i));
|
||||
}
|
||||
}
|
||||
@@ -609,12 +614,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
int len = getPropertyLength();
|
||||
boolean[] dirties = new boolean[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
dirties[i] = true;
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
// an embedded property has been changed - recurse
|
||||
dirties[i] = true;
|
||||
}
|
||||
// this, or an embedded property has been changed - recurse
|
||||
dirties[i] = (flags[i] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
|
||||
}
|
||||
return dirties;
|
||||
}
|
||||
@@ -634,11 +635,11 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void addDirtyPropertyNames(Set<String> props, String prefix) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
|
||||
props.add(propName);
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i) + ".");
|
||||
@@ -654,12 +655,12 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
String[] names = owner._ebean_getPropertyNames();
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
if (propertyNames.contains(names[i])) {
|
||||
return true;
|
||||
}
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
if (propertyNames.contains(names[i])) {
|
||||
return true;
|
||||
}
|
||||
@@ -683,15 +684,16 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void addDirtyPropertyValues(Map<String, ValuePair> dirtyValues, String prefix) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
|
||||
Object newVal = owner._ebean_getField(i);
|
||||
Object oldVal = getOrigValue(i);
|
||||
if (!areEqual(oldVal, newVal)) {
|
||||
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
|
||||
}
|
||||
|
||||
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
|
||||
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
|
||||
@@ -705,13 +707,15 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
// the property has been changed on this bean
|
||||
Object newVal = owner._ebean_getField(i);
|
||||
Object oldVal = getOrigValue(i);
|
||||
visitor.visit(i, newVal, oldVal);
|
||||
if (!areEqual(oldVal, newVal)) {
|
||||
visitor.visit(i, newVal, oldVal);
|
||||
}
|
||||
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
visitor.visitPush(i);
|
||||
@@ -739,9 +743,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
|
||||
sb.append(i).append(',');
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
sb.append(i).append('[');
|
||||
@@ -765,15 +769,12 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of property names for changed properties.
|
||||
*/
|
||||
public boolean[] getChanged() {
|
||||
return changedProps;
|
||||
}
|
||||
|
||||
public boolean[] getLoaded() {
|
||||
return loadedProps;
|
||||
boolean[] ret= new boolean[flags.length];
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -821,14 +822,14 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
|
||||
|
||||
if (loadedProps == null || loadedProps[loadProperty]) {
|
||||
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
|
||||
// race condition where multiple threads calling preGetter concurrently
|
||||
return;
|
||||
}
|
||||
|
||||
if (lazyLoadFailure) {
|
||||
// failed when batch lazy loaded by another bean in the batch
|
||||
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
|
||||
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
|
||||
}
|
||||
|
||||
if (lazyLoadProperty == -1) {
|
||||
@@ -887,7 +888,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Called when a BeanCollection is initialised automatically.
|
||||
*/
|
||||
public void initialisedMany(int propertyIndex) {
|
||||
loadedProps[propertyIndex] = true;
|
||||
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
private void preGetterCallback(int propertyIndex) {
|
||||
|
||||
@@ -123,7 +123,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
private void initClear() {
|
||||
synchronized (this) {
|
||||
if (map == null) {
|
||||
if (modifyListening) {
|
||||
if (!disableLazyLoad && modifyListening) {
|
||||
lazyLoadCollection(true);
|
||||
} else {
|
||||
map = new LinkedHashMap<>();
|
||||
@@ -135,7 +135,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
private void init() {
|
||||
synchronized (this) {
|
||||
if (map == null) {
|
||||
lazyLoadCollection(false);
|
||||
if (disableLazyLoad) {
|
||||
map = new LinkedHashMap<>();
|
||||
} else {
|
||||
lazyLoadCollection(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,14 +277,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
checkReadOnly();
|
||||
init();
|
||||
if (modifyListening) {
|
||||
Object oldBean = map.put(key, value);
|
||||
E oldBean = map.put(key, value);
|
||||
if (value != oldBean) {
|
||||
// register the add of the new and the removal of the old
|
||||
modifyAddition(value);
|
||||
modifyRemoval(oldBean);
|
||||
}
|
||||
return oldBean;
|
||||
} else {
|
||||
return map.put(key, value);
|
||||
}
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,8 +301,9 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
modifyRemoval(oldBean);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
map.putAll(puts);
|
||||
}
|
||||
map.putAll(puts);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -116,7 +116,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
private void initClear() {
|
||||
synchronized (this) {
|
||||
if (set == null) {
|
||||
if (modifyListening) {
|
||||
if (!disableLazyLoad && modifyListening) {
|
||||
lazyLoadCollection(true);
|
||||
} else {
|
||||
set = new LinkedHashSet<>();
|
||||
@@ -128,7 +128,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
private void init() {
|
||||
synchronized (this) {
|
||||
if (set == null) {
|
||||
lazyLoadCollection(true);
|
||||
if (disableLazyLoad) {
|
||||
set = new LinkedHashSet<>();
|
||||
} else {
|
||||
lazyLoadCollection(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +381,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
throw new IllegalStateException("This collection is in ReadOnly mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Throw when an processing an entity bean that is not bytecode enhanced.
|
||||
*
|
||||
* Refer: https://ebean.io/docs/trouble-shooting#not-enhanced
|
||||
*/
|
||||
public class BeanNotEnhancedException extends IllegalStateException {
|
||||
|
||||
private static final long serialVersionUID = 3008101919425876799L;
|
||||
|
||||
public BeanNotEnhancedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Throw when an processing thinks a bean is not registered.
|
||||
*
|
||||
* Refer: https://ebean.io/docs/trouble-shooting#not-registered
|
||||
*/
|
||||
public class BeanNotRegisteredException extends IllegalStateException {
|
||||
|
||||
private static final long serialVersionUID = -1411298126011136552L;
|
||||
|
||||
public BeanNotRegisteredException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import io.ebean.annotation.Platform;
|
||||
import io.ebean.migration.MigrationConfig;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -28,39 +27,13 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected Platform platform;
|
||||
|
||||
/**
|
||||
* Set to true if the DB migration should be generated on server start.
|
||||
*/
|
||||
protected boolean generate;
|
||||
|
||||
/**
|
||||
* The migration version name (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
protected String version;
|
||||
|
||||
/**
|
||||
* Description text that can be appended to the version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
*/
|
||||
protected String name;
|
||||
|
||||
/**
|
||||
* Resource path for the migration xml and sql.
|
||||
*/
|
||||
protected String migrationPath = "dbmigration";
|
||||
|
||||
protected String migrationInitPath = "dbinit";
|
||||
|
||||
/**
|
||||
* Subdirectory the model xml files go into.
|
||||
*/
|
||||
@@ -75,13 +48,6 @@ public class DbMigrationConfig {
|
||||
|
||||
protected String modelSuffix = ".model.xml";
|
||||
|
||||
protected boolean includeGeneratedFileComment;
|
||||
|
||||
/**
|
||||
* The version of a pending drop that should be generated as the next migration.
|
||||
*/
|
||||
protected String generatePendingDrop;
|
||||
|
||||
/**
|
||||
* For running migration the DB table that holds migration execution status.
|
||||
*/
|
||||
@@ -153,6 +119,15 @@ public class DbMigrationConfig {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path for normal migrations or dbinit migrations.
|
||||
*
|
||||
* @param dbinitMigration When true return the path for dbinit migrations.
|
||||
*/
|
||||
public String getMigrationPath(boolean dbinitMigration) {
|
||||
return dbinitMigration ? migrationInitPath : migrationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource path for db migrations.
|
||||
*/
|
||||
@@ -228,52 +203,6 @@ public class DbMigrationConfig {
|
||||
this.applyPrefix = applyPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the generated file comment should be included.
|
||||
*/
|
||||
public boolean isIncludeGeneratedFileComment() {
|
||||
return includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the generated file comment should be included.
|
||||
*/
|
||||
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
|
||||
this.includeGeneratedFileComment = includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration version (or "next") to generate pending drops for.
|
||||
*/
|
||||
public String getGeneratePendingDrop() {
|
||||
return generatePendingDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration version (or "next") to generate pending drops for.
|
||||
*/
|
||||
public void setGeneratePendingDrop(String generatePendingDrop) {
|
||||
this.generatePendingDrop = generatePendingDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration version.
|
||||
* <p>
|
||||
* Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
|
||||
*/
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the migration name.
|
||||
* <p>
|
||||
* Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table name that holds the migration run details
|
||||
* (used by DB Migration runner only).
|
||||
@@ -500,18 +429,13 @@ public class DbMigrationConfig {
|
||||
public void loadSettings(PropertiesWrapper properties, String serverName) {
|
||||
|
||||
migrationPath = properties.get("migration.migrationPath", migrationPath);
|
||||
migrationInitPath = properties.get("migration.migrationInitPath", migrationInitPath);
|
||||
modelPath = properties.get("migration.modelPath", modelPath);
|
||||
applyPrefix = properties.get("migration.applyPrefix", applyPrefix);
|
||||
applySuffix = properties.get("migration.applySuffix", applySuffix);
|
||||
modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
|
||||
includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
|
||||
generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop);
|
||||
|
||||
platform = properties.getEnum(Platform.class, "migration.platform", platform);
|
||||
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
|
||||
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
|
||||
|
||||
@@ -532,44 +456,6 @@ public class DbMigrationConfig {
|
||||
ddlHeader = properties.get("ddl.header", ddlHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration version (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
public String getVersion() {
|
||||
String envVersion = readEnvironment("ddl.migration.version");
|
||||
if (!isEmpty(envVersion)) {
|
||||
return envVersion.trim();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration name which is short description text that can be appended to
|
||||
* the migration version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* </p>
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
* </p>
|
||||
*/
|
||||
public String getName() {
|
||||
String envName = readEnvironment("ddl.migration.name");
|
||||
if (!isEmpty(envName)) {
|
||||
return envName.trim();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system or environment property.
|
||||
*/
|
||||
@@ -597,6 +483,7 @@ public class DbMigrationConfig {
|
||||
runnerConfig.setMetaTable(metaTable);
|
||||
runnerConfig.setApplySuffix(applySuffix);
|
||||
runnerConfig.setMigrationPath(migrationPath);
|
||||
runnerConfig.setMigrationInitPath(migrationInitPath);
|
||||
runnerConfig.setRunPlaceholderMap(runPlaceholderMap);
|
||||
runnerConfig.setRunPlaceholders(runPlaceholders);
|
||||
runnerConfig.setDbUsername(getDbUsername());
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.properties.PropertiesLoader;
|
||||
import io.ebean.datasource.DataSourceConfig;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
@@ -30,7 +31,6 @@ import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.time.Clock;
|
||||
@@ -229,6 +229,8 @@ public class ServerConfig {
|
||||
|
||||
private int persistBatchSize = 20;
|
||||
|
||||
private boolean disableLazyLoading;
|
||||
|
||||
/**
|
||||
* The default batch size for lazy loading
|
||||
*/
|
||||
@@ -927,6 +929,22 @@ public class ServerConfig {
|
||||
this.queryBatchSize = queryBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if lazy loading is disabled on queries by default.
|
||||
*/
|
||||
public boolean isDisableLazyLoading() {
|
||||
return disableLazyLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to disable lazy loading by default.
|
||||
* <p>
|
||||
* It can be turned on per query via {@link Query#setDisableLazyLoading(boolean)}.
|
||||
*/
|
||||
public void setDisableLazyLoading(boolean disableLazyLoading) {
|
||||
this.disableLazyLoading = disableLazyLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default batch size for lazy loading of beans and collections.
|
||||
*/
|
||||
@@ -1965,6 +1983,15 @@ public class ServerConfig {
|
||||
* Return the UUID state file.
|
||||
*/
|
||||
public String getUuidStateFile() {
|
||||
if (uuidStateFile == null || uuidStateFile.isEmpty()) {
|
||||
// by default, add servername...
|
||||
uuidStateFile = name + "-uuid.state";
|
||||
// and store it in the user's home directory
|
||||
String homeDir = System.getProperty("user.home");
|
||||
if (homeDir != null && homeDir.isEmpty()) {
|
||||
uuidStateFile = homeDir + "/.ebean/" + uuidStateFile;
|
||||
}
|
||||
}
|
||||
return uuidStateFile;
|
||||
}
|
||||
|
||||
@@ -2736,20 +2763,6 @@ public class ServerConfig {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated - this does nothing now, we always try to read test configuration.
|
||||
* <p>
|
||||
* Load settings from test-ebean.properties and do nothing if the properties is not found.
|
||||
* <p>
|
||||
* This is typically used when test-ebean.properties is put into the test class path and used
|
||||
* to configure Ebean for running tests.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
public void loadTestProperties() {
|
||||
// do nothing now ... as we always try to read test configuration and that should only
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
|
||||
*/
|
||||
@@ -2887,6 +2900,7 @@ public class ServerConfig {
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
|
||||
|
||||
disableLazyLoading = p.getBoolean("disableLazyLoading", disableLazyLoading);
|
||||
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
|
||||
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
|
||||
|
||||
|
||||
@@ -64,8 +64,6 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
protected String concatOperator = "||";
|
||||
|
||||
/**
|
||||
* When set to true all db column names and table names use quoted identifiers.
|
||||
*/
|
||||
@@ -178,9 +176,17 @@ public class DatabasePlatform {
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
|
||||
/**
|
||||
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
|
||||
*
|
||||
* This is {@code false} for HANA
|
||||
*/
|
||||
protected boolean supportsResultSetConcurrencyModeUpdatable = true;
|
||||
|
||||
|
||||
/**
|
||||
* By default we use JDBC batch when cascading (except for SQL Server).
|
||||
* By default we use JDBC batch when cascading (except for SQL Server and HANA).
|
||||
*/
|
||||
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
|
||||
|
||||
@@ -455,13 +461,6 @@ public class DatabasePlatform {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB concat operator.
|
||||
*/
|
||||
public String getConcatOperator() {
|
||||
return concatOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*/
|
||||
@@ -525,6 +524,24 @@ public class DatabasePlatform {
|
||||
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet CONCUR_UPDATABLE Hint should be used on
|
||||
* createNativeSqlTree() PreparedStatements.
|
||||
* <p>
|
||||
* This specifically is required for Hana which doesn't support CONCUR_UPDATABLE
|
||||
* </p>
|
||||
*/
|
||||
public boolean isSupportsResultSetConcurrencyModeUpdatable() {
|
||||
return supportsResultSetConcurrencyModeUpdatable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the ResultSet CONCUR_UPDATABLE Hint should be used by default on createNativeSqlTree() PreparedStatements.
|
||||
*/
|
||||
public void setSupportsResultSetConcurrencyModeUpdatable(boolean supportsResultSetConcurrencyModeUpdatable) {
|
||||
this.supportsResultSetConcurrencyModeUpdatable = supportsResultSetConcurrencyModeUpdatable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally not needed - overridden in CockroachPlatform.
|
||||
|
||||
@@ -8,7 +8,9 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class DbIdentity {
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
|
||||
private static final String TABLE_PLACEHOLDER = "{table}";
|
||||
|
||||
private static final Pattern TABLE_REPLACE = Pattern.compile(TABLE_PLACEHOLDER, Pattern.LITERAL);
|
||||
|
||||
/**
|
||||
* Set if this DB supports sequences. Note some DB's support both Sequences
|
||||
@@ -53,7 +55,9 @@ public class DbIdentity {
|
||||
if (selectLastInsertedIdTemplate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selectLastInsertedIdTemplate.contains(TABLE_PLACEHOLDER)) {
|
||||
return selectLastInsertedIdTemplate;
|
||||
}
|
||||
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.config.dbplatform.BasicSqlLimiter;
|
||||
|
||||
public class HanaBasicSqlLimiter implements BasicSqlLimiter {
|
||||
@Override
|
||||
public String limit(String dbSql, int firstRow, int maxRows) {
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
|
||||
sb.append(dbSql);
|
||||
|
||||
if (maxRows > 0) {
|
||||
sb.append(" ").append("limit");
|
||||
sb.append(" ").append(maxRows);
|
||||
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append("offset").append(" ");
|
||||
sb.append(firstRow);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.config.dbplatform.DbStandardHistorySupport;
|
||||
|
||||
public class HanaHistorySupport extends DbStandardHistorySupport {
|
||||
|
||||
@Override
|
||||
public String getAsOfViewSuffix(String asOfViewSuffix) {
|
||||
return " for system_time as of ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
|
||||
return " for system_time between ? and ?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_start";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_end";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.Query.ForUpdate;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
public class HanaPlatform extends DatabasePlatform {
|
||||
|
||||
public HanaPlatform() {
|
||||
this.platform = Platform.HANA;
|
||||
this.sqlLimiter = new HanaSqlLimiter();
|
||||
this.persistBatchOnCascade = PersistBatch.NONE;
|
||||
this.supportsResultSetConcurrencyModeUpdatable = false;
|
||||
this.columnAliasPrefix = null;
|
||||
|
||||
this.historySupport = new HanaHistorySupport();
|
||||
this.basicSqlLimiter = new HanaBasicSqlLimiter();
|
||||
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.maxConstraintNameLength = 127;
|
||||
this.maxTableNameLength = 127;
|
||||
|
||||
this.dbDefaultValue.setNow("current_timestamp");
|
||||
|
||||
this.exceptionTranslator = new SqlErrorCodes().addAcquireLock("131", "133", "146")
|
||||
.addDataIntegrity("130", "429", "461", "462").addDuplicateKey("144", "301", "349").build();
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSelectLastInsertedIdTemplate("select current_identity_value() from sys.dummy");
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
|
||||
this.dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
|
||||
this.dbTypeMap.put(DbType.BINARY, new DbPlatformType("varbinary", 255));
|
||||
this.dbTypeMap.put(DbType.BIT, new DbPlatformType("smallint", false));
|
||||
this.dbTypeMap.put(DbType.BLOB, new DbPlatformType("blob", false));
|
||||
this.dbTypeMap.put(DbType.CHAR, new DbPlatformType("nvarchar", 255));
|
||||
this.dbTypeMap.put(DbType.CLOB, new DbPlatformType("nclob", false));
|
||||
this.dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
|
||||
this.dbTypeMap.put(DbType.JSONVARCHAR, new DbPlatformType("nvarchar", 255));
|
||||
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("blob", false));
|
||||
this.dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nclob", false));
|
||||
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point"));
|
||||
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry"));
|
||||
this.dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("smallint", false));
|
||||
this.dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint", false));
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
|
||||
this.dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
|
||||
this.dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addGeoTypes(int srid) {
|
||||
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point(" + srid + ")", false));
|
||||
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql, ForUpdate forUpdateMode) {
|
||||
switch (forUpdateMode) {
|
||||
case BASE:
|
||||
return sql + " for update";
|
||||
case NOWAIT:
|
||||
return sql + " for update nowait";
|
||||
case SKIPLOCKED:
|
||||
return sql + " for update ignore locked";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown update mode: " + forUpdateMode.name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
|
||||
super.configure(config, allQuotedIdentifiers);
|
||||
if (config.getDbUuid().useBinary()) {
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varbinary", 16));
|
||||
} else {
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
|
||||
import io.ebean.config.dbplatform.SqlLimitRequest;
|
||||
import io.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import io.ebean.config.dbplatform.SqlLimiter;
|
||||
|
||||
public class HanaSqlLimiter implements SqlLimiter {
|
||||
@Override
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
sb.append("select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
|
||||
sb.append(dbSql);
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
int maxRows = request.getMaxRows();
|
||||
|
||||
if (maxRows > 0) {
|
||||
sb.append(" ").append("limit ").append(maxRows);
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append("offset ");
|
||||
sb.append(firstRow);
|
||||
}
|
||||
}
|
||||
|
||||
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
|
||||
|
||||
return new SqlLimitResponse(sql, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* SAP HANA specific support.
|
||||
*/
|
||||
package io.ebean.config.dbplatform.hana;
|
||||
@@ -136,8 +136,9 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
/**
|
||||
* Return SQL using built in partition helper functions to create some initial partitions.
|
||||
*
|
||||
* Only use this if extra-dll doesn't have some initial partitions defined (which it should).
|
||||
* Only use this if extra-ddl doesn't have some initial partitions defined (which it should).
|
||||
*/
|
||||
@Override
|
||||
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String pkey) {
|
||||
if (property == null) {
|
||||
property = "";
|
||||
|
||||
@@ -63,6 +63,7 @@ class Loader {
|
||||
*/
|
||||
private void loadTest() {
|
||||
loadProperties("application-test.properties", Source.RESOURCE);
|
||||
loadYaml("application-test.yaml", Source.RESOURCE);
|
||||
loadYaml("application-test.yml", Source.RESOURCE);
|
||||
loadProperties("test-ebean.properties", Source.RESOURCE);
|
||||
}
|
||||
@@ -83,6 +84,7 @@ class Loader {
|
||||
* Load the main configuration for the given source.
|
||||
*/
|
||||
private void loadMain(Source source) {
|
||||
loadYaml("application.yaml", source);
|
||||
loadYaml("application.yml", source);
|
||||
loadProperties("application.properties", source);
|
||||
loadProperties("ebean.properties", source);
|
||||
@@ -99,7 +101,7 @@ class Loader {
|
||||
}
|
||||
|
||||
void loadWithExtensionCheck(String fileName) {
|
||||
if (fileName.endsWith("yml")) {
|
||||
if (fileName.endsWith("yaml") || fileName.endsWith("yml")) {
|
||||
loadYaml(fileName, Source.FILE);
|
||||
} else if (fileName.endsWith("properties")) {
|
||||
loadProperties(fileName, Source.FILE);
|
||||
|
||||
@@ -18,14 +18,14 @@ public class PropertiesLoader {
|
||||
* <p>Firstly loads from main resources</p>
|
||||
* <pre>
|
||||
* - application.properties
|
||||
* - application.yml
|
||||
* - application.yaml
|
||||
* - ebean.properties
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>Then loads from local files</p>
|
||||
* <pre>
|
||||
* - application.properties
|
||||
* - application.yml
|
||||
* - application.yaml
|
||||
* - ebean.properties
|
||||
* </pre>
|
||||
* <p>
|
||||
@@ -41,7 +41,7 @@ public class PropertiesLoader {
|
||||
* <p>Loads from test resources</p>
|
||||
* <pre>
|
||||
* - application-test.properties
|
||||
* - application-test.yml
|
||||
* - application-test.yaml
|
||||
* - test-ebean.properties
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
@@ -148,7 +149,12 @@ public interface DbMigration {
|
||||
void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix);
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
* Return the list of versions that contain pending drops.
|
||||
*/
|
||||
List<String> getPendingDrops();
|
||||
|
||||
/**
|
||||
* Generate the next migration sql script and associated model xml.
|
||||
* <p>
|
||||
* This does not run the migration or ddl scripts but just generates them.
|
||||
* </p>
|
||||
@@ -180,4 +186,15 @@ public interface DbMigration {
|
||||
* @return the version of the generated migration or null
|
||||
*/
|
||||
String generateMigration() throws IOException;
|
||||
|
||||
/**
|
||||
* Generate an "init" migration which has all changes.
|
||||
* <p>
|
||||
* An "init" migration can only be executed and used on a database that has had no
|
||||
* prior migrations run on it.
|
||||
* </p>
|
||||
* @return the version of the generated migration
|
||||
*/
|
||||
String generateInitMigration() throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Information and methods on BeanDescriptors made available to plugins.
|
||||
@@ -18,6 +22,7 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Return the short name of the bean type.
|
||||
*/
|
||||
@Nonnull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
@@ -28,11 +33,13 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Return the full name of the bean type.
|
||||
*/
|
||||
@Nonnull
|
||||
String getFullName();
|
||||
|
||||
/**
|
||||
* Return the class type this BeanDescriptor describes.
|
||||
*/
|
||||
@Nonnull
|
||||
Class<T> getBeanType();
|
||||
|
||||
/**
|
||||
@@ -43,6 +50,7 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Return all the properties for this bean type.
|
||||
*/
|
||||
@Nonnull
|
||||
Collection<? extends Property> allProperties();
|
||||
|
||||
/**
|
||||
@@ -197,6 +205,28 @@ public interface BeanType<T> {
|
||||
*/
|
||||
boolean hasInheritance();
|
||||
|
||||
/**
|
||||
* Return true if this object is the root level object in its entity
|
||||
* inheritance.
|
||||
*/
|
||||
boolean isInheritanceRoot();
|
||||
|
||||
/**
|
||||
* Returns all direct children of this beantype
|
||||
*/
|
||||
List<BeanType<?>> getInheritanceChildren();
|
||||
|
||||
/**
|
||||
* Returns the parent in inheritance hiearchy
|
||||
*/
|
||||
BeanType<?> getInheritanceParent();
|
||||
|
||||
/**
|
||||
* Visit all children recursively
|
||||
* @param visitor
|
||||
*/
|
||||
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
|
||||
|
||||
/**
|
||||
* Return the discriminator column.
|
||||
*/
|
||||
|
||||
@@ -73,4 +73,14 @@ public interface ExpressionPath {
|
||||
* Return the Id values for the given bean value.
|
||||
*/
|
||||
Object[] getAssocIdValues(EntityBean bean);
|
||||
|
||||
/**
|
||||
* Return the underlying bean property.
|
||||
*/
|
||||
public Property getProperty();
|
||||
|
||||
/**
|
||||
* The ElPrefix plus name.
|
||||
*/
|
||||
String getElName();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebean.plugin;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Property of a entity bean that can be read.
|
||||
*/
|
||||
@@ -8,8 +10,15 @@ public interface Property {
|
||||
/**
|
||||
* Return the name of the property.
|
||||
*/
|
||||
@Nonnull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the type of the property.
|
||||
*/
|
||||
@Nonnull
|
||||
Class<?> getPropertyType();
|
||||
|
||||
/**
|
||||
* Return the value of the property on the given bean.
|
||||
*/
|
||||
|
||||
@@ -26,9 +26,9 @@ public class CamelCaseHelper {
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
lastUpper = i;
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
|
||||
@@ -168,13 +168,8 @@ public class LoadBeanRequest extends LoadRequest {
|
||||
// necessary but allow processing to continue until it is accessed by client code
|
||||
Object id = desc.getId(ebi.getOwner());
|
||||
if (!loadedIds.contains(id)) {
|
||||
if (desc.isSoftDelete()) {
|
||||
// assume this is logically deleted (hence not found)
|
||||
desc.setSoftDeleteValue(ebi.getOwner());
|
||||
} else {
|
||||
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
|
||||
ebi.setLazyLoadFailure(id);
|
||||
}
|
||||
// assume this is logically deleted (hence not found)
|
||||
desc.markAsDeleted(ebi.getOwner());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class NaturalKeyQueryData<T> {
|
||||
private List<Pairs.Entry> inPairs;
|
||||
|
||||
// IN clause - only one allowed
|
||||
private Collection<?> inValues;
|
||||
private List<Object> inValues;
|
||||
private String inProperty;
|
||||
|
||||
// normal EQ expressions
|
||||
@@ -52,36 +52,36 @@ public class NaturalKeyQueryData<T> {
|
||||
/**
|
||||
* Match for In Pairs expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchInPairs(Pairs pairs) {
|
||||
public List<Pairs.Entry> matchInPairs(String property0, String property1, List<Pairs.Entry> inPairs) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
if (matchProperty(pairs.getProperty0()) && matchProperty(pairs.getProperty1())) {
|
||||
if (matchProperty(property0) && matchProperty(property1)) {
|
||||
this.hasIn = true;
|
||||
this.inProperty0 = pairs.getProperty0();
|
||||
this.inProperty1 = pairs.getProperty1();
|
||||
this.inPairs = pairs.getEntries();
|
||||
return true;
|
||||
this.inProperty0 = property0;
|
||||
this.inProperty1 = property1;
|
||||
this.inPairs = new ArrayList<>(inPairs); // will be modified
|
||||
return this.inPairs;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for IN expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchIn(String propName, Collection<?> sourceValues) {
|
||||
public List<Object> matchIn(String propName, List<Object> sourceValues) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
if (matchProperty(propName)) {
|
||||
this.hasIn = true;
|
||||
this.inProperty = propName;
|
||||
this.inValues = sourceValues;
|
||||
return true;
|
||||
this.inValues = new ArrayList<>(sourceValues);
|
||||
return this.inValues;
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -38,9 +37,9 @@ public class ScopeTrans {
|
||||
*/
|
||||
private final ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
|
||||
private PersistBatch restoreBatch;
|
||||
private Boolean restoreBatch;
|
||||
|
||||
private PersistBatch restoreBatchOnCascade;
|
||||
private Boolean restoreBatchOnCascade;
|
||||
|
||||
private int restoreBatchSize;
|
||||
|
||||
@@ -68,20 +67,20 @@ public class ScopeTrans {
|
||||
|
||||
if (transaction != null) {
|
||||
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
|
||||
restoreBatch = transaction.getBatch();
|
||||
restoreBatchOnCascade = transaction.getBatchOnCascade();
|
||||
restoreBatch = transaction.isBatchMode();
|
||||
restoreBatchOnCascade = transaction.isBatchOnCascade();
|
||||
restoreBatchSize = transaction.getBatchSize();
|
||||
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
|
||||
restoreBatchFlushOnQuery = transaction.isBatchFlushOnQuery();
|
||||
}
|
||||
if (txScope.isBatchSet()) {
|
||||
transaction.setBatch(txScope.getBatch());
|
||||
transaction.setBatchMode(txScope.isBatchMode());
|
||||
}
|
||||
if (!txScope.isFlushOnQuery()) {
|
||||
transaction.setBatchFlushOnQuery(false);
|
||||
}
|
||||
if (txScope.isBatchOnCascadeSet()) {
|
||||
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
|
||||
transaction.setBatchOnCascade(txScope.isBatchOnCascade());
|
||||
}
|
||||
if (txScope.isBatchSizeSet()) {
|
||||
transaction.setBatchSize(txScope.getBatchSize());
|
||||
@@ -139,7 +138,7 @@ public class ScopeTrans {
|
||||
nestedCommit = true;
|
||||
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
|
||||
if (restoreBatch != null) {
|
||||
transaction.setBatch(restoreBatch);
|
||||
transaction.setBatchMode(restoreBatch);
|
||||
}
|
||||
if (restoreBatchOnCascade != null) {
|
||||
transaction.setBatchOnCascade(restoreBatchOnCascade);
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
/**
|
||||
* Return the label for the query.
|
||||
*/
|
||||
@Override
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
|
||||
@@ -312,4 +312,8 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader,
|
||||
*/
|
||||
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql update regardless of transaction batch mode.
|
||||
*/
|
||||
int executeNow(SpiSqlUpdate sqlUpdate);
|
||||
}
|
||||
|
||||
@@ -134,27 +134,40 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
/**
|
||||
* Includes soft deletes rows in the result.
|
||||
*/
|
||||
SOFT_DELETED,
|
||||
SOFT_DELETED(false),
|
||||
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
DRAFT,
|
||||
DRAFT(false),
|
||||
|
||||
/**
|
||||
* Query runs against current data (normal).
|
||||
*/
|
||||
CURRENT,
|
||||
CURRENT(false),
|
||||
|
||||
/**
|
||||
* Query runs potentially returning many versions of the same bean.
|
||||
*/
|
||||
VERSIONS,
|
||||
VERSIONS(true),
|
||||
|
||||
/**
|
||||
* Query runs 'As Of' a given date time.
|
||||
*/
|
||||
AS_OF;
|
||||
AS_OF(true);
|
||||
|
||||
private final boolean history;
|
||||
|
||||
TemporalMode(boolean history) {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a history query.
|
||||
*/
|
||||
public boolean isHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode of the query of if null return CURRENT mode.
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeaninternal.server.transaction.ProfileStream;
|
||||
|
||||
@@ -3,12 +3,10 @@ package io.ebeaninternal.api;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeaninternal.server.transaction.ProfileStream;
|
||||
@@ -255,36 +253,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.isBatchMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatch(PersistBatch persistBatchMode) {
|
||||
transaction.setBatch(persistBatchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatch() {
|
||||
return transaction.getBatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(boolean batchMode) {
|
||||
transaction.setBatchOnCascade(batchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
|
||||
transaction.setBatchOnCascade(batchOnCascadeMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchOnCascade() {
|
||||
return transaction.isBatchOnCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatchOnCascade() {
|
||||
return transaction.getBatchOnCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchSize(int batchSize) {
|
||||
transaction.setBatchSize(batchSize);
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.transaction.TransactionManager;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdates;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -29,16 +30,21 @@ public class TransactionEvent implements Serializable {
|
||||
*/
|
||||
private final transient boolean local;
|
||||
|
||||
private final long startMillis;
|
||||
|
||||
private TransactionEventTable eventTables;
|
||||
|
||||
private transient TransactionEventBeans eventBeans;
|
||||
private transient List<PersistRequestBean<?>> listenerNotify;
|
||||
|
||||
private transient DeleteByIdMap deleteByIdMap;
|
||||
|
||||
private transient CacheChangeSet changeSet;
|
||||
|
||||
/**
|
||||
* Create the TransactionEvent, one per Transaction.
|
||||
*/
|
||||
public TransactionEvent() {
|
||||
public TransactionEvent(long startMillis) {
|
||||
this.startMillis = startMillis;
|
||||
this.local = true;
|
||||
}
|
||||
|
||||
@@ -71,8 +77,8 @@ public class TransactionEvent implements Serializable {
|
||||
/**
|
||||
* Return the list of PersistRequestBean's for this transaction.
|
||||
*/
|
||||
public List<PersistRequestBean<?>> getPersistRequestBeans() {
|
||||
return (eventBeans == null) ? null : eventBeans.getRequests();
|
||||
public List<PersistRequestBean<?>> getListenerNotify() {
|
||||
return (listenerNotify == null) ? null : listenerNotify;
|
||||
}
|
||||
|
||||
public TransactionEventTable getEventTables() {
|
||||
@@ -94,17 +100,13 @@ public class TransactionEvent implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a inserted updated or deleted bean to the event.
|
||||
* Add post commit listeners. Watch this for large transactions.
|
||||
*/
|
||||
public void add(PersistRequestBean<?> request) {
|
||||
|
||||
if (request.isNotify()) {
|
||||
// either a BeanListener or Cache is interested
|
||||
if (eventBeans == null) {
|
||||
eventBeans = new TransactionEventBeans();
|
||||
}
|
||||
eventBeans.add(request);
|
||||
public void addListenerNotify(PersistRequestBean<?> request) {
|
||||
if (listenerNotify == null) {
|
||||
listenerNotify = new ArrayList<>();
|
||||
}
|
||||
listenerNotify.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,11 +114,13 @@ public class TransactionEvent implements Serializable {
|
||||
*/
|
||||
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
|
||||
|
||||
if (eventBeans == null && deleteByIdMap == null && eventTables == null) {
|
||||
if (changeSet == null && deleteByIdMap == null && eventTables == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheChangeSet changeSet = new CacheChangeSet(manager.clockNowMillis());
|
||||
if (changeSet == null) {
|
||||
changeSet = new CacheChangeSet(manager.clockNowMillis());
|
||||
}
|
||||
if (eventTables != null && !eventTables.isEmpty()) {
|
||||
// notify cache with table based changes
|
||||
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
|
||||
@@ -124,9 +128,6 @@ public class TransactionEvent implements Serializable {
|
||||
dm.cacheNotify(tableIUD, changeSet);
|
||||
}
|
||||
}
|
||||
if (eventBeans != null) {
|
||||
eventBeans.notifyCache(changeSet);
|
||||
}
|
||||
if (deleteByIdMap != null) {
|
||||
deleteByIdMap.notifyCache(changeSet);
|
||||
}
|
||||
@@ -137,12 +138,25 @@ public class TransactionEvent implements Serializable {
|
||||
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
|
||||
*/
|
||||
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
|
||||
|
||||
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
|
||||
if (persistRequestBeans != null) {
|
||||
for (PersistRequestBean<?> persistRequestBean : persistRequestBeans) {
|
||||
List<PersistRequestBean<?>> requests = getListenerNotify();
|
||||
if (requests != null) {
|
||||
for (PersistRequestBean<?> persistRequestBean : requests) {
|
||||
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CacheChangeSet that we add cache notification messages to.
|
||||
*
|
||||
* We want to add to this change set as we process requests allowing the
|
||||
* PersistRequestBean to be garbage collected for large transactions.
|
||||
*/
|
||||
public CacheChangeSet obtainCacheChangeSet() {
|
||||
if (changeSet == null) {
|
||||
changeSet = new CacheChangeSet(startMillis);
|
||||
}
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Lists of inserted updated and deleted beans that have a BeanPersistListener.
|
||||
* <p>
|
||||
* These beans will be sent to the appropriate BeanListeners after a successful
|
||||
* commit of the transaction.
|
||||
* </p>
|
||||
*/
|
||||
public class TransactionEventBeans {
|
||||
|
||||
final ArrayList<PersistRequestBean<?>> requests = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return the list of PersistRequests that BeanListeners are interested in.
|
||||
*/
|
||||
public List<PersistRequestBean<?>> getRequests() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bean for BeanListener notification.
|
||||
*/
|
||||
public void add(PersistRequestBean<?> request) {
|
||||
|
||||
requests.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the cache changes.
|
||||
*/
|
||||
public void notifyCache(CacheChangeSet changeSet) {
|
||||
for (PersistRequestBean<?> request : requests) {
|
||||
request.notifyCache(changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.config.DbMigrationConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebean.migration.runner.ScriptTransform;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
@@ -42,6 +44,7 @@ public class DdlGenerator {
|
||||
private final boolean jaxbPresent;
|
||||
private final boolean ddlCommitOnCreateIndex;
|
||||
private final String dbSchema;
|
||||
private final ScriptTransform scriptTransform;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropAllContent;
|
||||
@@ -61,6 +64,7 @@ public class DdlGenerator {
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
|
||||
}
|
||||
this.scriptTransform = createScriptTransform(serverConfig.getMigrationConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +130,9 @@ public class DdlGenerator {
|
||||
|
||||
private void createSchemaIfRequired(Connection connection) {
|
||||
try {
|
||||
server.getDatabasePlatform().createSchemaIfNotExists(dbSchema, connection);
|
||||
for (String schema : dbSchema.split(",")) {
|
||||
server.getDatabasePlatform().createSchemaIfNotExists(schema, connection);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to create DB Schema", e);
|
||||
}
|
||||
@@ -144,7 +150,7 @@ public class DdlGenerator {
|
||||
} else if (ddlCommitOnCreateIndex) {
|
||||
runner.setCommitOnCreateIndex();
|
||||
}
|
||||
int count = runner.runAll(content, connection);
|
||||
int count = runner.runAll(scriptTransform.transform(content), connection);
|
||||
if (expectErrors) {
|
||||
connection.setAutoCommit(false);
|
||||
}
|
||||
@@ -164,7 +170,7 @@ public class DdlGenerator {
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
|
||||
if (extraApply != null) {
|
||||
runScript(connection, false, extraApply, "extra-dll");
|
||||
runScript(connection, false, extraApply, "extra-ddl");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,13 +192,13 @@ public class DdlGenerator {
|
||||
if (currentModel.isTablePartitioning()) {
|
||||
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
|
||||
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
|
||||
runScript(connection, false, extraPartitioning, "builtin-partitioning-dll");
|
||||
runScript(connection, false, extraPartitioning, "builtin-partitioning-ddl");
|
||||
}
|
||||
}
|
||||
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
|
||||
if (extraApply != null) {
|
||||
runScript(connection, false, extraApply, "extra-dll");
|
||||
runScript(connection, false, extraApply, "extra-ddl");
|
||||
}
|
||||
|
||||
if (currentModel.isTablePartitioning()) {
|
||||
@@ -203,7 +209,7 @@ public class DdlGenerator {
|
||||
|
||||
/**
|
||||
* Check if table partitions exist and if not create some. The expectation is that
|
||||
* extra-dll.xml should have some partition initialisation but this helps people get going.
|
||||
* extra-ddl.xml should have some partition initialisation but this helps people get going.
|
||||
*/
|
||||
private void checkInitialTablePartitions(Connection connection) {
|
||||
|
||||
@@ -342,8 +348,15 @@ public class DdlGenerator {
|
||||
buf.append(s).append("\n");
|
||||
}
|
||||
return buf.toString();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ScriptTransform for placeholder key/value replacement.
|
||||
*/
|
||||
private ScriptTransform createScriptTransform(DbMigrationConfig config) {
|
||||
|
||||
return ScriptTransform.build(config.getRunPlaceholders(), config.getRunPlaceholderMap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hana.HanaPlatform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
@@ -57,7 +58,7 @@ import java.util.List;
|
||||
*
|
||||
* DbMigration migration = DbMigration.create();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
* migration.setPlatform(Platform.POSTGRES);
|
||||
*
|
||||
* migration.generateMigration();
|
||||
*
|
||||
@@ -65,7 +66,7 @@ import java.util.List;
|
||||
*/
|
||||
public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DefaultDbMigration.class);
|
||||
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration");
|
||||
|
||||
private static final String initialVersion = "1.0";
|
||||
|
||||
@@ -265,10 +266,20 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the generated migration or null
|
||||
*/
|
||||
@Override
|
||||
public String generateMigration() throws IOException {
|
||||
return generateMigrationFor(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateInitMigration() throws IOException {
|
||||
return generateMigrationFor(true);
|
||||
}
|
||||
|
||||
private String generateMigrationFor(boolean dbinitMigration) throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
if (!online) {
|
||||
@@ -284,9 +295,18 @@ public class DefaultDbMigration implements DbMigration {
|
||||
configurePlatforms();
|
||||
}
|
||||
try {
|
||||
Request request = createRequest();
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
|
||||
Request request = createRequest(dbinitMigration);
|
||||
if (!dbinitMigration) {
|
||||
// repeatable migrations
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
|
||||
} else {
|
||||
for (Pair pair : platforms) {
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(request.migrationDir, pair.prefix);
|
||||
generateExtraDdl(subPath, pair.platform, request.isTablePartitioning());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String pendingVersion = generatePendingDrop();
|
||||
@@ -303,6 +323,24 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the versions containing pending drops.
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPendingDrops() {
|
||||
if (!online) {
|
||||
DbOffline.setGenerateMigration();
|
||||
}
|
||||
setDefaults();
|
||||
try {
|
||||
return createRequest(false).getPendingDrops();
|
||||
} finally {
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration for each of the target platforms.
|
||||
*/
|
||||
@@ -316,7 +354,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Generate "repeatable" migration scripts.
|
||||
* <p>
|
||||
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
|
||||
* These take scrips from extra-ddl.xml (typically views) and outputs "repeatable"
|
||||
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
|
||||
* migration runner.
|
||||
* </p>
|
||||
@@ -407,36 +445,52 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return version;
|
||||
}
|
||||
|
||||
private Request createRequest() {
|
||||
return new Request();
|
||||
private Request createRequest(boolean dbinitMigration) {
|
||||
return new Request(dbinitMigration);
|
||||
}
|
||||
|
||||
private class Request {
|
||||
|
||||
final boolean dbinitMigration;
|
||||
final File migrationDir;
|
||||
final File modelDir;
|
||||
final MigrationModel migrationModel;
|
||||
final CurrentModel currentModel;
|
||||
final ModelContainer migrated;
|
||||
final ModelContainer current;
|
||||
|
||||
private Request() {
|
||||
this.migrationDir = getMigrationDirectory();
|
||||
this.modelDir = getModelDirectory(migrationDir);
|
||||
this.migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
|
||||
this.migrated = migrationModel.read();
|
||||
private Request(boolean dbinitMigration) {
|
||||
this.dbinitMigration = dbinitMigration;
|
||||
this.currentModel = new CurrentModel(server, constraintNaming);
|
||||
this.current = currentModel.read();
|
||||
this.migrationDir = getMigrationDirectory(dbinitMigration);
|
||||
if (dbinitMigration) {
|
||||
this.modelDir = null;
|
||||
this.migrated = new ModelContainer();
|
||||
} else {
|
||||
this.modelDir = getModelDirectory(migrationDir);
|
||||
MigrationModel migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
|
||||
this.migrated = migrationModel.read(dbinitMigration);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isTablePartitioning() {
|
||||
return current.isTablePartitioning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next migration version (based on existing migration versions).
|
||||
*/
|
||||
String nextVersion() {
|
||||
// always read the next version using the main migration directory (not dbinit)
|
||||
File migDirectory = getMigrationDirectory(false);
|
||||
File modelDir = getModelDirectory(migDirectory);
|
||||
return LastMigration.nextVersion(migDirectory, modelDir, dbinitMigration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration for the pending drops for a given version.
|
||||
*/
|
||||
public Migration migrationForPendingDrop(String pendingVersion) {
|
||||
Migration migrationForPendingDrop(String pendingVersion) {
|
||||
|
||||
Migration migration = migrated.migrationForPendingDrop(pendingVersion);
|
||||
|
||||
@@ -455,7 +509,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Create and return the diff of the current model to the migration model.
|
||||
*/
|
||||
public Migration createDiffMigration() {
|
||||
Migration createDiffMigration() {
|
||||
ModelDiff diff = new ModelDiff(migrated);
|
||||
diff.compareTo(current);
|
||||
return diff.isEmpty() ? null : diff.getMigration();
|
||||
@@ -464,10 +518,10 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
private String generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
|
||||
|
||||
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
|
||||
String fullVersion = getFullVersion(request.nextVersion(), dropsFor);
|
||||
|
||||
logger.info("generating migration:{}", fullVersion);
|
||||
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
|
||||
if (!request.dbinitMigration && !writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
|
||||
logger.warn("migration already exists, not generating DDL");
|
||||
return null;
|
||||
} else {
|
||||
@@ -494,7 +548,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (nextDrop != null) {
|
||||
return nextDrop;
|
||||
}
|
||||
return migrationConfig.getGeneratePendingDrop();
|
||||
return generatePendingDrop;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,16 +556,17 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* <p>
|
||||
* The full version can contain a comment suffix after a "__" double underscore.
|
||||
*/
|
||||
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
|
||||
private String getFullVersion(String nextVersion, String dropsFor) {
|
||||
|
||||
String version = migrationConfig.getVersion();
|
||||
String version = getVersion();
|
||||
if (version == null) {
|
||||
version = migrationModel.getNextVersion(initialVersion);
|
||||
version = (nextVersion != null) ? nextVersion : initialVersion;
|
||||
}
|
||||
|
||||
String fullVersion = migrationConfig.getApplyPrefix() + version;
|
||||
if (migrationConfig.getName() != null) {
|
||||
fullVersion += "__" + toUnderScore(migrationConfig.getName());
|
||||
String name = getName();
|
||||
if (name != null) {
|
||||
fullVersion += "__" + toUnderScore(name);
|
||||
|
||||
} else if (dropsFor != null) {
|
||||
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
|
||||
@@ -532,15 +587,13 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Write any extra platform ddl.
|
||||
*/
|
||||
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(writePath, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
|
||||
|
||||
generateExtraDdl(subPath, pair.platform, currentModel.isTablePartitioning());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,14 +604,14 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Write the migration xml.
|
||||
*/
|
||||
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
private boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
|
||||
String modelFile = fullVersion + migrationConfig.getModelSuffix();
|
||||
File file = new File(resourcePath, modelFile);
|
||||
if (file.exists()) {
|
||||
return false;
|
||||
}
|
||||
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
|
||||
String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null;
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
|
||||
xmlWriter.write(dbMigration, file);
|
||||
return true;
|
||||
@@ -567,7 +620,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Set default server and platform if necessary.
|
||||
*/
|
||||
protected void setDefaults() {
|
||||
private void setDefaults() {
|
||||
if (server == null) {
|
||||
setServer(Ebean.getDefaultServer());
|
||||
}
|
||||
@@ -586,29 +639,81 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (header != null) {
|
||||
migrationConfig.setDdlHeader(header);
|
||||
}
|
||||
if (includeGeneratedFileComment != null) {
|
||||
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
|
||||
}
|
||||
if (version != null) {
|
||||
migrationConfig.setVersion(version);
|
||||
}
|
||||
if (name != null) {
|
||||
migrationConfig.setName(name);
|
||||
}
|
||||
if (generatePendingDrop != null) {
|
||||
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration version (typically FlywayDb compatible).
|
||||
* <p>
|
||||
* Example: 1.1.1_2
|
||||
* <p>
|
||||
* The version is expected to be the combination of the current pom version plus
|
||||
* a 'feature' id. The combined version must be unique and ordered to work with
|
||||
* FlywayDb so each developer sets a unique version so that the migration script
|
||||
* generated is unique (typically just prior to being submitted as a merge request).
|
||||
*/
|
||||
private String getVersion() {
|
||||
String envVersion = readEnvironment("ddl.migration.version");
|
||||
if (!isEmpty(envVersion)) {
|
||||
return envVersion.trim();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration name which is short description text that can be appended to
|
||||
* the migration version to become the ddl script file name.
|
||||
* <p>
|
||||
* So if the name is "a foo table" then the ddl script file could be:
|
||||
* "1.1.1_2__a-foo-table.sql"
|
||||
* </p>
|
||||
* <p>
|
||||
* When the DB migration relates to a git feature (merge request) then this description text
|
||||
* is a short description of the feature.
|
||||
* </p>
|
||||
*/
|
||||
private String getName() {
|
||||
String envName = readEnvironment("ddl.migration.name");
|
||||
if (!isEmpty(envName)) {
|
||||
return envName.trim();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the string is null or empty.
|
||||
*/
|
||||
private boolean isEmpty(String val) {
|
||||
return val == null || val.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system or environment property.
|
||||
*/
|
||||
private String readEnvironment(String key) {
|
||||
|
||||
String val = System.getProperty(key);
|
||||
if (val == null) {
|
||||
val = System.getenv(key);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the main migration directory.
|
||||
*/
|
||||
File getMigrationDirectory() {
|
||||
return getMigrationDirectory(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
protected File getMigrationDirectory() {
|
||||
File getMigrationDirectory(boolean dbinitMigration) {
|
||||
|
||||
// path to src/main/resources in typical maven project
|
||||
File resourceRootDir = new File(pathToResources);
|
||||
String resourcePath = migrationConfig.getMigrationPath();
|
||||
String resourcePath = migrationConfig.getMigrationPath(dbinitMigration);
|
||||
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/model
|
||||
File path = new File(resourceRootDir, resourcePath);
|
||||
@@ -623,7 +728,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Return the model directory (relative to the migration directory).
|
||||
*/
|
||||
protected File getModelDirectory(File migrationDirectory) {
|
||||
private File getModelDirectory(File migrationDirectory) {
|
||||
String modelPath = migrationConfig.getModelPath();
|
||||
if (modelPath == null || modelPath.isEmpty()) {
|
||||
return migrationDirectory;
|
||||
@@ -662,6 +767,8 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
return new SQLitePlatform();
|
||||
case HANA:
|
||||
return new HanaPlatform();
|
||||
case GENERIC:
|
||||
return new DatabasePlatform();
|
||||
|
||||
@@ -674,19 +781,19 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* Holds a platform and prefix. Used to generate multiple platform specific DDL
|
||||
* for a single migration.
|
||||
*/
|
||||
public static class Pair {
|
||||
static class Pair {
|
||||
|
||||
/**
|
||||
* The platform to generate the DDL for.
|
||||
*/
|
||||
public final DatabasePlatform platform;
|
||||
final DatabasePlatform platform;
|
||||
|
||||
/**
|
||||
* A prefix included into the file/resource names indicating the platform.
|
||||
*/
|
||||
public final String prefix;
|
||||
final String prefix;
|
||||
|
||||
public Pair(DatabasePlatform platform, String prefix) {
|
||||
Pair(DatabasePlatform platform, String prefix) {
|
||||
this.platform = platform;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebeaninternal.dbmigration.model.MigrationVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility to determine the last sql migration version and next version.
|
||||
*/
|
||||
class LastMigration {
|
||||
|
||||
private static final String SQL = ".sql";
|
||||
|
||||
private static final String MODEL_XML = ".model.xml";
|
||||
|
||||
/**
|
||||
* Return the next migation version given the migration directory.
|
||||
*/
|
||||
static String nextVersion(File migDir, File modelDir, boolean dbinitMigration) {
|
||||
|
||||
String last = lastVersion(migDir, modelDir);
|
||||
if (last == null) {
|
||||
return null;
|
||||
}
|
||||
return (dbinitMigration) ? last : MigrationVersion.parse(last).nextVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last migation version given the migration directory.
|
||||
*/
|
||||
static String lastVersion(File migDirectory, File modelDir) {
|
||||
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
|
||||
File[] sqlFiles = migDirectory.listFiles(pathname -> includeSqlFile(pathname.getName().toLowerCase()));
|
||||
if (sqlFiles != null) {
|
||||
for (File file : sqlFiles) {
|
||||
fileNames.add(trim(file.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modelDir != null) {
|
||||
File[] xmlFiles = modelDir.listFiles(pathname -> includeModelFile(pathname.getName().toLowerCase()));
|
||||
if (xmlFiles != null) {
|
||||
for (File file : xmlFiles) {
|
||||
fileNames.add(trim(file.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(fileNames);
|
||||
if (!fileNames.isEmpty()) {
|
||||
return fileNames.get(fileNames.size() - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean includeSqlFile(String lowerFileName) {
|
||||
if (lowerFileName.startsWith("r") || lowerFileName.startsWith("i") || !lowerFileName.endsWith(SQL)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean includeModelFile(String lowerFileName) {
|
||||
return lowerFileName.endsWith(MODEL_XML);
|
||||
}
|
||||
|
||||
private static String trim(String name) {
|
||||
name = name.toLowerCase();
|
||||
char c = name.charAt(0);
|
||||
if (c == 'v') {
|
||||
name = name.substring(1);
|
||||
}
|
||||
int p = name.indexOf("__");
|
||||
if (p > -1) {
|
||||
name = name.substring(0, p);
|
||||
}
|
||||
if (name.endsWith(SQL)) {
|
||||
name = name.substring(0, name.length() - 4);
|
||||
}
|
||||
if (name.endsWith(MODEL_XML)) {
|
||||
name = name.substring(0, name.length() - 10);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,11 @@ public class BaseDdlHandler implements DdlHandler {
|
||||
protected final TableDdl tableDdl;
|
||||
|
||||
public BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.tableDdl = new BaseTableDdl(serverConfig, platformDdl);
|
||||
this(serverConfig, platformDdl, new BaseTableDdl(serverConfig, platformDdl));
|
||||
}
|
||||
|
||||
protected BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl, TableDdl tableDdl) {
|
||||
this.tableDdl = tableDdl;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class AbstractHanaDdl extends PlatformDdl {
|
||||
|
||||
private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\w+)\\s*\\[\\s*\\]\\s*(\\(\\d+\\))?", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
public AbstractHanaDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.addColumn = "add (";
|
||||
this.addColumnSuffix = ")";
|
||||
this.alterColumn = "alter (";
|
||||
this.alterColumnSuffix = ")";
|
||||
this.columnDropDefault = " default null";
|
||||
this.columnSetDefault = " default";
|
||||
this.columnSetNotnull = " not null";
|
||||
this.columnSetNull = " null";
|
||||
this.dropColumn = "drop (";
|
||||
this.dropColumnSuffix = ")";
|
||||
this.dropConstraintIfExists = "drop constraint ";
|
||||
this.dropIndexIfExists = "drop index ";
|
||||
this.dropSequenceIfExists = "drop sequence ";
|
||||
this.dropTableCascade = " cascade";
|
||||
this.dropTableIfExists = "drop table ";
|
||||
this.fallbackArrayType = "nvarchar(1000)";
|
||||
this.historyDdl = new HanaHistoryDdl();
|
||||
this.identitySuffix = " generated by default as identity";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnBaseAttributes(AlterColumn alter) {
|
||||
String tableName = alter.getTableName();
|
||||
String columnName = alter.getColumnName();
|
||||
String currentType = alter.getCurrentType();
|
||||
String type = alter.getType() != null ? alter.getType() : currentType;
|
||||
type = convert(type, false);
|
||||
currentType = convert(currentType, false);
|
||||
boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
|
||||
String notnullClause = notnull ? " not null" : "";
|
||||
String defaultValue = DdlHelp.isDropDefault(alter.getDefaultValue()) ? "null"
|
||||
: (alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue());
|
||||
String defaultValueClause = (defaultValue == null || defaultValue.isEmpty()) ? "" : " default " + defaultValue;
|
||||
|
||||
try {
|
||||
DdlBuffer buffer = new BaseDdlBuffer(null);
|
||||
if (!isConvertible(currentType, type)) {
|
||||
// add an intermediate conversion if possible
|
||||
if (isNumberType(currentType)) {
|
||||
// numbers can always be converted to decimal
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
|
||||
.append(" decimal ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
|
||||
.endOfStatement();
|
||||
|
||||
} else if (isStringType(currentType)) {
|
||||
// strings can always be converted to nclob
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
|
||||
.append(" nclob ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
|
||||
.endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
|
||||
.append(" ").append(type).append(defaultValueClause).append(notnullClause).append(alterColumnSuffix);
|
||||
|
||||
return buffer.getBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return new HanaDdlHandler(serverConfig, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterColumnType(String tableName, String columnName, String type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
Matcher matcher = ARRAY_PATTERN.matcher(logicalArrayType);
|
||||
if (matcher.matches()) {
|
||||
return convert(matcher.group(1), false) + " array" + (matcher.group(2) == null ? "" : matcher.group(2));
|
||||
} else {
|
||||
return fallbackArrayType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
|
||||
if (nullableColumns == null || nullableColumns.length == 0) {
|
||||
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
|
||||
} else {
|
||||
return "-- cannot create unique index \"" + uqName + "\" on table \"" + tableName + "\" with nullable columns";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
|
||||
DdlBuffer buffer = new BaseDdlBuffer(null);
|
||||
try {
|
||||
buffer.append("delimiter $$").newLine();
|
||||
buffer.append("do").newLine();
|
||||
buffer.append("begin").newLine();
|
||||
buffer.append("declare exit handler for sql_error_code 397 begin end").endOfStatement();
|
||||
buffer.append("exec 'alter table ").append(tableName).append(" ").append(dropUniqueConstraint).append(" ")
|
||||
.append(maxConstraintName(uniqueConstraintName)).append("'").endOfStatement();
|
||||
buffer.append("end").endOfStatement();
|
||||
buffer.append("$$");
|
||||
return buffer.getBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String alterTableDropConstraint(String tableName, String constraintName) {
|
||||
return alterTableDropUniqueConstraint(tableName, constraintName);
|
||||
}
|
||||
|
||||
/**
|
||||
* It is rather complex to delete a column on HANA as there must not exist any
|
||||
* foreign keys. That's why we call a user stored procedure here
|
||||
*/
|
||||
@Override
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')")
|
||||
.endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a data type can be converted to another data type. Data types can't
|
||||
* be converted if the target type has a lower precision than the source type.
|
||||
*
|
||||
* @param sourceType The source data type
|
||||
* @param targetType the target data type
|
||||
* @return {@code true} if the type can be converted, {@code false} otherwise
|
||||
*/
|
||||
private boolean isConvertible(String sourceType, String targetType) {
|
||||
if (Objects.equals(sourceType, targetType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourceType == null || targetType == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("bigint".equals(sourceType)) {
|
||||
if ("integer".equals(targetType) || "smallint".equals(targetType) || "tinyint".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("integer".equals(sourceType)) {
|
||||
if ("smallint".equals(targetType) || "tinyint".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("smallint".equals(sourceType)) {
|
||||
if ("tinyint".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("double".equals(sourceType)) {
|
||||
if ("real".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DbPlatformType dbPlatformSourceType = DbPlatformType.parse(sourceType);
|
||||
|
||||
if ("float".equals(dbPlatformSourceType.getName())) {
|
||||
if ("real".equals(targetType)) {
|
||||
return false;
|
||||
}
|
||||
} else if ("varchar".equals(dbPlatformSourceType.getName()) || "nvarchar".equals(dbPlatformSourceType.getName())) {
|
||||
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
|
||||
if ("varchar".equals(dbPlatformTargetType.getName()) || "nvarchar".equals(dbPlatformTargetType.getName())) {
|
||||
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if ("decimal".equals(dbPlatformSourceType.getName())) {
|
||||
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
|
||||
if ("decimal".equals(dbPlatformTargetType.getName())) {
|
||||
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()
|
||||
|| dbPlatformSourceType.getDefaultScale() > dbPlatformTargetType.getDefaultScale()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isNumberType(String type) {
|
||||
return type != null
|
||||
&& ("bigint".equals(type) || "integer".equals(type) || "smallint".equals(type) || "tinyint".equals(type)
|
||||
|| type.startsWith("float") || "real".equals(type) || "double".equals(type) || type.startsWith("decimal"));
|
||||
}
|
||||
|
||||
private boolean isStringType(String type) {
|
||||
return type != null
|
||||
&& (type.startsWith("varchar") || type.startsWith("nvarchar") || "clob".equals(type) || "nclob".equals(type));
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
String partitionMode = createTable.getPartitionMode();
|
||||
|
||||
DdlBuffer apply = writer.apply();
|
||||
apply.append("create table ").append(tableName).append(" (");
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(tableName).append(" (");
|
||||
writeTableColumns(apply, columns, useIdentity);
|
||||
writeCheckConstraints(apply, createTable);
|
||||
writeUniqueConstraints(apply, createTable);
|
||||
@@ -909,7 +909,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == historySupport.TRIGGER_BASED) {
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// mysql and sql server column type change allowing nulls in the history table column
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
|
||||
+3
-3
@@ -175,7 +175,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
|
||||
|
||||
apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(table.getName()).append(historySuffix).append("(").newLine();
|
||||
|
||||
Collection<MColumn> cols = table.allColumns();
|
||||
for (MColumn column : cols) {
|
||||
@@ -238,8 +238,8 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
}
|
||||
|
||||
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodStart).endOfStatement();
|
||||
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
|
||||
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodStart);
|
||||
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodEnd);
|
||||
}
|
||||
|
||||
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
|
||||
|
||||
@@ -15,6 +15,8 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
|
||||
|
||||
public H2HistoryDdl() {
|
||||
this.sysPeriodType = "timestamp";
|
||||
this.currentTimestamp = "now()";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
public class HanaColumnStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaColumnStoreDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.createTable = "create column table";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createIndex(String indexName, String tableName, String[] columns) {
|
||||
if (columns == null || columns.length == 0) {
|
||||
return "-- cannot create index: no columns given";
|
||||
}
|
||||
|
||||
if (columns.length == 1) {
|
||||
return "-- explicit index \"" + indexName + "\" for single column \"" + columns[0] + "\" of table \"" + tableName
|
||||
+ "\" is not necessary";
|
||||
}
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("create inverted hash index ").append(maxConstraintName(indexName)).append(" on ").append(tableName);
|
||||
appendColumns(columns, buffer);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dropIndex(String indexName, String tableName) {
|
||||
DdlBuffer buffer = new BaseDdlBuffer(null);
|
||||
try {
|
||||
buffer.append("delimiter $$").newLine();
|
||||
buffer.append("do").newLine();
|
||||
buffer.append("begin").newLine();
|
||||
buffer.append("declare exit handler for sql_error_code 261 begin end").endOfStatement();
|
||||
buffer.append("exec '").append(dropIndexIfExists).append(maxConstraintName(indexName)).append("'")
|
||||
.endOfStatement();
|
||||
buffer.append("end").endOfStatement();
|
||||
buffer.append("$$");
|
||||
return buffer.getBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
|
||||
|
||||
public class HanaDdlHandler extends BaseDdlHandler {
|
||||
|
||||
public HanaDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
super(serverConfig, platformDdl, new HanaTableDdl(serverConfig, platformDdl));
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.model.MColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class HanaHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
private String systemPeriodStart;
|
||||
private String systemPeriodEnd;
|
||||
private PlatformDdl platformDdl;
|
||||
private String historySuffix;
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
private Map<String, String> createdHistoryTables = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "_start";
|
||||
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "_end";
|
||||
this.platformDdl = platformDdl;
|
||||
this.historySuffix = serverConfig.getHistoryTableSuffix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
|
||||
String tableName = table.getName();
|
||||
String historyTableName = tableName + historySuffix;
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
if (apply.isEmpty()) {
|
||||
createdHistoryTables.clear();
|
||||
}
|
||||
|
||||
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(historyTableName).append(" (").newLine();
|
||||
|
||||
// create history table
|
||||
Collection<MColumn> cols = table.allColumns();
|
||||
for (MColumn column : cols) {
|
||||
if (!column.isDraftOnly()) {
|
||||
writeColumnDefinition(apply, column.getName(), column.getType(), column.getDefaultValue(), column.isNotnull(),
|
||||
column.isIdentity() ? platformDdl.identitySuffix : null);
|
||||
apply.append(",").newLine();
|
||||
}
|
||||
}
|
||||
writeColumnDefinition(apply, systemPeriodStart, "TIMESTAMP", null, false, null);
|
||||
apply.append(",").newLine();
|
||||
writeColumnDefinition(apply, systemPeriodEnd, "TIMESTAMP", null, false, null);
|
||||
apply.newLine().append(")").endOfStatement();
|
||||
|
||||
// enable system versioning
|
||||
apply.append("alter table ").append(tableName).append(" add (").newLine();
|
||||
apply.append(" ").append(systemPeriodStart).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW START, ").newLine();
|
||||
apply.append(" ").append(systemPeriodEnd).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW END").newLine();
|
||||
apply.append(")").endOfStatement();
|
||||
|
||||
apply.append("alter table ").append(tableName).append(" add period for system_time(").append(systemPeriodStart)
|
||||
.append(",").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
enableSystemVersioning(apply, tableName, historyTableName, true, false);
|
||||
|
||||
createdHistoryTables.put(tableName, historyTableName);
|
||||
|
||||
dropHistoryTable(writer.dropAll(), tableName, historyTableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
dropHistoryTable(writer.applyDropDependencies(), dropHistoryTable.getBaseTable(),
|
||||
dropHistoryTable.getBaseTable() + historySuffix);
|
||||
}
|
||||
|
||||
protected void dropHistoryTable(DdlBuffer apply, String baseTable, String historyTable) throws IOException {
|
||||
// disable system versioning
|
||||
disableSystemVersioning(apply, baseTable);
|
||||
|
||||
apply.append("alter table ").append(baseTable).append(" drop period for system_time").endOfStatement();
|
||||
|
||||
// drop the period columns
|
||||
apply.append("alter table ").append(baseTable).append(" drop (").append(systemPeriodStart).append(",")
|
||||
.append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
// drop the history table
|
||||
apply.append("drop table ").append(historyTable).append(" cascade").endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
|
||||
MTable table = writer.getTable(addHistoryTable.getBaseTable());
|
||||
if (table == null) {
|
||||
throw new IllegalStateException("MTable " + addHistoryTable.getBaseTable() + " not found in writer? (required for history DDL)");
|
||||
}
|
||||
createWithHistory(writer, table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type, String defaultValue,
|
||||
boolean isNotNull, String generated) throws IOException {
|
||||
|
||||
String platformType = platformDdl.convert(type, false);
|
||||
buffer.append(" ").append(platformDdl.lowerColumnName(columnName));
|
||||
buffer.append(" ").append(platformType);
|
||||
if (defaultValue != null) {
|
||||
buffer.append(" default ").append(defaultValue);
|
||||
}
|
||||
if (isNotNull) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
if (generated != null) {
|
||||
buffer.append(" ").append(generated);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableSystemVersioning(DdlBuffer apply, String tableName) throws IOException {
|
||||
disableSystemVersioning(apply, tableName, false);
|
||||
}
|
||||
|
||||
public void disableSystemVersioning(DdlBuffer apply, String tableName, boolean uniqueStatement) throws IOException {
|
||||
apply.append("alter table ").append(tableName).append(" drop system versioning");
|
||||
if (uniqueStatement) {
|
||||
// needed for the DB migration test to prevent the statement from being filtered
|
||||
// out as a duplicate
|
||||
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
|
||||
}
|
||||
apply.endOfStatement();
|
||||
}
|
||||
|
||||
public void enableSystemVersioning(DdlBuffer apply, String tableName, String historyTableName, boolean validated,
|
||||
boolean uniqueStatement) throws IOException {
|
||||
apply.append("alter table ").append(tableName).append(" add system versioning history table ").append(historyTableName);
|
||||
if (!validated) {
|
||||
apply.append(" not validated");
|
||||
}
|
||||
if (uniqueStatement) {
|
||||
// needed for the DB migration test to prevent the statement from being filtered
|
||||
// out as a duplicate
|
||||
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
|
||||
}
|
||||
apply.endOfStatement();
|
||||
}
|
||||
|
||||
public boolean isSystemVersioningEnabled(String tableName) {
|
||||
return !createdHistoryTables.containsKey(tableName);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
|
||||
public class HanaRowStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaRowStoreDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.createTable = "create row table";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.config.PropertiesWrapper;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
public class HanaTableDdl extends BaseTableDdl {
|
||||
|
||||
private final HanaHistoryDdl historyDdl;
|
||||
private final boolean generateUniqueDdl;
|
||||
|
||||
public HanaTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) {
|
||||
super(serverConfig, platformDdl);
|
||||
this.historyDdl = (HanaHistoryDdl) platformDdl.historyDdl;
|
||||
if (serverConfig.getProperties() != null) {
|
||||
PropertiesWrapper wrapper = new PropertiesWrapper("ebean", "hana", serverConfig.getProperties(), serverConfig.getClassLoadConfig());
|
||||
this.generateUniqueDdl = wrapper.getBoolean("generateUniqueDdl", false);
|
||||
} else {
|
||||
this.generateUniqueDdl = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
String ddl = platformDdl.alterColumnBaseAttributes(alter);
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
|
||||
String tableName = addColumn.getTableName();
|
||||
MTable table = writer.getTable(tableName);
|
||||
if (table == null) {
|
||||
super.generate(writer, addColumn);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
historyDdl.disableSystemVersioning(writer.apply(), table.getName(), this.generateUniqueDdl);
|
||||
}
|
||||
|
||||
super.generate(writer, addColumn);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
List<Column> columns = addColumn.getColumn();
|
||||
for (Column column : columns) {
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
|
||||
}
|
||||
|
||||
historyDdl.enableSystemVersioning(writer.apply(), table.getName(), historyTable, false, this.generateUniqueDdl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
|
||||
String tableName = alterColumn.getTableName();
|
||||
MTable table = writer.getTable(tableName);
|
||||
if (table == null) {
|
||||
super.generate(writer, alterColumn);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
|
||||
}
|
||||
|
||||
super.generate(writer, alterColumn);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
if (hasValue(alterColumn.getType()) || hasValue(alterColumn.getDefaultValue()) || alterColumn.isNotnull() != null) {
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable);
|
||||
alterHistoryColumn.setColumnName(alterColumn.getColumnName());
|
||||
alterHistoryColumn.setType(alterColumn.getType());
|
||||
alterHistoryColumn.setDefaultValue(alterColumn.getDefaultValue());
|
||||
alterHistoryColumn.setNotnull(alterColumn.isNotnull());
|
||||
alterHistoryColumn.setCurrentType(alterColumn.getCurrentType());
|
||||
alterHistoryColumn.setCurrentDefaultValue(alterColumn.getCurrentDefaultValue());
|
||||
alterHistoryColumn.setCurrentNotnull(alterColumn.isCurrentNotnull());
|
||||
String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
|
||||
|
||||
// write the apply to history table
|
||||
writer.apply().append(histColumnDdl).endOfStatement();
|
||||
}
|
||||
|
||||
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
|
||||
String tableName = dropColumn.getTableName();
|
||||
MTable table = writer.getTable(tableName);
|
||||
if (table == null) {
|
||||
super.generate(writer, dropColumn);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
|
||||
}
|
||||
|
||||
super.generate(writer, dropColumn);
|
||||
|
||||
if (manageSystemVersioning) {
|
||||
// also drop from the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
alterTableDropColumn(writer.apply(), historyTable, dropColumn.getColumnName());
|
||||
|
||||
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -78,12 +78,16 @@ public class PlatformDdl {
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
|
||||
protected String alterColumn = "alter column";
|
||||
|
||||
protected String alterColumnSuffix = "";
|
||||
|
||||
protected String dropUniqueConstraint = "drop constraint";
|
||||
|
||||
protected String addConstraint = "add constraint";
|
||||
|
||||
protected String addColumn = "add column";
|
||||
|
||||
protected String addColumnSuffix = "";
|
||||
|
||||
protected String columnSetType = "";
|
||||
|
||||
@@ -96,6 +100,12 @@ public class PlatformDdl {
|
||||
protected String columnSetNull = "set null";
|
||||
|
||||
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
|
||||
|
||||
protected String createTable = "create table";
|
||||
|
||||
protected String dropColumn = "drop column";
|
||||
|
||||
protected String dropColumnSuffix = "";
|
||||
|
||||
/**
|
||||
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
|
||||
@@ -458,6 +468,7 @@ public class PlatformDdl {
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
buffer.append(addColumnSuffix);
|
||||
buffer.endOfStatement();
|
||||
|
||||
// check constraints cannot be added in one statement for h2
|
||||
@@ -467,14 +478,15 @@ public class PlatformDdl {
|
||||
buffer.append(ddl).endOfStatement();
|
||||
}
|
||||
} else {
|
||||
buffer.append(addColumnSuffix);
|
||||
buffer.endOfStatement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
|
||||
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
|
||||
.endOfStatement();
|
||||
buffer.append("alter table ").append(tableName).append(" ").append(dropColumn).append(" ").append(columnName)
|
||||
.append(dropColumnSuffix).endOfStatement();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,19 +506,19 @@ public class PlatformDdl {
|
||||
*/
|
||||
public String alterColumnType(String tableName, String columnName, String type) {
|
||||
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false);
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false) + alterColumnSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter a column adding or removing the not null constraint.
|
||||
* <p>
|
||||
* Note that that MySql and SQL Server instead use alterColumnBaseAttributes()
|
||||
* Note that that MySql, SQL Server, and HANA instead use alterColumnBaseAttributes()
|
||||
* </p>
|
||||
*/
|
||||
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
|
||||
|
||||
String suffix = notnull ? columnSetNotnull : columnSetNull;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,17 +534,17 @@ public class PlatformDdl {
|
||||
*/
|
||||
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
|
||||
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
|
||||
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter column setting both the type and not null constraint.
|
||||
* <p>
|
||||
* Used by MySql and SQL Server as these require both column attributes to be set together.
|
||||
* Used by MySql, SQL Server, and HANA as these require both column attributes to be set together.
|
||||
* </p>
|
||||
*/
|
||||
public String alterColumnBaseAttributes(AlterColumn alter) {
|
||||
// by default do nothing, only used by mysql and sql server as they can only
|
||||
// by default do nothing, only used by mysql, sql server, and HANA as they can only
|
||||
// modify the column with the full column definition
|
||||
return null;
|
||||
}
|
||||
@@ -662,6 +674,17 @@ public class PlatformDdl {
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database-specific "create table" command prefix. For HANA this is
|
||||
* either "create column table" or "create row table", for all other databases
|
||||
* it is "create table".
|
||||
*
|
||||
* @return The "create table" command prefix
|
||||
*/
|
||||
public String getCreateTableCommandPrefix() {
|
||||
return createTable;
|
||||
}
|
||||
|
||||
public boolean suppressPrimaryKeyOnPartition() {
|
||||
return false;
|
||||
|
||||
@@ -29,6 +29,8 @@ public class CurrentModel {
|
||||
|
||||
private final boolean platformTypes;
|
||||
|
||||
private final boolean jaxbPresent;
|
||||
|
||||
private ModelContainer model;
|
||||
|
||||
private ChangeSet changeSet;
|
||||
@@ -58,6 +60,7 @@ public class CurrentModel {
|
||||
this.constraintNaming = constraintNaming;
|
||||
this.maxLength = maxLength(server, constraintNaming);
|
||||
this.platformTypes = platformTypes;
|
||||
this.jaxbPresent = server.getServerConfig().getClassLoadConfig().isJavaxJAXBPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,7 +133,9 @@ public class CurrentModel {
|
||||
ddl.append(header).append('\n');
|
||||
}
|
||||
|
||||
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
|
||||
if (jaxbPresent) {
|
||||
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
|
||||
}
|
||||
|
||||
ddl.append(write.apply().getBuffer());
|
||||
ddl.append(write.applyForeignKeys().getBuffer());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.dbmigration.model;
|
||||
|
||||
import io.ebean.annotation.PartitionMode;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns;
|
||||
import io.ebeaninternal.dbmigration.migration.AddColumn;
|
||||
|
||||
@@ -31,14 +31,16 @@ public class MigrationModel {
|
||||
/**
|
||||
* Read all the migrations returning the model with all
|
||||
* the migrations applied in version order.
|
||||
*
|
||||
* @param dbinitMigration If true we don't apply model changes, migration is from scratch.
|
||||
*/
|
||||
public ModelContainer read() {
|
||||
public ModelContainer read(boolean dbinitMigration) {
|
||||
|
||||
readMigrations();
|
||||
readMigrations(dbinitMigration);
|
||||
return model;
|
||||
}
|
||||
|
||||
private void readMigrations() {
|
||||
private void readMigrations(boolean dbinitMigration) {
|
||||
|
||||
// find all the migration xml files
|
||||
File[] xmlFiles = modelDirectory.listFiles(pathname -> pathname.getName().toLowerCase().endsWith(modelSuffix));
|
||||
@@ -53,9 +55,11 @@ public class MigrationModel {
|
||||
// sort into version order before applying
|
||||
Collections.sort(resources);
|
||||
|
||||
for (MigrationResource migrationResource : resources) {
|
||||
logger.debug("read {}", migrationResource);
|
||||
model.apply(migrationResource.read(), migrationResource.getVersion());
|
||||
if (!dbinitMigration) {
|
||||
for (MigrationResource migrationResource : resources) {
|
||||
logger.debug("read {}", migrationResource);
|
||||
model.apply(migrationResource.read(), migrationResource.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
// remember the last version
|
||||
|
||||
@@ -24,9 +24,6 @@ public class ModelBuildElementTable {
|
||||
|
||||
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
|
||||
|
||||
ctx.fkeyBuilder(table)
|
||||
.addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true);
|
||||
|
||||
ctx.addTable(table);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public class CachedBeanDataFromBean {
|
||||
Object v = aPropertiesNonTransient.getValue(bean);
|
||||
aPropertiesNonTransient.setValue(sharableBean, v);
|
||||
}
|
||||
EntityBeanIntercept intercept = sharableBean._ebean_intercept();
|
||||
EntityBeanIntercept intercept = sharableBean._ebean_getIntercept();
|
||||
intercept.setReadOnly(true);
|
||||
intercept.setLoaded();
|
||||
return sharableBean;
|
||||
|
||||
@@ -19,6 +19,7 @@ public class DefaultServerQueryCache extends DefaultServerCache {
|
||||
this.queryCacheEntryValidate = config.getQueryCacheEntryValidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object unwrapEntry(CacheEntry entry) {
|
||||
return ((QueryCacheEntry) entry.getValue()).getValue();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ClusterManager implements ServerLookup {
|
||||
|
||||
public ClusterManager(ContainerConfig config) {
|
||||
ClusterBroadcastFactory factory = createFactory();
|
||||
if (factory != null) {
|
||||
if (factory != null && config.isActive()) {
|
||||
broadcast = factory.create(this, config);
|
||||
} else {
|
||||
broadcast = null;
|
||||
@@ -59,6 +59,7 @@ public class ClusterManager implements ServerLookup {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EbeanServer getServer(String name) {
|
||||
synchronized (monitor) {
|
||||
return serverMap.get(name);
|
||||
|
||||
@@ -11,7 +11,6 @@ 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.transaction.TransactionManager;
|
||||
import io.ebeaninternal.server.util.BindParamsParser;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
@@ -7,6 +7,10 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -341,6 +345,9 @@ public final class BasicTypeConverter implements Serializable {
|
||||
} else if (value instanceof String) {
|
||||
return Timestamp.valueOf((String) value);
|
||||
|
||||
} else if (value instanceof LocalDateTime) {
|
||||
return Timestamp.valueOf((LocalDateTime) value);
|
||||
|
||||
} else if (value instanceof Number) {
|
||||
return new Timestamp(((Number) value).longValue());
|
||||
|
||||
@@ -361,6 +368,9 @@ public final class BasicTypeConverter implements Serializable {
|
||||
} else if (value instanceof String) {
|
||||
return java.sql.Time.valueOf((String) value);
|
||||
|
||||
} else if (value instanceof LocalTime) {
|
||||
return java.sql.Time.valueOf((LocalTime) value);
|
||||
|
||||
} else {
|
||||
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
|
||||
throw new RuntimeException(m);
|
||||
@@ -390,6 +400,9 @@ public final class BasicTypeConverter implements Serializable {
|
||||
} else if (value instanceof Number) {
|
||||
return new java.sql.Date(((Number) value).longValue());
|
||||
|
||||
} else if (value instanceof LocalDate) {
|
||||
return java.sql.Date.valueOf((LocalDate)value);
|
||||
|
||||
} else {
|
||||
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
|
||||
throw new RuntimeException(m);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.ScriptRunner;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebean.migration.runner.ScriptTransform;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URL;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
class DScriptRunner implements ScriptRunner {
|
||||
|
||||
private static final String NEWLINE = "\n";
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
DScriptRunner(SpiEbeanServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String path) {
|
||||
run(path, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String path, Map<String, String> placeholderMap) {
|
||||
run(this.getClass().getResource(path), path, placeholderMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(URL resource) {
|
||||
run(resource, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(URL resource, Map<String, String> placeholderMap) {
|
||||
run(resource, null, placeholderMap);
|
||||
}
|
||||
|
||||
private void run(URL resource, String scriptName, Map<String, String> placeholderMap) {
|
||||
if (resource == null) {
|
||||
throw new IllegalArgumentException("resource is null?");
|
||||
}
|
||||
if (scriptName == null) {
|
||||
scriptName = resource.getFile();
|
||||
}
|
||||
|
||||
String content = content(resource);
|
||||
runScript(content, scriptName, placeholderMap);
|
||||
}
|
||||
|
||||
private String content(URL resource) {
|
||||
if (resource == null) {
|
||||
throw new IllegalArgumentException("resource is null?");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = resource.openStream()) {
|
||||
return readContent(new InputStreamReader(inputStream));
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException("Failed to read script content", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
private void runScript(String content, String scriptName, Map<String, String> placeholderMap) {
|
||||
|
||||
try {
|
||||
if (placeholderMap != null) {
|
||||
content = ScriptTransform.build(null, placeholderMap).transform(content);
|
||||
}
|
||||
|
||||
try (Connection connection = obtainConnection()) {
|
||||
DdlRunner runner = new DdlRunner(false, scriptName);
|
||||
runner.runAll(content, connection);
|
||||
connection.commit();
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to run script", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Connection obtainConnection() {
|
||||
try {
|
||||
return server.getPluginApi().getDataSource().getConnection();
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to obtain connection to run script", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String readContent(Reader reader) throws IOException {
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
try (LineNumberReader lineReader = new LineNumberReader(reader)) {
|
||||
String line;
|
||||
while ((line = lineReader.readLine()) != null) {
|
||||
buf.append(line).append(NEWLINE);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hana.HanaPlatform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
@@ -106,6 +107,9 @@ public class DatabasePlatformFactory {
|
||||
if (dbName.equals("sqlite")) {
|
||||
return new SQLitePlatform();
|
||||
}
|
||||
if (dbName.equals("hana")) {
|
||||
return new HanaPlatform();
|
||||
}
|
||||
|
||||
throw new RuntimeException("database platform " + dbName + " is not known?");
|
||||
}
|
||||
@@ -156,6 +160,8 @@ public class DatabasePlatformFactory {
|
||||
return new DB2Platform();
|
||||
} else if (dbProductName.contains("sql anywhere")) {
|
||||
return new SqlAnywherePlatform();
|
||||
} else if (dbProductName.contains("hdb")) {
|
||||
return new HanaPlatform();
|
||||
}
|
||||
|
||||
// use the standard one
|
||||
|
||||
@@ -7,6 +7,10 @@ import io.ebean.config.TenantMode;
|
||||
import io.ebean.config.UnderscoreNamingConvention;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.datasource.DataSourceAlertFactory;
|
||||
import io.ebean.datasource.DataSourceConfig;
|
||||
import io.ebean.datasource.DataSourceFactory;
|
||||
import io.ebean.datasource.DataSourcePoolListener;
|
||||
import io.ebean.service.SpiContainer;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
@@ -15,10 +19,6 @@ import io.ebeaninternal.server.cluster.ClusterManager;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClassPathSearch;
|
||||
import io.ebeaninternal.server.core.bootup.BootupClasses;
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
import org.avaje.datasource.DataSourceAlertFactory;
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
import org.avaje.datasource.DataSourceFactory;
|
||||
import org.avaje.datasource.DataSourcePoolListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -249,7 +249,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
return null;
|
||||
}
|
||||
|
||||
DataSourceFactory factory = config.service(DataSourceFactory.class);
|
||||
DataSourceFactory factory = DataSourceFactory.get();
|
||||
if (factory == null) {
|
||||
throw new IllegalStateException("No DataSourceFactory service implementation found in class path."
|
||||
+ " Probably missing dependency to avaje-datasource?");
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.ebean.Query;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.ScriptRunner;
|
||||
import io.ebean.SqlQuery;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.SqlUpdate;
|
||||
@@ -185,6 +186,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final DdlGenerator ddlGenerator;
|
||||
|
||||
private final ScriptRunner scriptRunner;
|
||||
|
||||
private final ExpressionFactory expressionFactory;
|
||||
|
||||
private final SpiBackgroundExecutor backgroundExecutor;
|
||||
@@ -291,6 +294,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
this.serverPlugins = config.getPlugins();
|
||||
this.ddlGenerator = new DdlGenerator(this, serverConfig);
|
||||
this.scriptRunner = new DScriptRunner(this);
|
||||
|
||||
configureServerPlugins();
|
||||
|
||||
@@ -370,6 +374,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return databasePlatform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScriptRunner script() {
|
||||
return scriptRunner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DdlHandler createDdlHandler() {
|
||||
return PlatformDdlBuilder.create(databasePlatform).createDdlHandler(serverConfig);
|
||||
@@ -2054,6 +2063,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return persister.executeSqlUpdate(updSql, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow(SpiSqlUpdate sqlUpdate) {
|
||||
return persister.executeSqlUpdateNow(sqlUpdate, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
|
||||
persister.addBatch(sqlUpdate, transaction);
|
||||
@@ -2346,7 +2360,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
Object id = idProperty.getVal(entityBean);
|
||||
if (entityBean._ebean_intercept().isNew() && id != null) {
|
||||
if (entityBean._ebean_getIntercept().isNew() && id != null) {
|
||||
// Primary Key is changeable only on new models - so skip check if we are not
|
||||
// new.
|
||||
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
|
||||
@@ -2376,7 +2390,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
|
||||
ExpressionList<?> exprList = query.where();
|
||||
|
||||
if (!entityBean._ebean_intercept().isNew()) {
|
||||
if (!entityBean._ebean_getIntercept().isNew()) {
|
||||
// if model is not new, exclude ourself.
|
||||
exprList.ne(idProperty.getName(), idProperty.getVal(entityBean));
|
||||
}
|
||||
|
||||
@@ -133,6 +133,15 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow() {
|
||||
if (server != null) {
|
||||
return server.executeNow(this);
|
||||
} else {
|
||||
throw new IllegalStateException("server is null?");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] executeBatch() {
|
||||
if (server == null) {
|
||||
@@ -230,6 +239,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlUpdate setNextParameter(Object value) {
|
||||
setParameter(++addPos, value);
|
||||
return this;
|
||||
|
||||
@@ -87,7 +87,7 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
|
||||
import io.ebeanservice.docstore.api.DocStoreIntegration;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
|
||||
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
|
||||
import org.avaje.datasource.DataSourcePool;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantCatalogProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import org.avaje.datasource.DataSourcePool;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantSchemaProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import org.avaje.datasource.DataSourcePool;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -8,6 +8,9 @@ import io.ebeaninternal.server.persist.BatchPostExecute;
|
||||
import io.ebeaninternal.server.persist.BatchedSqlException;
|
||||
import io.ebeaninternal.server.persist.PersistExecute;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Wraps all the objects used to persist a bean.
|
||||
*/
|
||||
@@ -95,6 +98,13 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
|
||||
return transaction.isBatchThisRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the SQLException into a specific exception given the platform.
|
||||
*/
|
||||
public PersistenceException translateSqlException(SQLException e) {
|
||||
return transaction.translate(e.getMessage(), e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanManager;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
@@ -131,6 +132,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private boolean[] dirtyProperties;
|
||||
|
||||
/**
|
||||
* Imported OneToOne orphan that needs to be deleted.
|
||||
*/
|
||||
private EntityBean orphanBean;
|
||||
|
||||
/**
|
||||
* Flag set when request is added to JDBC batch.
|
||||
*/
|
||||
@@ -167,6 +173,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private int pendingPostUpdateNotify;
|
||||
|
||||
/**
|
||||
* Set to true when post execute has occured (so includes batch flush).
|
||||
*/
|
||||
private boolean postExecute;
|
||||
|
||||
/**
|
||||
* Set to true after many properties have been persisted (so includes element collections).
|
||||
*/
|
||||
private boolean complete;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
|
||||
|
||||
@@ -435,10 +451,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this change should notify cache, listener or doc store.
|
||||
* Return true if this change should notify persist listener or doc store (and keep the request).
|
||||
*/
|
||||
public boolean isNotify() {
|
||||
return notifyCache || isNotifyPersistListener() || isDocStoreNotify();
|
||||
private boolean isNotifyListeners() {
|
||||
return isNotifyPersistListener() || isDocStoreNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -912,7 +928,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
@Override
|
||||
public void postExecute() {
|
||||
|
||||
postExecute = true;
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
@@ -933,7 +949,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
postInsert();
|
||||
}
|
||||
|
||||
addEvent();
|
||||
addPostCommitListeners();
|
||||
notifyCacheOnPostExecute();
|
||||
|
||||
if (isLogSummary()) {
|
||||
logSummary();
|
||||
@@ -1011,14 +1028,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the bean to the TransactionEvent. This will be used by TransactionManager to sync Cache,
|
||||
* Cluster and text indexes.
|
||||
* Add the request to TransactionEvent if there are post commit listeners.
|
||||
*/
|
||||
private void addEvent() {
|
||||
|
||||
private void addPostCommitListeners() {
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
event.add(this);
|
||||
if (event != null && isNotifyListeners()) {
|
||||
event.addListenerNotify(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1081,23 +1096,62 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of its many properties where cascade saved and hence we need to update related
|
||||
* many property caches.
|
||||
* Completed insert or delete request. Do cache notify in non-batched case.
|
||||
*/
|
||||
public void checkUpdatedManysOnly() {
|
||||
public void complete() {
|
||||
notifyCacheOnComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Completed update request handling cases for element collection and where ONLY
|
||||
* many properties were updated.
|
||||
*/
|
||||
public void completeUpdate() {
|
||||
if (!dirty && updatedManys != null) {
|
||||
// set the flag and register for post commit processing if there
|
||||
// is caching or registered listeners
|
||||
if (idValue == null) {
|
||||
this.idValue = beanDescriptor.getId(entityBean);
|
||||
}
|
||||
// not dirty so postExecute() will never be called
|
||||
// set true to trigger cache notify if needed
|
||||
postExecute = true;
|
||||
updatedManysOnly = true;
|
||||
setNotifyCache();
|
||||
addEvent();
|
||||
addPostCommitListeners();
|
||||
}
|
||||
notifyCacheOnComplete();
|
||||
postUpdateNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify cache when using batched persist.
|
||||
*/
|
||||
private void notifyCacheOnPostExecute() {
|
||||
postExecute = true;
|
||||
if (notifyCache && complete) {
|
||||
// add cache notification (on batch persist)
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
notifyCache(event.obtainCacheChangeSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify cache when not use batched persist.
|
||||
*/
|
||||
private void notifyCacheOnComplete() {
|
||||
complete = true;
|
||||
if (notifyCache && postExecute) {
|
||||
// add cache notification (on non-batch persist)
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
notifyCache(event.obtainCacheChangeSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For requests that update document store add this event to either the list
|
||||
* of queue events or list of update events.
|
||||
@@ -1377,4 +1431,25 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an orphan bean that needs to be deleted AFTER the request has persisted.
|
||||
*/
|
||||
public void setImportedOrphanForRemoval(BeanPropertyAssocOne<?> prop) {
|
||||
Object orphan = getOrigValue(prop);
|
||||
if (orphan instanceof EntityBean) {
|
||||
orphanBean = (EntityBean) orphan;
|
||||
}
|
||||
}
|
||||
|
||||
public EntityBean getImportedOrphanForRemoval() {
|
||||
return orphanBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL used to fetch the last inserted id value.
|
||||
*/
|
||||
public String getSelectLastInsertedId() {
|
||||
return beanDescriptor.getSelectLastInsertedId(publish);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,18 +28,23 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
|
||||
private boolean addBatch;
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
private boolean forceNoBatch;
|
||||
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
SpiTransaction t, PersistExecute persistExecute, boolean forceNoBatch) {
|
||||
|
||||
super(server, t, persistExecute, sqlUpdate.getLabel());
|
||||
this.type = Type.UPDATESQL;
|
||||
this.updateSql = sqlUpdate;
|
||||
this.forceNoBatch = forceNoBatch;
|
||||
updateSql.reset();
|
||||
}
|
||||
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
this(server, sqlUpdate, t, persistExecute, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profile(long offset, int flushCount) {
|
||||
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
|
||||
@@ -60,7 +65,7 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest() {
|
||||
return addBatch || super.isBatchThisRequest();
|
||||
return !forceNoBatch && (addBatch || super.isBatchThisRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -74,10 +74,15 @@ public interface Persister {
|
||||
int executeOrmUpdate(Update<?> update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the UpdateSql.
|
||||
* Execute the SqlUpdate (taking into account transaction batch mode).
|
||||
*/
|
||||
int executeSqlUpdate(SqlUpdate update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the SqlUpdate now regardless of transaction batch mode.
|
||||
*/
|
||||
int executeSqlUpdateNow(SpiSqlUpdate update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the CallableSql.
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HanaColumnStoreDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MySqlDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.Oracle10Ddl;
|
||||
@@ -47,6 +48,8 @@ public class PlatformDdlBuilder {
|
||||
case SQLSERVER17:
|
||||
case SQLSERVER:
|
||||
return new SqlServerDdl(platform);
|
||||
case HANA:
|
||||
return new HanaColumnStoreDdl(platform);
|
||||
default:
|
||||
return new PlatformDdl(platform);
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to io.ebean.event.ServletContextListener.
|
||||
* <p>
|
||||
* Listens for webserver server starting and stopping events.
|
||||
* <p>
|
||||
* Register this listener in the web.xml configuration file. This will listen
|
||||
* for startup and shutdown events.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
public class ServletContextListener implements javax.servlet.ServletContextListener {
|
||||
|
||||
/**
|
||||
* The servlet container is stopping.
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent event) {
|
||||
ShutdownManager.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing on startup.
|
||||
*/
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent event) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import org.avaje.datasource.DataSourcePool;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -28,6 +29,24 @@ abstract class AssocOneHelp {
|
||||
property.targetIdBinder.loadIgnore(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the property.
|
||||
*/
|
||||
Object read(DataReader reader) throws SQLException {
|
||||
return property.read(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the property setting value into the bean.
|
||||
*/
|
||||
Object readSet(DataReader reader, EntityBean bean) throws SQLException {
|
||||
Object val = read(reader);
|
||||
if (bean != null) {
|
||||
property.setValue(bean, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the bean.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -9,14 +10,41 @@ import java.sql.SQLException;
|
||||
*/
|
||||
final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
|
||||
public AssocOneHelpEmbedded(BeanPropertyAssocOne<?> property) {
|
||||
AssocOneHelpEmbedded(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
void loadIgnore(DbReadContext ctx) {
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
property.embeddedProps[i].loadIgnore(ctx);
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
property.loadIgnore(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Object readSet(DataReader reader, EntityBean bean) throws SQLException {
|
||||
Object dbVal = read(reader);
|
||||
if (bean != null) {
|
||||
property.setValue(bean, dbVal);
|
||||
}
|
||||
return dbVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
Object read(DataReader reader) throws SQLException {
|
||||
|
||||
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
|
||||
boolean notNull = false;
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
Object value = property.readSet(reader, embeddedBean);
|
||||
if (value != null) {
|
||||
notNull = true;
|
||||
}
|
||||
}
|
||||
if (notNull) {
|
||||
return embeddedBean;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +67,8 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
|
||||
|
||||
boolean notNull = false;
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
Object value = property.embeddedProps[i].readSet(ctx, embeddedBean);
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
Object value = property.readSet(ctx, embeddedBean);
|
||||
if (value != null) {
|
||||
notNull = true;
|
||||
}
|
||||
@@ -55,8 +83,8 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
|
||||
@Override
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
property.embeddedProps[i].appendSelect(ctx, subQuery);
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
property.appendSelect(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,14 @@ import io.ebeaninternal.server.query.SqlJoinType;
|
||||
*/
|
||||
class AssocOneHelpRefExported extends AssocOneHelp {
|
||||
|
||||
public AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
|
||||
private final boolean softDelete;
|
||||
|
||||
private final String softDeletePredicate;
|
||||
|
||||
AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
this.softDelete = property.targetDescriptor.isSoftDelete();
|
||||
this.softDeletePredicate = (softDelete) ? property.targetDescriptor.getSoftDeletePredicate("") : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +34,10 @@ class AssocOneHelpRefExported extends AssocOneHelp {
|
||||
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
String relativePrefix = ctx.getRelativePrefix(property.getName());
|
||||
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
if (softDelete && !ctx.isIncludeSoftDelete()) {
|
||||
property.tableJoin.addJoin(joinType, relativePrefix, ctx, softDeletePredicate);
|
||||
} else {
|
||||
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class AssocOneHelpRefInherit extends AssocOneHelp {
|
||||
// for inheritance hierarchy create the correct type for this row...
|
||||
boolean disableLazyLoading = ctx.isDisableLazyLoading();
|
||||
Object ref = desc.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
|
||||
if (disableLazyLoading) {
|
||||
if (!disableLazyLoading) {
|
||||
ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept());
|
||||
}
|
||||
return ref;
|
||||
|
||||
@@ -42,6 +42,7 @@ abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public Collection underlying(Object value) {
|
||||
if (value instanceof BeanCollection) {
|
||||
|
||||
@@ -39,6 +39,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
|
||||
/**
|
||||
* Create an empty collection of the correct type without a parent bean.
|
||||
*/
|
||||
@Override
|
||||
BeanCollection<T> createEmptyNoParent();
|
||||
|
||||
/**
|
||||
@@ -49,6 +50,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
|
||||
/**
|
||||
* Add a bean to the List Set or Map.
|
||||
*/
|
||||
@Override
|
||||
void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck);
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ public class BeanCollectionUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the details of the collection or map taking care to avoid
|
||||
* Return the details (map entry set) of the collection or map taking care to avoid
|
||||
* unnecessary fetching of the data.
|
||||
*/
|
||||
public static Collection<?> getActualEntries(Object o) {
|
||||
@@ -48,4 +48,32 @@ public class BeanCollectionUtil {
|
||||
}
|
||||
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the details (map values) of the collection or map taking care to avoid
|
||||
* unnecessary fetching of the data.
|
||||
*/
|
||||
public static Collection<?> getActualDetails(Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
}
|
||||
if (o instanceof BeanCollection<?>) {
|
||||
BeanCollection<?> bc = (BeanCollection<?>) o;
|
||||
if (!bc.isPopulated()) {
|
||||
return null;
|
||||
}
|
||||
// For maps this is a collection of Map.Entry, otherwise it
|
||||
// returns a collection of beans
|
||||
return bc.getActualDetails();
|
||||
}
|
||||
|
||||
if (o instanceof Map<?, ?>) {
|
||||
// yes, we want the entrySet (to set the keys)
|
||||
return ((Map<?, ?>) o).values();
|
||||
|
||||
} else if (o instanceof Collection<?>) {
|
||||
return ((Collection<?>) o);
|
||||
}
|
||||
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PartitionMode;
|
||||
import io.ebean.annotation.Formula;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
@@ -61,6 +61,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
|
||||
import io.ebeaninternal.server.el.ElComparator;
|
||||
import io.ebeaninternal.server.el.ElComparatorCompound;
|
||||
import io.ebeaninternal.server.el.ElComparatorNoop;
|
||||
import io.ebeaninternal.server.el.ElComparatorProperty;
|
||||
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
@@ -100,6 +101,7 @@ import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -107,6 +109,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Describes Beans including their deployment information.
|
||||
@@ -179,6 +183,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* getGeneratedKeys is not supported.
|
||||
*/
|
||||
private final String selectLastInsertedId;
|
||||
private final String selectLastInsertedIdDraft;
|
||||
|
||||
private final boolean autoTunable;
|
||||
|
||||
@@ -470,6 +475,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
this.sequenceInitialValue = deploy.getSequenceInitialValue();
|
||||
this.sequenceAllocationSize = deploy.getSequenceAllocationSize();
|
||||
this.selectLastInsertedId = deploy.getSelectLastInsertedId();
|
||||
this.selectLastInsertedIdDraft = deploy.getSelectLastInsertedIdDraft();
|
||||
this.concurrencyMode = deploy.getConcurrencyMode();
|
||||
this.updateChangesOnly = deploy.isUpdateChangesOnly();
|
||||
this.indexDefinitions = deploy.getIndexDefinitions();
|
||||
@@ -492,7 +498,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
|
||||
|
||||
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
|
||||
this.softDelete = (softDeleteProperty != null);
|
||||
// if formula is set, the property is virtual only (there is no column in db) the formula must evaluate to true,
|
||||
// if there is a join to a deleted bean. Example: '@Formula(select = "${ta}.user_id is null")'
|
||||
// this is required to support markAsDelete on beans that may have no FK constraint.
|
||||
this.softDelete = (softDeleteProperty != null && !softDeleteProperty.isFormula());
|
||||
this.idProperty = listHelper.getId();
|
||||
this.versionProperty = listHelper.getVersionProperty();
|
||||
this.unmappedJson = listHelper.getUnmappedJson();
|
||||
@@ -1133,6 +1142,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* Return true if this object is the root level object in its entity
|
||||
* inheritance.
|
||||
*/
|
||||
@Override
|
||||
public boolean isInheritanceRoot() {
|
||||
return inheritInfo == null || inheritInfo.isRoot();
|
||||
}
|
||||
@@ -1829,6 +1839,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Return the IdBinder which is helpful for handling the various types of Id.
|
||||
*/
|
||||
@Override
|
||||
public IdBinder getIdBinder() {
|
||||
return idBinder;
|
||||
}
|
||||
@@ -1937,6 +1948,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Creates a new entity bean without invoking {@link BeanPostConstructListener#postCreate(Object)}
|
||||
*/
|
||||
@Override
|
||||
public EntityBean createEntityBean() {
|
||||
return createEntityBean(false);
|
||||
}
|
||||
@@ -2207,6 +2219,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Put the bean into the persistence context if it is absent.
|
||||
*/
|
||||
@Override
|
||||
public Object contextPutIfAbsent(PersistenceContext pc, Object id, EntityBean localBean) {
|
||||
return pc.putIfAbsent(rootBeanType, id, localBean);
|
||||
}
|
||||
@@ -2431,7 +2444,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
private ElComparator<T> createPropertyComparator(SortByClause.Property sortProp) {
|
||||
|
||||
ElPropertyValue elGetValue = getElGetValue(sortProp.getName());
|
||||
if (elGetValue == null) {
|
||||
logger.error("Sort property [" + sortProp + "] not found in " + beanType + ". Cannot sort.");
|
||||
return new ElComparatorNoop<>();
|
||||
}
|
||||
|
||||
if (elGetValue.isAssocMany()) {
|
||||
logger.error("Sort property [" + sortProp + "] in " + beanType + " is a many-property. Cannot sort.");
|
||||
return new ElComparatorNoop<>();
|
||||
}
|
||||
Boolean nullsHigh = sortProp.getNullsHigh();
|
||||
if (nullsHigh == null) {
|
||||
nullsHigh = Boolean.TRUE;
|
||||
@@ -2633,6 +2654,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return autoTunable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElementType() {
|
||||
return false;
|
||||
}
|
||||
@@ -2641,6 +2663,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* Returns the Inheritance mapping information. This will be null if this type
|
||||
* of bean is not involved in any ORM inheritance mapping.
|
||||
*/
|
||||
@Override
|
||||
public InheritInfo getInheritInfo() {
|
||||
return inheritInfo;
|
||||
}
|
||||
@@ -2809,6 +2832,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Returns true if this bean is based on RawSql.
|
||||
*/
|
||||
@Override
|
||||
public boolean isRawSqlBased() {
|
||||
return EntityType.SQL == entityType;
|
||||
}
|
||||
@@ -2863,6 +2887,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Return the base table to use given the query temporal mode.
|
||||
*/
|
||||
@Override
|
||||
public String getBaseTable(SpiQuery.TemporalMode mode) {
|
||||
switch (mode) {
|
||||
case DRAFT:
|
||||
@@ -2890,6 +2915,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
@@ -2902,10 +2928,25 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return softDeleteProperty.getSoftDeleteDbSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSoftDeletePredicate(String tableAlias) {
|
||||
return softDeleteProperty.getSoftDeleteDbPredicate(tableAlias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsDeleted(EntityBean bean) {
|
||||
if (softDeleteProperty == null) {
|
||||
Object id = getId(bean);
|
||||
logger.info("(Lazy) loading unsuccessful for type:{} id:{} - expecting when bean has been deleted", getName(), id);
|
||||
bean._ebean_getIntercept().setLazyLoadFailure(id);
|
||||
} else {
|
||||
setSoftDeleteValue(bean);
|
||||
bean._ebean_getIntercept().setLoaded();
|
||||
setAllLoaded(bean);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if this entity type is draftable.
|
||||
*/
|
||||
@@ -2974,6 +3015,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* Set the draft to true for this entity bean instance.
|
||||
* This bean is being loaded via asDraft() query.
|
||||
*/
|
||||
@Override
|
||||
public void setDraft(EntityBean entityBean) {
|
||||
if (draft != null) {
|
||||
draft.setValue(entityBean, true);
|
||||
@@ -3027,6 +3069,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Return true if this entity bean has history support.
|
||||
*/
|
||||
@Override
|
||||
public boolean isHistorySupport() {
|
||||
return historySupport;
|
||||
}
|
||||
@@ -3082,8 +3125,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* supported.
|
||||
* </p>
|
||||
*/
|
||||
public String getSelectLastInsertedId() {
|
||||
return selectLastInsertedId;
|
||||
public String getSelectLastInsertedId(boolean publish) {
|
||||
return publish ? selectLastInsertedId : selectLastInsertedIdDraft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean uses a SQL select to fetch the last inserted id value.
|
||||
*/
|
||||
public boolean supportsSelectLastInsertedId() {
|
||||
return selectLastInsertedId != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -3475,4 +3525,29 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
public List<BeanProperty[]> getUniqueProps() {
|
||||
return propertiesUnique;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BeanType<?>> getInheritanceChildren() {
|
||||
if (hasInheritance()) {
|
||||
return getInheritInfo().getChildren()
|
||||
.stream()
|
||||
.map(InheritInfo::desc)
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanType<?> getInheritanceParent() {
|
||||
return getInheritInfo() == null ? null : getInheritInfo().getParent().desc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor) {
|
||||
if (hasInheritance()) {
|
||||
getInheritInfo().visitChildren(info -> visitor.accept(info.desc()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -348,13 +348,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
|
||||
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
|
||||
Collection<?> actualDetails = BeanCollectionUtil.getActualDetails(details);
|
||||
if (actualDetails == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
List<Object> idList = new ArrayList<>(actualDetails.size());
|
||||
for (Object bean : actualDetails) {
|
||||
idList.add(targetDescriptor.getId((EntityBean) bean));
|
||||
@@ -794,7 +793,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
// query caching only
|
||||
return;
|
||||
}
|
||||
|
||||
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
|
||||
if (manyCollections != null) {
|
||||
for (BeanPropertyAssocMany<?> many : manyCollections) {
|
||||
|
||||
@@ -46,6 +46,7 @@ class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
|
||||
return (EntityBean)prototype._ebean_newInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindElementValue(SqlUpdate insert, Object value) {
|
||||
targetDescriptor.bindElementValue(insert, value);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
|
||||
this.scalarType = firstBaseScalarType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindElementValue(SqlUpdate insert, Object value) {
|
||||
insert.setNextParameter(value);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebean.RawSqlBuilder;
|
||||
import io.ebean.annotation.ConstraintMode;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.config.BeanNotEnhancedException;
|
||||
import io.ebean.config.EncryptKey;
|
||||
import io.ebean.config.EncryptKeyManager;
|
||||
import io.ebean.config.NamingConvention;
|
||||
@@ -388,6 +389,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
return asOfTableMap;
|
||||
|
||||
} catch (BeanNotEnhancedException e) {
|
||||
throw e;
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Error in deployment", e);
|
||||
throw e;
|
||||
@@ -1010,7 +1014,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
* into the order_id column on the order_lines table).
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private void makeUnidirectional(DeployBeanPropertyAssocMany<?> oneToMany) {
|
||||
|
||||
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
|
||||
@@ -1387,9 +1390,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
}
|
||||
|
||||
if (IdType.IDENTITY == desc.getIdType()) {
|
||||
// used when getGeneratedKeys is not supported (SQL Server 2000)
|
||||
// used when getGeneratedKeys is not supported (SQL Server 2000, SAP Hana)
|
||||
String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable());
|
||||
desc.setSelectLastInsertedId(selectLastInsertedId);
|
||||
String selectLastInsertedIdDraft = (!desc.isDraftable()) ? selectLastInsertedId : dbIdentity.getSelectLastInsertedId(desc.getDraftTable());
|
||||
desc.setSelectLastInsertedId(selectLastInsertedId, selectLastInsertedIdDraft);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1470,7 +1474,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
if (isPersistentField(prop)) {
|
||||
throw new IllegalStateException(
|
||||
"If you are running in an IDE with enhancement plugin try a Build -> Rebuild Project to recompile and enhance all entity beans. " +
|
||||
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
|
||||
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1553,7 +1557,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
Class<?> beanClass = desc.getBeanType();
|
||||
|
||||
if (!hasEntityBeanInterface(beanClass)) {
|
||||
throw new IllegalStateException("Bean " + beanClass + " is not enhanced?");
|
||||
String msg = "Bean " + beanClass + " is not enhanced? Check packages specified in ebean.mf. If you are running in IDEA or " +
|
||||
"Eclipse check that the enhancement plugin is installed. See https://ebean.io/docs/trouble-shooting#not-enhanced";
|
||||
throw new BeanNotEnhancedException(msg);
|
||||
}
|
||||
|
||||
// the bean already implements EntityBean
|
||||
@@ -1582,7 +1588,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
// ok to stop and treat just the same as Object.class
|
||||
return;
|
||||
}
|
||||
throw new IllegalStateException("Super type " + superclass + " is not enhanced?");
|
||||
throw new BeanNotEnhancedException("Super type " + superclass + " is not enhanced? Check the packages specified in ebean.mf See https://ebean.io/docs/trouble-shooting#not-enhanced");
|
||||
}
|
||||
|
||||
// recursively continue up the inheritance hierarchy
|
||||
@@ -1665,11 +1671,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private ElementHelp elementHelper(ManyType manyType) {
|
||||
switch (manyType) {
|
||||
case LIST: return new ElementHelpList();
|
||||
case SET: return new ElementHelpSet();
|
||||
case MAP: return new ElementHelpMap();
|
||||
case LIST:
|
||||
return new ElementHelpList();
|
||||
case SET:
|
||||
return new ElementHelpSet();
|
||||
case MAP:
|
||||
return new ElementHelpMap();
|
||||
default:
|
||||
throw new IllegalStateException("manyType unexpected "+manyType);
|
||||
throw new IllegalStateException("manyType unexpected " + manyType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.config.BeanNotRegisteredException;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -22,8 +22,8 @@ public class BeanEmbeddedMetaFactory {
|
||||
BeanDescriptor<?> targetDesc = owner.getBeanDescriptor(prop.getTargetType());
|
||||
if (targetDesc == null) {
|
||||
String msg = "Could not find BeanDescriptor for " + prop.getTargetType()
|
||||
+ ". Perhaps the EmbeddedId class is not registered?";
|
||||
throw new PersistenceException(msg);
|
||||
+ ". Perhaps the EmbeddedId class is not registered? See https://ebean.io/docs/trouble-shooting#not-registered";
|
||||
throw new BeanNotRegisteredException(msg);
|
||||
}
|
||||
|
||||
// deployment override information (column names)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.StringParser;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
@@ -226,4 +227,9 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty() {
|
||||
throw new RuntimeException("ElPropertyDeploy only - not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user