diff --git a/composites/ebean-clickhouse/pom.xml b/composites/ebean-clickhouse/pom.xml
index 29758dc5e2..4c8a31f94f 100644
--- a/composites/ebean-clickhouse/pom.xml
+++ b/composites/ebean-clickhouse/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-clickhouse
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-clickhouse
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-cockroach/pom.xml b/composites/ebean-cockroach/pom.xml
index cae0bb35a3..62f0bc6eb7 100644
--- a/composites/ebean-cockroach/pom.xml
+++ b/composites/ebean-cockroach/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-cockroach
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-postgres
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-db2/pom.xml b/composites/ebean-db2/pom.xml
index cece1db865..1bcb7c848f 100644
--- a/composites/ebean-db2/pom.xml
+++ b/composites/ebean-db2/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-db2
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-db2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-h2/pom.xml b/composites/ebean-h2/pom.xml
index be909354c3..61ba67edbb 100644
--- a/composites/ebean-h2/pom.xml
+++ b/composites/ebean-h2/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-h2
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-hana/pom.xml b/composites/ebean-hana/pom.xml
index b16776d69f..6096877158 100644
--- a/composites/ebean-hana/pom.xml
+++ b/composites/ebean-hana/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-hana
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-hana
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-mariadb/pom.xml b/composites/ebean-mariadb/pom.xml
index 07112691b6..3cbb2a996c 100644
--- a/composites/ebean-mariadb/pom.xml
+++ b/composites/ebean-mariadb/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-mariadb
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-mariadb
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-mysql/pom.xml b/composites/ebean-mysql/pom.xml
index 7fa5080abf..f46393a4e5 100644
--- a/composites/ebean-mysql/pom.xml
+++ b/composites/ebean-mysql/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-mysql
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-mysql
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-nuodb/pom.xml b/composites/ebean-nuodb/pom.xml
index 01d6fb3ee7..59d292ba94 100644
--- a/composites/ebean-nuodb/pom.xml
+++ b/composites/ebean-nuodb/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-nuodb
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-nuodb
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-oracle/pom.xml b/composites/ebean-oracle/pom.xml
index a6451554f6..01ddd5d75b 100644
--- a/composites/ebean-oracle/pom.xml
+++ b/composites/ebean-oracle/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-oracle
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,14 +35,18 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-oracle
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ ebean-parent-13.6.4-FOC1
+
diff --git a/composites/ebean-postgres/pom.xml b/composites/ebean-postgres/pom.xml
index e65fa5477e..52219c13cf 100644
--- a/composites/ebean-postgres/pom.xml
+++ b/composites/ebean-postgres/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-postgres
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,13 +35,13 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-postgres
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/composites/ebean-sqlite/pom.xml b/composites/ebean-sqlite/pom.xml
index 823d3d3879..03c0f40b4d 100644
--- a/composites/ebean-sqlite/pom.xml
+++ b/composites/ebean-sqlite/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-sqlite
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,13 +35,13 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-sqlite
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/composites/ebean-sqlserver/pom.xml b/composites/ebean-sqlserver/pom.xml
index a4702098de..f784345350 100644
--- a/composites/ebean-sqlserver/pom.xml
+++ b/composites/ebean-sqlserver/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-sqlserver
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,13 +35,13 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-sqlserver
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/composites/ebean-yugabyte/pom.xml b/composites/ebean-yugabyte/pom.xml
index 5222eed93f..0f0709080d 100644
--- a/composites/ebean-yugabyte/pom.xml
+++ b/composites/ebean-yugabyte/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-yugabyte
@@ -16,13 +16,13 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -35,13 +35,13 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-postgres
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/composites/ebean/pom.xml b/composites/ebean/pom.xml
index f524e247b8..dc3903cd76 100644
--- a/composites/ebean/pom.xml
+++ b/composites/ebean/pom.xml
@@ -4,7 +4,7 @@
compositesio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean (all platforms)
@@ -16,31 +16,31 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-joda-time
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-jackson-jsonnode
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-jackson-mapper
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -53,13 +53,13 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-all
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/composites/pom.xml b/composites/pom.xml
index 29c05f3ca5..f1508a7d0e 100644
--- a/composites/pom.xml
+++ b/composites/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTcomposites
diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml
index 0770152d5f..2111b887ba 100644
--- a/ebean-api/pom.xml
+++ b/ebean-api/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean api
diff --git a/ebean-api/src/main/java/io/ebean/DB.java b/ebean-api/src/main/java/io/ebean/DB.java
index f20bcb740c..881b8cda9b 100644
--- a/ebean-api/src/main/java/io/ebean/DB.java
+++ b/ebean-api/src/main/java/io/ebean/DB.java
@@ -447,6 +447,19 @@ public static int saveAll(Collection> beans) throws OptimisticLockException {
public static int saveAll(Object... beans) throws OptimisticLockException {
return getDefault().saveAll(beans);
}
+
+ /**
+ * This will visit all beans in the persist graph on a given object
+ * start. It will call the visitor for each dirty bean that would
+ * be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can
+ * use this method to implement custom validations.
+ *
+ * @param start could be a bean, a list of beans or a map of beans.
+ * @param visitor the visitor
+ */
+ public static void visitSave(Object start, PersistVisitor visitor) {
+ getDefault().visitSave(start, visitor);
+ }
/**
* This method checks the uniqueness of a bean. I.e. if the save will work. It will return the
diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java
index 6dd0ab3cfa..6a8cc2d82e 100644
--- a/ebean-api/src/main/java/io/ebean/Database.java
+++ b/ebean-api/src/main/java/io/ebean/Database.java
@@ -843,6 +843,17 @@ public interface Database {
*/
int saveAll(Object... beans) throws OptimisticLockException;
+ /**
+ * This will visit all beans in the persist graph on a given object
+ * start. It will call the visitor for each dirty bean that would
+ * be saved with {@link #save(Object)} or {@link #saveAll(Collection)}. You can
+ * use this method to implement custom validations.
+ *
+ * @param start could be a bean, a list of beans or a map of beans.
+ * @param visitor the visitor
+ */
+ void visitSave(Object start, PersistVisitor visitor);
+
/**
* Delete the bean.
*
@@ -1509,4 +1520,8 @@ public interface Database {
*/
void truncate(Class>... beanTypes);
+ /**
+ * RunDdl manually. This can be used if 'db.ddl.run=false' is set and you plan to run DDL manually.
+ */
+ void runDdl();
}
diff --git a/ebean-api/src/main/java/io/ebean/PersistVisitor.java b/ebean-api/src/main/java/io/ebean/PersistVisitor.java
new file mode 100644
index 0000000000..b5f1398d09
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/PersistVisitor.java
@@ -0,0 +1,29 @@
+package io.ebean;
+
+import java.util.Collection;
+import java.util.Map;
+
+import io.ebean.bean.EntityBean;
+import io.ebean.plugin.Property;
+
+@FunctionalInterface
+public interface PersistVisitor {
+
+ PersistVisitor visitBean(EntityBean bean);
+
+ default PersistVisitor visitProperty(Property prop) {
+ return this;
+ };
+
+ default PersistVisitor visitCollection(Collection> collection) {
+ return this;
+ };
+
+ default PersistVisitor visitMap(Map, ?> map) {
+ return this;
+ };
+
+ default void visitEnd() {
+ }
+
+}
diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/EntityImplements.java b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityImplements.java
new file mode 100644
index 0000000000..ee312bb72c
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityImplements.java
@@ -0,0 +1,21 @@
+package io.ebean.annotation.ext;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to define a list of interface for which this entity is target for.
+ * @author Roland Praml, FOCONIS AG
+ */
+@Documented
+@Target({ FIELD, TYPE })
+@Retention(RUNTIME)
+public @interface EntityImplements {
+
+ Class>[] value();
+
+}
diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/EntityOverride.java b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityOverride.java
new file mode 100644
index 0000000000..2c166812a3
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/annotation/ext/EntityOverride.java
@@ -0,0 +1,24 @@
+package io.ebean.annotation.ext;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to define that an entity overrides the parent entity.
+ * @author Roland Praml, FOCONIS AG
+ */
+@Documented
+@Target({ FIELD, TYPE })
+@Retention(RUNTIME)
+public @interface EntityOverride {
+
+ /**
+ * The priority of the statement. Lower priority wins.
+ */
+ int priority() default 0;
+
+}
diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/IntersectionFactory.java b/ebean-api/src/main/java/io/ebean/annotation/ext/IntersectionFactory.java
new file mode 100644
index 0000000000..5749549fb9
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/annotation/ext/IntersectionFactory.java
@@ -0,0 +1,29 @@
+package io.ebean.annotation.ext;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to define a factory for an intersection model. This class MUST have a constructor or factory method with two parameters that accepts parent and property type.
+ * @author Roland Praml, FOCONIS AG
+ */
+@Documented
+@Target({ FIELD, TYPE })
+@Retention(RUNTIME)
+public @interface IntersectionFactory {
+
+ /**
+ * The intersection model class.
+ */
+ Class value();
+
+ /**
+ * An optional factory method.
+ */
+ String factoryMethod() default "";
+}
diff --git a/ebean-api/src/main/java/io/ebean/annotation/ext/package.html b/ebean-api/src/main/java/io/ebean/annotation/ext/package.html
new file mode 100644
index 0000000000..9505c002ca
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/annotation/ext/package.html
@@ -0,0 +1,8 @@
+
+
+ Ebean Annotations
+
+
+This classes will be moved later to the ebean-annotation module.
+
+
diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
index ca778d02ca..20c434d720 100644
--- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
+++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
@@ -16,6 +16,7 @@
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
+import io.ebean.plugin.CustomDeployParser;
import io.ebean.meta.MetricNamingMatch;
import io.ebean.util.StringHelper;
@@ -400,6 +401,8 @@ public class DatabaseConfig {
*/
private Clock clock = Clock.systemUTC();
+ private TempFileProvider tempFileProvider = new WeakRefTempFileProvider();
+
private List idGenerators = new ArrayList<>();
private List findControllers = new ArrayList<>();
private List persistControllers = new ArrayList<>();
@@ -409,6 +412,7 @@ public class DatabaseConfig {
private List queryAdapters = new ArrayList<>();
private final List bulkTableEventListeners = new ArrayList<>();
private final List configStartupListeners = new ArrayList<>();
+ private final List customDeployParsers = new ArrayList<>();
/**
* By default inserts are included in the change log.
@@ -570,6 +574,14 @@ public void setClock(final Clock clock) {
this.clock = clock;
}
+ public TempFileProvider getTempFileProvider() {
+ return tempFileProvider;
+ }
+
+ public void setTempFileProvider(final TempFileProvider tempFileProvider) {
+ this.tempFileProvider = tempFileProvider;
+ }
+
/**
* Return the slow query time in millis.
*/
@@ -615,7 +627,7 @@ public boolean isDefaultOrderById() {
}
/**
- * Put a service object into configuration such that it can be passed to a plugin.
+ * Put a service object into configuration such that it can be used by ebean or a plugin.
*
* For example, put IgniteConfiguration in to be passed to the Ignite plugin.
*/
@@ -623,6 +635,28 @@ public void putServiceObject(String key, Object configObject) {
serviceObject.put(key, configObject);
}
+ /**
+ * Put a service object into configuration such that it can be used by ebean or a plugin.
+ *
+ * For example, put IgniteConfiguration in to be passed to the Ignite plugin.
+ * You can also override some SPI objects that should be used for that Database. Currently, the following
+ * objects are possible.
+ *
+ *
DataSourceAlertFactory (e.g. add different alert factories for different ebean instances)
+ *
DocStoreFactory
+ *
XmapService
+ *
SpiLoggerFactory (e.g. add custom logger for a certain ebean instance)
+ *
AutoTuneServiceProvider
+ *
SpiProfileHandler
+ *
SlowQueryListener (e.g. add custom query listener for a certain ebean instance)
+ *
ServerCacheNotifyPlugin
+ *
SpiDdlGenneratorProvider
+ *
+ */
+ public void putServiceObject(Class iface, T configObject) {
+ serviceObject.put(serviceObjectKey(iface), configObject);
+ }
+
/**
* Return the service object given the key.
*/
@@ -631,7 +665,7 @@ public Object getServiceObject(String key) {
}
/**
- * Put a service object into configuration such that it can be passed to a plugin.
+ * Put a service object into configuration such that it can be used by ebean or a plugin.
*
*
{@code
*
@@ -656,7 +690,7 @@ private String serviceObjectKey(Class> cls) {
}
/**
- * Used by plugins to obtain service objects.
+ * Used by ebean or plugins to obtain service objects.
*
*
{@code
*
@@ -2691,6 +2725,17 @@ public List getServerConfigStartupListeners() {
return configStartupListeners;
}
+ /**
+ * Add a CustomDeployParser.
+ */
+ public void addCustomDeployParser(CustomDeployParser customDeployParser) {
+ customDeployParsers.add(customDeployParser);
+ }
+
+ public List getCustomDeployParsers() {
+ return customDeployParsers;
+ }
+
/**
* Register all the BeanPersistListener instances.
*
diff --git a/ebean-api/src/main/java/io/ebean/config/DeleteOnShutdownTempFileProvider.java b/ebean-api/src/main/java/io/ebean/config/DeleteOnShutdownTempFileProvider.java
new file mode 100644
index 0000000000..ad257c98ee
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/config/DeleteOnShutdownTempFileProvider.java
@@ -0,0 +1,68 @@
+package io.ebean.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TempFileProvider implementation, which deletes all temp files on shutdown.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+public class DeleteOnShutdownTempFileProvider implements TempFileProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(DeleteOnShutdownTempFileProvider.class);
+
+ List tempFiles = new ArrayList<>();
+ private final String prefix;
+ private final String suffix;
+ private final File directory;
+
+ /**
+ * Creates the TempFileProvider with default prefix "db-".
+ */
+ public DeleteOnShutdownTempFileProvider() {
+ this("db-", null, null);
+ }
+
+ /**
+ * Creates the TempFileProvider.
+ */
+ public DeleteOnShutdownTempFileProvider(String prefix, String suffix, File directory) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.directory = directory;
+ }
+
+ @Override
+ public File createTempFile() throws IOException {
+ File file = File.createTempFile(prefix, suffix, directory);
+ synchronized (tempFiles) {
+ tempFiles.add(file.getAbsolutePath());
+ }
+ return file;
+ }
+
+ /**
+ * Deletes all created files on shutdown.
+ */
+ @Override
+ public void shutdown() {
+ synchronized (tempFiles) {
+ for (String path : tempFiles) {
+ if (new File(path).delete()) {
+ logger.trace("deleted {}", path);
+ } else {
+ logger.warn("could not delete {}", path);
+ }
+ }
+ tempFiles.clear();
+ }
+ }
+
+}
diff --git a/ebean-api/src/main/java/io/ebean/config/TempFileProvider.java b/ebean-api/src/main/java/io/ebean/config/TempFileProvider.java
new file mode 100644
index 0000000000..4658b46c28
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/config/TempFileProvider.java
@@ -0,0 +1,23 @@
+package io.ebean.config;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Creates a temp file for the ScalarTypeFile datatype.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+public interface TempFileProvider {
+
+ /**
+ * Creates a tempFile.
+ */
+ File createTempFile() throws IOException;
+
+ /**
+ * Shutdown the tempFileProvider.
+ */
+ void shutdown();
+}
diff --git a/ebean-api/src/main/java/io/ebean/config/WeakRefTempFileProvider.java b/ebean-api/src/main/java/io/ebean/config/WeakRefTempFileProvider.java
new file mode 100644
index 0000000000..aa3ae82905
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/config/WeakRefTempFileProvider.java
@@ -0,0 +1,145 @@
+package io.ebean.config;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * WeakRefTempFileProvider will delete the tempFile if all references to the returned File
+ * object are collected by the garbage collection.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+public class WeakRefTempFileProvider implements TempFileProvider {
+
+ private static final Logger logger = LoggerFactory.getLogger(WeakRefTempFileProvider.class);
+
+ private final ReferenceQueue tempFiles = new ReferenceQueue<>();
+
+ private WeakFileReference root;
+
+ private final String prefix;
+ private final String suffix;
+ private final File directory;
+
+ /**
+ * We hold a linkedList of weak references. So we can remove stale files in O(1)
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+ private static class WeakFileReference extends WeakReference {
+
+ String path;
+ WeakFileReference prev;
+ WeakFileReference next;
+
+ WeakFileReference(File referent, ReferenceQueue super File> q) {
+ super(referent, q);
+ path = referent.getAbsolutePath();
+ }
+
+ boolean delete(boolean shutdown) {
+ File file = new File(path);
+ if (!file.exists()) {
+ logger.trace("already deleted {}", path);
+ return true;
+ } else if (file.delete()) {
+ logger.trace("deleted {}", path);
+ return true;
+ } else {
+ if (shutdown) {
+ logger.warn("could not delete {}", path);
+ } else {
+ logger.info("could not delete {} - will delete on shutdown", path);
+ }
+ return false;
+ }
+ }
+ }
+
+
+ /**
+ * Creates the TempFileProvider with default prefix "db-".
+ */
+ public WeakRefTempFileProvider() {
+ this("db-", null, null);
+ }
+
+ /**
+ * Creates the TempFileProvider.
+ */
+ public WeakRefTempFileProvider(String prefix, String suffix, File directory) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.directory = directory;
+ }
+
+ @Override
+ public File createTempFile() throws IOException {
+ File tempFile = File.createTempFile(prefix, suffix, directory);
+ logger.trace("createTempFile: {}", tempFile);
+ synchronized (this) {
+ add(new WeakFileReference(tempFile, tempFiles));
+ }
+ return tempFile;
+ }
+
+ /**
+ * Will delete stale files.
+ * This is public to use in tests.
+ */
+ public void deleteStaleTempFiles() {
+ synchronized (this) {
+ deleteStaleTempFilesInternal();
+ }
+ }
+
+ private void deleteStaleTempFilesInternal() {
+ WeakFileReference ref;
+ while ((ref = (WeakFileReference) tempFiles.poll()) != null) {
+ if (ref.delete(false)) {
+ remove(ref); // remove from linkedList only, if delete was successful.
+ }
+ }
+ }
+
+ private void add(WeakFileReference ref) {
+ deleteStaleTempFilesInternal();
+
+ if (root == null) {
+ root = ref;
+ } else {
+ ref.next = root;
+ root.prev = ref;
+ root = ref;
+ }
+ }
+
+ private void remove(WeakFileReference ref) {
+ if (ref.next != null) {
+ ref.next.prev = ref.prev;
+ }
+ if (ref.prev != null) {
+ ref.prev.next = ref.next;
+ } else {
+ root = ref.next;
+ }
+ }
+
+ /**
+ * Deletes all created files on shutdown.
+ */
+ @Override
+ public void shutdown() {
+ while (root != null) {
+ root.delete(true);
+ root = root.next;
+ }
+ }
+
+}
diff --git a/ebean-api/src/main/java/io/ebean/plugin/CustomDeployParser.java b/ebean-api/src/main/java/io/ebean/plugin/CustomDeployParser.java
new file mode 100644
index 0000000000..4c854f2f1e
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/plugin/CustomDeployParser.java
@@ -0,0 +1,15 @@
+package io.ebean.plugin;
+
+import io.ebean.config.dbplatform.DatabasePlatform;
+
+/**
+ * Fired after all beans are parsed. You may implement own parsers to handle custom annotations.
+ * (See test case for example)
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+@FunctionalInterface
+public interface CustomDeployParser {
+
+ void parse(DeployBeanDescriptorMeta descriptor, DatabasePlatform databasePlatform);
+}
diff --git a/ebean-api/src/main/java/io/ebean/plugin/DeployBeanDescriptorMeta.java b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanDescriptorMeta.java
new file mode 100644
index 0000000000..4347a5057c
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanDescriptorMeta.java
@@ -0,0 +1,36 @@
+package io.ebean.plugin;
+
+import java.util.Collection;
+
+/**
+ * General deployment information. This is used in {@link CustomDeployParser}.
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+public interface DeployBeanDescriptorMeta {
+
+ /**
+ * Return a collection of all BeanProperty deployment information.
+ */
+ public Collection extends DeployBeanPropertyMeta> propertiesAll();
+
+ /**
+ * Get a BeanProperty by its name.
+ */
+ public DeployBeanPropertyMeta getBeanProperty(String secondaryBeanName);
+
+ /**
+ * Return the DeployBeanDescriptorMeta for the given bean class.
+ */
+ public DeployBeanDescriptorMeta getDeployBeanDescriptorMeta(Class> propertyType);
+
+ /**
+ * Returns the discriminator column, if any.
+ * @return
+ */
+ public String getDiscriminatorColumn();
+
+ public String getBaseTable();
+
+ DeployBeanPropertyMeta idProperty();
+}
diff --git a/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyAssocMeta.java b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyAssocMeta.java
new file mode 100644
index 0000000000..d7d1d637a3
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyAssocMeta.java
@@ -0,0 +1,23 @@
+package io.ebean.plugin;
+
+public interface DeployBeanPropertyAssocMeta extends DeployBeanPropertyMeta {
+
+ /**
+ * Return the mappedBy deployment attribute.
+ *
+ * This is the name of the property in the 'detail' bean that maps back to
+ * this 'master' bean.
+ *
+ */
+ String getMappedBy();
+
+ /**
+ * Return the base table for this association.
+ *
+ * This has the table name which is used to determine the relationship for
+ * this association.
+ *
+ */
+ String getBaseTable();
+
+}
diff --git a/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyMeta.java b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyMeta.java
new file mode 100644
index 0000000000..4ba2d3e2a1
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/plugin/DeployBeanPropertyMeta.java
@@ -0,0 +1,37 @@
+package io.ebean.plugin;
+
+import java.lang.reflect.Field;
+
+public interface DeployBeanPropertyMeta {
+
+ /**
+ * Return the name of the property.
+ */
+ String getName();
+
+ /**
+ * The database column name this is mapped to.
+ */
+ String getDbColumn();
+
+ /**
+ * Return the bean Field associated with this property.
+ */
+ Field getField();
+
+ /**
+ * The property is based on a formula.
+ */
+ void setSqlFormula(String sqlSelect, String sqlJoin);
+
+ /**
+ * Return the bean type.
+ */
+ Class> getOwningType();
+
+ /**
+ * Return the property type.
+ */
+ Class> getPropertyType();
+
+}
diff --git a/ebean-api/src/main/java/module-info.java b/ebean-api/src/main/java/module-info.java
index a8fb4ea387..9349bd8882 100644
--- a/ebean-api/src/main/java/module-info.java
+++ b/ebean-api/src/main/java/module-info.java
@@ -41,5 +41,5 @@
exports io.ebean.text;
exports io.ebean.text.json;
exports io.ebean.util;
-
+ exports io.ebean.annotation.ext;
}
diff --git a/ebean-api/src/test/java/io/ebean/TestWeakRefTempFileProvider.java b/ebean-api/src/test/java/io/ebean/TestWeakRefTempFileProvider.java
new file mode 100644
index 0000000000..dc69cc6876
--- /dev/null
+++ b/ebean-api/src/test/java/io/ebean/TestWeakRefTempFileProvider.java
@@ -0,0 +1,171 @@
+package io.ebean;
+
+
+import io.ebean.config.WeakRefTempFileProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.nio.channels.FileLock;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for the WeakRefTempFileProvider. (Note: this test relies on an aggressive garbage collection.
+ * if GC implementation will change, the test may fail)
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+public class TestWeakRefTempFileProvider {
+
+ WeakRefTempFileProvider prov = new WeakRefTempFileProvider();
+
+ @AfterEach
+ public void shutdown() {
+ prov.shutdown();
+ }
+
+ /**
+ * Run the garbage collection and delete stale files.
+ */
+ private void gc() throws InterruptedException {
+ System.gc();
+ Thread.sleep(100);
+ prov.deleteStaleTempFiles();
+ }
+
+ @Test
+ public void testStaleEntries() throws Exception {
+ File tempFile = prov.createTempFile();
+ String fileName = tempFile.getAbsolutePath();
+
+ gc();
+
+ assertThat(new File(fileName)).exists();
+
+ tempFile = null; // give up reference
+
+ gc();
+
+ assertThat(new File(fileName)).doesNotExist();
+
+
+ }
+
+ @Test
+ public void testLinkedListForward() throws Exception {
+ File tempFile1 = prov.createTempFile();
+ String fileName1 = tempFile1.getAbsolutePath();
+ File tempFile2 = prov.createTempFile();
+ String fileName2 = tempFile2.getAbsolutePath();
+ File tempFile3 = prov.createTempFile();
+ String fileName3 = tempFile3.getAbsolutePath();
+
+ assertThat(new File(fileName1)).exists();
+ assertThat(new File(fileName2)).exists();
+ assertThat(new File(fileName3)).exists();
+
+ gc();
+
+ // give up first ref
+ tempFile1 = null;
+
+ gc();
+
+ assertThat(new File(fileName1)).doesNotExist();
+ assertThat(new File(fileName2)).exists();
+ assertThat(new File(fileName3)).exists();
+
+ // give up second ref
+ tempFile2 = null;
+
+ gc();
+
+ assertThat(new File(fileName1)).doesNotExist();
+ assertThat(new File(fileName2)).doesNotExist();
+ assertThat(new File(fileName3)).exists();
+
+ // give up third ref
+ tempFile3 = null;
+
+ gc();
+
+ assertThat(new File(fileName1)).doesNotExist();
+ assertThat(new File(fileName2)).doesNotExist();
+ assertThat(new File(fileName3)).doesNotExist();
+
+ }
+
+
+ @Test
+ public void testLinkedListReverse() throws Exception {
+ File tempFile1 = prov.createTempFile();
+ String fileName1 = tempFile1.getAbsolutePath();
+ File tempFile2 = prov.createTempFile();
+ String fileName2 = tempFile2.getAbsolutePath();
+ File tempFile3 = prov.createTempFile();
+ String fileName3 = tempFile3.getAbsolutePath();
+
+ assertThat(new File(fileName1)).exists();
+ assertThat(new File(fileName2)).exists();
+ assertThat(new File(fileName3)).exists();
+
+ gc();
+
+ // give up third ref
+ tempFile3 = null;
+
+ gc();
+
+ assertThat(new File(fileName1)).exists();
+ assertThat(new File(fileName2)).exists();
+ assertThat(new File(fileName3)).doesNotExist();
+
+ // give up second ref
+ tempFile2 = null;
+
+ gc();
+
+ assertThat(new File(fileName1)).exists();
+ assertThat(new File(fileName2)).doesNotExist();
+ assertThat(new File(fileName3)).doesNotExist();
+
+ // give up first ref
+ tempFile1 = null;
+
+ gc();
+
+ assertThat(new File(fileName1)).doesNotExist();
+ assertThat(new File(fileName2)).doesNotExist();
+ assertThat(new File(fileName3)).doesNotExist();
+
+ }
+
+ @Test
+ @Disabled("Runs on Windows only")
+ public void testFileLocked() throws Exception {
+ File tempFile = prov.createTempFile();
+ String fileName = tempFile.getAbsolutePath();
+
+ try (FileOutputStream os = new FileOutputStream(fileName)) {
+ FileLock lock = os.getChannel().lock();
+ try {
+ os.write(42);
+
+ tempFile = null;
+ gc();
+ } finally {
+ lock.release();
+ }
+
+ }
+
+ assertThat(new File(fileName)).exists();
+
+ prov.shutdown();
+
+ assertThat(new File(fileName)).doesNotExist();
+ }
+}
diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml
index 90d0597124..fde3907e39 100644
--- a/ebean-autotune/pom.xml
+++ b/ebean-autotune/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -13,8 +13,8 @@
- scm:git:git@github.com:ebean-orm/ebean.git
- HEAD
+ scm:git:git@github.com:FOCONIS/ebean.git
+ ebean-parent-13.6.4-FOC1ebean autotune
@@ -26,7 +26,7 @@
io.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
@@ -55,7 +55,7 @@
io.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml
index 618e44ba02..dfa25d283a 100644
--- a/ebean-bom/pom.xml
+++ b/ebean-bom/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean bom
@@ -89,106 +89,106 @@
io.ebeanebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core-type
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-joda-time
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-jackson-jsonnode
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-jackson-mapper
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-ddl-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-externalmapping-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-externalmapping-xml
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-autotune
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanquerybean-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeankotlin-querybean-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-postgis
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-redis
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml
index 866d3ec569..8b5a7b6eee 100644
--- a/ebean-core-type/pom.xml
+++ b/ebean-core-type/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-core-type
@@ -16,7 +16,7 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml
index ac73b3c697..86a115b5d4 100644
--- a/ebean-core/pom.xml
+++ b/ebean-core/pom.xml
@@ -3,7 +3,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-core
@@ -14,15 +14,15 @@
https://ebean.io/
- scm:git:git@github.com:ebean-orm/ebean.git
- HEAD
+ scm:git:git@github.com:FOCONIS/ebean.git
+ ebean-parent-13.6.4-FOC1io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -37,6 +37,12 @@
7.1
+
+ io.ebean
+ ebean-api
+ 13.10.1-FOC4-SNAPSHOT
+
+
io.ebeanebean-migration-auto
@@ -46,13 +52,13 @@
io.ebeanebean-core-type
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-externalmapping-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -143,21 +149,21 @@
io.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-platform-postgres
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-platform-sqlserver
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java
index b3c4858139..d821e75b84 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiDdlGenerator.java
@@ -12,4 +12,9 @@ public interface SpiDdlGenerator {
*/
void execute(boolean online);
+ /**
+ * Run DDL manually. This can be used to initialize multi tenant environments or if you plan not to run
+ * DDL on startup
+ */
+ void runDdl();
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java
index f15ab82ffc..ddafc8607e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultContainer.java
@@ -168,6 +168,7 @@ private BootupClasses bootupClasses(DatabaseConfig config) {
bootup.addPersistListeners(config.getPersistListeners());
bootup.addQueryAdapters(config.getQueryAdapters());
bootup.addServerConfigStartup(config.getServerConfigStartupListeners());
+ bootup.addCustomDeployParser(config.getCustomDeployParsers());
bootup.addChangeLogInstances(config);
bootup.runServerConfigStartup(config);
return bootup;
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index 2c604ed988..4f82437f7c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -79,6 +79,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final String serverName;
private final DatabasePlatform databasePlatform;
private final TransactionManager transactionManager;
+ private final TempFileProvider tempFileProvider;
private final QueryPlanManager queryPlanManager;
private final ExtraMetrics extraMetrics;
private final DataTimeZone dataTimeZone;
@@ -159,6 +160,7 @@ public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming());
this.serverPlugins = config.getPlugins();
this.ddlGenerator = config.initDdlGenerator(this);
+ this.tempFileProvider = config.getConfig().getTempFileProvider();
this.scriptRunner = new DScriptRunner(this);
configureServerPlugins();
@@ -307,7 +309,10 @@ public void initialise() {
*/
public void start() {
if (config.isRunMigration() && TenantMode.DB != config.getTenantMode()) {
- final AutoMigrationRunner migrationRunner = ServiceUtil.service(AutoMigrationRunner.class);
+ AutoMigrationRunner migrationRunner = config.getServiceObject(AutoMigrationRunner.class);
+ if (migrationRunner == null) {
+ migrationRunner = ServiceUtil.service(AutoMigrationRunner.class);
+ }
if (migrationRunner == null) {
throw new IllegalStateException("No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?");
}
@@ -388,6 +393,8 @@ private void shutdownInternal(boolean shutdownDataSource, boolean deregisterDriv
backgroundExecutor.shutdown();
// shutdown DataSource (if its an Ebean one)
transactionManager.shutdown(shutdownDataSource, deregisterDriver);
+
+ tempFileProvider.shutdown();
dumpMetrics();
shutdown = true;
if (shutdownDataSource) {
@@ -598,7 +605,11 @@ public void clearQueryStatistics() {
*/
@Override
public T createEntityBean(Class type) {
- return descriptor(type).createBean();
+ final BeanDescriptor desc = descriptor(type);
+ if (desc == null) {
+ throw new IllegalArgumentException("No bean type " + type.getName() + " registered");
+ }
+ return desc.createBean();
}
/**
@@ -1581,6 +1592,11 @@ public void save(Object bean, @Nullable Transaction transaction) {
persister.save(checkEntityBean(bean), transaction);
}
+ @Override
+ public void visitSave(Object start, PersistVisitor visitor) {
+ new VisitHandler(descriptorManager).visit(start, visitor);
+ }
+
@Override
public void markAsDirty(Object bean) {
if (!(bean instanceof EntityBean)) {
@@ -2249,4 +2265,9 @@ List queryPlanInit(QueryPlanInit initRequest) {
List queryPlanCollectNow(QueryPlanRequest request) {
return queryPlanManager.collect(request);
}
+
+ @Override
+ public void runDdl() {
+ ddlGenerator.runDdl();
+ }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InitDataSource.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InitDataSource.java
index 3eb6dfe3a0..7dddf0d124 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InitDataSource.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InitDataSource.java
@@ -117,7 +117,10 @@ private DataSource create(DataSourceConfig dsConfig, boolean readOnly) {
* Attach DataSourceAlert via service loader if present.
*/
private void attachAlert(DataSourceConfig dsConfig) {
- DataSourceAlertFactory alertFactory = ServiceUtil.service(DataSourceAlertFactory.class);
+ DataSourceAlertFactory alertFactory = config.getServiceObject(DataSourceAlertFactory.class);
+ if (alertFactory == null) {
+ alertFactory = ServiceUtil.service(DataSourceAlertFactory.class);
+ }
if (alertFactory != null) {
dsConfig.setAlert(alertFactory.createAlert());
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
index bce1339783..b31b6ed44a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
@@ -4,10 +4,7 @@
import io.ebean.ExpressionFactory;
import io.ebean.annotation.Platform;
import io.ebean.cache.*;
-import io.ebean.config.DatabaseConfig;
-import io.ebean.config.ExternalTransactionManager;
-import io.ebean.config.ProfilingConfig;
-import io.ebean.config.SlowQueryListener;
+import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.event.changelog.ChangeLogListener;
@@ -37,6 +34,7 @@
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
+import io.ebeaninternal.server.json.DJsonContext;
import io.ebeaninternal.server.logger.DLogManager;
import io.ebeaninternal.server.logger.DLoggerFactory;
import io.ebeaninternal.server.persist.Binder;
@@ -46,7 +44,6 @@
import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
-import io.ebeaninternal.server.json.DJsonContext;
import io.ebeaninternal.server.transaction.*;
import io.ebeaninternal.server.type.DefaultTypeManager;
import io.ebeaninternal.server.type.TypeManager;
@@ -76,6 +73,7 @@ public final class InternalConfiguration {
private final DatabasePlatform databasePlatform;
private final DeployInherit deployInherit;
private final TypeManager typeManager;
+ private final TempFileProvider tempFileProvider;
private final DtoBeanManager dtoBeanManager;
private final ClockService clockService;
private final DataTimeZone dataTimeZone;
@@ -116,6 +114,7 @@ public final class InternalConfiguration {
this.databasePlatform = config.getDatabasePlatform();
this.expressionFactory = initExpressionFactory(config);
this.typeManager = new DefaultTypeManager(config, bootupClasses);
+ this.tempFileProvider = config.getTempFileProvider();
this.multiValueBind = createMultiValueBind(databasePlatform.platform());
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
@@ -144,7 +143,12 @@ private InternalConfigXmlMap initExternalMapping() {
}
private S service(Class cls) {
- return ServiceUtil.service(cls);
+ S service = config.getServiceObject(cls);
+ if (service != null) {
+ return service;
+ } else {
+ return ServiceUtil.service(cls);
+ }
}
private List readExternalMapping() {
@@ -512,6 +516,10 @@ SpiLogManager getLogManager() {
return logManager;
}
+ public TempFileProvider getTempFileProvider() {
+ return tempFileProvider;
+ }
+
private ServerCachePlugin initServerCachePlugin() {
if (config.isLocalOnlyL2Cache()) {
localL2Caching = true;
@@ -591,6 +599,8 @@ QueryPlanLogger queryPlanLogger(Platform platform) {
return new QueryPlanLoggerSqlServer();
case ORACLE:
return new QueryPlanLoggerOracle();
+ case DB2:
+ return new QueryPlanLoggerDb2();
default:
return new QueryPlanLoggerExplain();
}
@@ -611,6 +621,11 @@ private static class NoopDdl implements SpiDdlGenerator {
this.ddlRun = ddlRun;
}
+ @Override
+ public void runDdl() {
+ CoreLog.log.log(ERROR, "Manual DDL run not possible");
+ }
+
@Override
public void execute(boolean online) {
if (online && ddlRun) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java
index 0391582009..1743929189 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/SpiOrmQueryRequest.java
@@ -121,7 +121,7 @@ public interface SpiOrmQueryRequest extends BeanQueryRequest, DocQueryRequ
Map findMap();
/**
- * Execute the findSingleAttributeList query.
+ * Execute the findSingleAttributeCollection query.
*/
> A findSingleAttributeCollection(A collection);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java
new file mode 100644
index 0000000000..fe267898c9
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/VisitHandler.java
@@ -0,0 +1,128 @@
+package io.ebeaninternal.server.core;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import io.ebean.PersistVisitor;
+import io.ebean.bean.EntityBean;
+import io.ebean.bean.EntityBeanIntercept;
+import io.ebeaninternal.server.deploy.BeanDescriptor;
+import io.ebeaninternal.server.deploy.BeanDescriptorManager;
+import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
+import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
+
+/**
+ * Handler to process persist graphs. It will allow you to visit a persist
+ * action (insert/update) and get information about all beans that will be
+ * affected by that action. This allows you to do validation and other things on
+ * a set of beans.
+ *
+ * @author Roland Praml, FOCONIS AG
+ *
+ */
+class VisitHandler {
+
+ private final Set
*/
+ @Override
public String getMappedBy() {
return mappedBy;
}
@@ -173,4 +175,9 @@ public void setFetchPreference(int fetchPreference) {
public void setTargetType(Class> targetType) {
this.targetType = (Class)targetType;
}
+
+ @Override
+ public String getBaseTable() {
+ return getBeanTable().getBaseTable();
+ }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java
index c201308e2d..45f61b77ee 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyAssocMany.java
@@ -2,6 +2,7 @@
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebeaninternal.server.deploy.BeanDescriptor;
+import io.ebeaninternal.server.deploy.IntersectionFactoryHelp;
import io.ebeaninternal.server.deploy.ManyType;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.type.TypeReflectHelper;
@@ -32,6 +33,12 @@ public class DeployBeanPropertyAssocMany extends DeployBeanPropertyAssoc {
* Join for manyToMany intersection table.
*/
private DeployTableJoin intersectionJoin;
+
+ /**
+ * Factory to create intersection beans (instead of rows). For managed intersections.
+ */
+ private IntersectionFactoryHelp intersectionFactory;
+
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
@@ -113,6 +120,10 @@ public TableJoin createIntersectionTableJoin() {
}
}
+ public boolean isTableManaged() {
+ return intersectionJoin != null && desc.isTableManaged(intersectionJoin.getTable());
+ }
+
/**
* Create the immutable version of the inverse join.
*/
@@ -149,6 +160,20 @@ public void setInverseJoin(DeployTableJoin inverseJoin) {
this.inverseJoin = inverseJoin;
}
+ /**
+ * Return the intersection factory.
+ */
+ public IntersectionFactoryHelp getIntersectionFactory() {
+ return intersectionFactory;
+ }
+
+ /**
+ * Sets the intersection factory.
+ */
+ public void setIntersectionFactory(IntersectionFactoryHelp intersectionFactory) {
+ this.intersectionFactory = intersectionFactory;
+ }
+
/**
* Return the order by clause used to order the fetching of the data for
* this list, set or map.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java
index 0d29c3ba41..586164ffd0 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocManys.java
@@ -4,15 +4,13 @@
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.Where;
+import io.ebean.annotation.ext.IntersectionFactory;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.config.NamingConvention;
import io.ebean.config.TableName;
import io.ebean.core.type.ScalarType;
import io.ebean.util.CamelCaseHelper;
-import io.ebeaninternal.server.deploy.BeanDescriptorManager;
-import io.ebeaninternal.server.deploy.BeanProperty;
-import io.ebeaninternal.server.deploy.BeanTable;
-import io.ebeaninternal.server.deploy.PropertyForeignKey;
+import io.ebeaninternal.server.deploy.*;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
@@ -128,6 +126,11 @@ private void read(DeployBeanPropertyAssocMany> prop) {
prop.getTableJoin().addJoinColumn(util, true, joinColumns, beanTable);
}
+ IntersectionFactory intersectionFactory = get(prop, IntersectionFactory.class);
+ if (intersectionFactory != null) {
+ readIntersectionFactory(prop, intersectionFactory);
+ }
+
JoinTable joinTable = get(prop, JoinTable.class);
if (joinTable != null) {
if (prop.isManyToMany()) {
@@ -165,6 +168,12 @@ private void read(DeployBeanPropertyAssocMany> prop) {
}
}
+ private void readIntersectionFactory(DeployBeanPropertyAssocMany> prop, IntersectionFactory factory) {
+ Class> leftSide = descriptor.getBeanType();
+ Class> rightSide = prop.getPropertyType();
+ prop.setIntersectionFactory(new IntersectionFactoryHelp(factory.value(), leftSide, rightSide, factory.factoryMethod()));
+ }
+
private void checkSelfManyToMany(DeployBeanPropertyAssocMany> prop) {
if (prop.getTargetType().equals(descriptor.getBeanType())) {
throw new IllegalStateException("@ManyToMany mapping for " + prop.getFullBeanName() + " requires explicit @JoinTable with joinColumns & inverseJoinColumns. Refer issue #2157");
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java
index 598962fcfd..7424f4d1e1 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationAssocOnes.java
@@ -122,7 +122,7 @@ private void readAssocOne(DeployBeanPropertyAssocOne> prop) {
}
}
- prop.setJoinType(prop.isNullable());
+ prop.setJoinType(prop.isNullable() || prop.getForeignKey() != null && prop.getForeignKey().isNoConstraint());
if (!prop.getTableJoin().hasJoinColumns() && beanTable != null) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
index 662b73be80..68087e4261 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationClass.java
@@ -2,7 +2,10 @@
import io.ebean.annotation.Index;
import io.ebean.annotation.*;
+import io.ebean.annotation.ext.EntityImplements;
+import io.ebean.annotation.ext.EntityOverride;
import io.ebean.config.TableName;
+import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.CoreLog;
import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
import io.ebeaninternal.server.deploy.IndexDefinition;
@@ -12,6 +15,7 @@
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.*;
+import java.util.Set;
import static io.ebean.util.AnnotationUtil.typeGet;
import static java.lang.System.Logger.Level.ERROR;
@@ -68,7 +72,7 @@ public void parse() {
*/
private void setTableName() {
if (descriptor.isBaseTableType()) {
- Class> beanType = descriptor.getBeanType();
+ Class> beanType = descriptor.getBaseBeanType();
InheritInfo inheritInfo = descriptor.getInheritInfo();
if (inheritInfo != null) {
beanType = inheritInfo.getRoot().getType();
@@ -184,6 +188,18 @@ private void read(Class> cls) {
for (NamedQuery namedQuery : annotationClassNamedQuery(cls)) {
descriptor.addNamedQuery(namedQuery.name(), namedQuery.query());
}
+
+ Set entityImplements = AnnotationUtil.typeGetAll(cls, EntityImplements.class);
+ for (EntityImplements ann : entityImplements) {
+ for (Class> iface : ann.value()) {
+ descriptor.addInterface(iface);
+ }
+ }
+
+ EntityOverride entityOverride = AnnotationUtil.typeGet(cls, EntityOverride.class);
+ if (entityOverride != null) {
+ descriptor.setOverridePriority(entityOverride.priority());
+ }
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java b/ebean-core/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java
index 21e34a8b3a..adbf5e0824 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/idgen/UuidV1RndIdGenerator.java
@@ -106,7 +106,7 @@ public UUID nextId(Transaction t) {
delta = current - last;
if (delta < -10000 * 20000) {
- log.log(INFO, "Clock skew of {} ms detected", delta / -10000);
+ log.log(INFO, "Clock skew of {0} ms detected", delta / -10000);
// The clock was adjusted back about 2 seconds, or we were generating a lot of ids too fast
// if so, we try to set the current as last and also increment the clockSeq.
lock.lock();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/json/WriteJson.java b/ebean-core/src/main/java/io/ebeaninternal/server/json/WriteJson.java
index 39e03eb95e..f8a3574cad 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/json/WriteJson.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/json/WriteJson.java
@@ -506,6 +506,9 @@ private boolean isIncludeProperty(BeanProperty prop) {
return true;
if (currentIncludeProps != null) {
// explicitly controlled by pathProperties
+ if (prop.isId() && currentIncludeProps.contains("${identifier}")) {
+ return true;
+ }
return currentIncludeProps.contains(prop.name());
} else {
// include only loaded properties
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
index f78e11ea83..7a27c3946b 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
@@ -915,7 +915,7 @@ void deleteManyIntersection(EntityBean bean, BeanPropertyAssocMany> many, SpiT
private SpiSqlUpdate deleteAllIntersection(EntityBean bean, BeanPropertyAssocMany> many, boolean publish) {
IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean, publish);
- return intRow.createDeleteChildren(server);
+ return intRow.createDeleteChildren(server, many.extraWhere());
}
/**
@@ -939,7 +939,10 @@ private void deleteAssocMany(PersistRequestBean> request) {
for (BeanPropertyAssocOne> prop : expOnes) {
// for soft delete check cascade type also supports soft delete
if (deleteMode.isHard() || prop.isTargetSoftDelete()) {
- if (request.isLoadedProperty(prop)) {
+ if (prop.isPrimaryKeyExport()) {
+ // we can delete by id, neither if property loaded or not
+ delete(prop.targetDescriptor(), prop.descriptor().id(parentBean), null, t, deleteMode);
+ } else if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(parentBean);
if (detailBean != null) {
deleteRecurse((EntityBean) detailBean, t, deleteMode);
@@ -1009,7 +1012,7 @@ void deleteManyDetails(SpiTransaction t, BeanDescriptor> desc, EntityBean pare
if (targetDesc.isDeleteByStatement()) {
// Just delete all the children with one statement
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
- SqlUpdate sqlDelete = intRow.createDelete(server, deleteMode);
+ SqlUpdate sqlDelete = intRow.createDelete(server, deleteMode, many.extraWhere());
executeSqlUpdate(sqlDelete, t);
} else {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java
index 5b6858e784..7752245510 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/SaveManyBeans.java
@@ -6,18 +6,10 @@
import io.ebeaninternal.api.CoreLog;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.server.core.PersistRequestBean;
-import io.ebeaninternal.server.deploy.BeanCollectionUtil;
-import io.ebeaninternal.server.deploy.BeanDescriptor;
-import io.ebeaninternal.server.deploy.BeanProperty;
-import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
-import io.ebeaninternal.server.deploy.IntersectionRow;
+import io.ebeaninternal.server.deploy.*;
import javax.persistence.PersistenceException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import static io.ebeaninternal.server.persist.DmlUtil.isNullOrZero;
@@ -96,6 +88,22 @@ private boolean isSaveIntersection() {
// OneToMany JoinTable
return true;
}
+ if (many.isTableManaged()) {
+ List tables = new ArrayList<>(3);
+ tables.add(many.descriptor().baseTable());
+ tables.add(many.targetDescriptor().baseTable());
+ tables.add(many.intersectionTableJoin().getTable());
+ // put all tables in a deterministic order
+ tables.sort(Comparator.naturalOrder());
+
+ if (transaction.isSaveAssocManyIntersection(String.join("-", tables), many.descriptor().rootName())) {
+ // notify others, that we do save this transaction
+ transaction.isSaveAssocManyIntersection(many.intersectionTableJoin().getTable(), many.descriptor().rootName());
+ return true;
+ } else {
+ return false;
+ }
+ }
return transaction.isSaveAssocManyIntersection(many.intersectionTableJoin().getTable(), many.descriptor().rootName());
}
@@ -299,14 +307,16 @@ private void saveAssocManyIntersection(boolean queue) {
}
transaction.depth(+1);
+ boolean needsFlush = false;
if (deletions != null && !deletions.isEmpty()) {
for (Object other : deletions) {
EntityBean otherDelete = (EntityBean) other;
// the object from the 'other' side of the ManyToMany
// build a intersection row for 'delete'
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish);
- SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD);
+ SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD, many.extraWhere());
persister.executeOrQueue(sqlDelete, transaction, queue);
+ needsFlush = true;
}
}
if (additions != null && !additions.isEmpty()) {
@@ -322,6 +332,21 @@ private void saveAssocManyIntersection(boolean queue) {
} else {
if (!many.hasImportedId(otherBean)) {
throw new PersistenceException("ManyToMany bean " + otherBean + " does not have an Id value.");
+ } else if (many.getIntersectionFactory() != null) {
+ // build a intersection bean for 'insert'
+ // They need to be executed very late and would normally go to Queue#2, but we do not have
+ // a SpiSqlUpdate for now.
+ if (needsFlush) {
+ transaction.flushBatchOnCascade();
+ }
+ if (queue) {
+ transaction.depth(+100);
+ }
+ Object intersectionBean = many.getIntersectionFactory().invoke(parentBean, otherBean);
+ persister.saveRecurse((EntityBean) intersectionBean, transaction, parentBean, request.flags());
+ if (queue) {
+ transaction.depth(-100);
+ }
} else {
// build a intersection row for 'insert'
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherBean, publish);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java
index 4a4abe17d5..1b71bc1a09 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java
@@ -70,7 +70,8 @@ private int execute(PersistRequestBean> request, PersistHandler handler) {
}
} catch (SQLException e) {
// log the error to the transaction log
- String msg = "Error: " + StringHelper.removeNewLines(e.getMessage());
+ String msg = "Error[" + StringHelper.removeNewLines(e.getMessage()) + "] " + handler;
+
if (request.transaction().isLogSummary()) {
request.transaction().logSummary(msg);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java
index 58ec931fc5..97c483fc51 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java
@@ -263,4 +263,14 @@ PreparedStatement getPstmtBatch(SpiTransaction t, String sql, PersistRequestBean
return stmt;
}
+ @Override
+ public String toString() {
+ if (sql == null) {
+ return "not yet initialized";
+ } else if (bindLog == null || bindLog.length() == 0) {
+ return sql;
+ } else {
+ return Str.add(sql, " -- bind(", bindLog.toString(), ")");
+ }
+ }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/QueryPlanLoggerDb2.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/QueryPlanLoggerDb2.java
new file mode 100644
index 0000000000..d6cb3f4dd6
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/QueryPlanLoggerDb2.java
@@ -0,0 +1,54 @@
+
+package io.ebeaninternal.server.query;
+
+import io.ebeaninternal.api.CoreLog;
+import io.ebeaninternal.api.SpiDbQueryPlan;
+import io.ebeaninternal.api.SpiQueryPlan;
+import io.ebeaninternal.server.bind.capture.BindCapture;
+
+import java.sql.*;
+import java.util.Random;
+
+import static java.lang.System.Logger.Level.WARNING;
+
+/**
+ * A QueryPlanLogger for DB2.
+ *
+ * To use query plan capturing, you have to install the explain tables with
+ * SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA ).
+ * To do this in a repeatable script, you may use this statement:
+ *
+ *
+ * BEGIN
+ * IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN
+ * call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA );
+ * END IF;
+ * END
+ *
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+public final class QueryPlanLoggerDb2 extends QueryPlanLogger {
+
+ private Random rnd = new Random();
+
+ @Override
+ public SpiDbQueryPlan collectPlan(Connection conn, SpiQueryPlan plan, BindCapture bind) {
+ try (Statement stmt = conn.createStatement()) {
+ int queryNo = rnd.nextInt(Integer.MAX_VALUE);
+ try (PreparedStatement explainStmt = conn
+ .prepareStatement("EXPLAIN PLAN SET QUERYNO=" + queryNo + " FOR " + plan.sql())) {
+ bind.prepare(explainStmt, conn);
+ explainStmt.execute();
+ }
+
+ try (ResultSet rset = stmt.executeQuery("select * from EXPLAIN_STATEMENT where QUERYNO=" + queryNo)) {
+ return readQueryPlan(plan, bind, rset);
+ }
+ } catch (SQLException e) {
+ CoreLog.log.log(WARNING, "Could not log query plan", e);
+ return null;
+ }
+ }
+
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java
index 412c74fc85..a38cfc51b4 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java
@@ -414,7 +414,7 @@ private void addProperty(SqlTreeProperties selectProps, STreeType desc, OrmQuery
// make sure we only included the base/embedded bean once
if (!selectProps.containsProperty(baseName)) {
- STreeProperty p = desc.findPropertyWithDynamic(baseName, null);
+ STreeProperty p = desc.findProperty(baseName);
if (p == null) {
// maybe dynamic formula with schema prefix
p = desc.findPropertyWithDynamic(propName, null);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
index 9fae2e21c8..4b8408c27a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -885,6 +885,9 @@ public final void postCommit() {
public final void preCommit() {
internalBatchFlush();
firePreCommit();
+ // we must flush the batch queue again, because the callback can
+ // modify current transaction
+ internalBatchFlush();
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
index c60386f30a..fdf77e8293 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
@@ -56,7 +56,7 @@ public final class DefaultTypeManager implements TypeManager {
private final DefaultTypeFactory extraTypeFactory;
- private final ScalarType> fileType = new ScalarTypeFile();
+ private final ScalarType> fileType;
private final ScalarType> hstoreType = new ScalarTypePostgresHstore();
private final JsonConfig.DateTime jsonDateTime;
@@ -94,6 +94,7 @@ public DefaultTypeManager(DatabaseConfig config, BootupClasses bootupClasses) {
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
this.offlineMigrationGeneration = DbOffline.isGenerateMigration();
this.defaultEnumType = config.getDefaultEnumType();
+ this.fileType = new ScalarTypeFile(config.getTempFileProvider());
ServiceLoader mappers = ServiceLoader.load(ScalarJsonMapper.class);
jsonMapper = mappers.findFirst().orElse(null);
@@ -111,7 +112,10 @@ public DefaultTypeManager(DatabaseConfig config, BootupClasses bootupClasses) {
}
private void loadGeoTypeBinder(DatabaseConfig config) {
- final GeoTypeProvider provider = ServiceUtil.service(GeoTypeProvider.class);
+ GeoTypeProvider provider = config.getServiceObject(GeoTypeProvider.class);
+ if (provider == null) {
+ provider = ServiceUtil.service(GeoTypeProvider.class);
+ }
if (provider != null) {
geoTypeBinder = provider.createBinder(config);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java
index 8ac56e5176..0331296cb5 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeCalendar.java
@@ -61,7 +61,7 @@ public Calendar convertFromInstant(Instant ts) {
@Override
protected String toJsonNanos(Calendar value) {
- return String.valueOf(value.getTime());
+ return String.valueOf(value.getTime().getTime());
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java
index 5e4ad5df80..5dd6ff96e9 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeFile.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
+import io.ebean.config.TempFileProvider;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -20,26 +21,22 @@
*/
final class ScalarTypeFile extends ScalarTypeBase {
- private final String prefix;
- private final String suffix;
- private final File directory;
+ private final TempFileProvider tempFileProvider;
private final int bufferSize;
/**
- * Construct with reasonable defaults of Blob and 8096 buffer size.
+ * Construct with reasonable defaults of Blob and 8192 buffer size.
*/
- ScalarTypeFile() {
- this(Types.LONGVARBINARY, "db-", null, null, 8096);
+ ScalarTypeFile(TempFileProvider tempFileProvider) {
+ this(Types.LONGVARBINARY, tempFileProvider, 8192);
}
/**
* Create the ScalarTypeFile.
*/
- ScalarTypeFile(int jdbcType, String prefix, String suffix, File directory, int bufferSize) {
+ ScalarTypeFile(int jdbcType, TempFileProvider tempFileProvider, int bufferSize) {
super(File.class, false, jdbcType);
- this.prefix = prefix;
- this.suffix = suffix;
- this.directory = directory;
+ this.tempFileProvider = tempFileProvider;
this.bufferSize = bufferSize;
}
@@ -66,7 +63,7 @@ public File read(DataReader reader) throws SQLException {
}
try {
// stream from db into our temp file
- File tempFile = File.createTempFile(prefix, suffix, directory);
+ File tempFile = tempFileProvider.createTempFile();
OutputStream os = getOutputStream(tempFile);
pump(is, os);
return tempFile;
@@ -109,7 +106,7 @@ public void jsonWrite(JsonGenerator writer, File value) throws IOException {
@Override
public File jsonRead(JsonParser parser) throws IOException {
- File tempFile = File.createTempFile(prefix, suffix, directory);
+ File tempFile = tempFileProvider.createTempFile();
try (OutputStream os = getOutputStream(tempFile)) {
parser.readBinaryValue(os);
os.flush();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java
index 3de0184e1f..b5b523c149 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTime.java
@@ -11,6 +11,7 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
/**
* ScalarType for java.sql.Timestamp.
@@ -63,7 +64,11 @@ public String formatValue(LocalDateTime value) {
@Override
public LocalDateTime parse(String value) {
- return LocalDateTime.parse(value);
+ try {
+ return LocalDateTime.parse(value);
+ } catch (DateTimeParseException pe) {
+ return super.parse(value);
+ }
}
@Override
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java
index 623402fb71..27aebb9d79 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java
@@ -3,6 +3,8 @@
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.ebean.config.JsonConfig;
+
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.io.StringWriter;
@@ -123,4 +125,20 @@ public void isoJsonFormatParse() {
LocalDateTime value = typeIso.fromJsonISO8601(asJson);
assertThat(localDateTime).isEqualToIgnoringNanos(value);
}
+
+ @Test
+ @Disabled("Does not work @github due different timezone")
+ // Expecting actual:
+ // 2018-02-03T03:05:06 (java.time.LocalDateTime)
+ // to have same year, month, day, hour, minute and second as:
+ // 2018-02-03T04:05:06 (java.time.LocalDateTime)
+ public void testParseEbean11() {
+
+ ScalarTypeLocalDateTime typeDefault = new ScalarTypeLocalDateTime(JsonConfig.DateTime.ISO8601);
+
+ LocalDateTime fromMillis = typeDefault.parse("1517627106000");
+ LocalDateTime fromIso= typeDefault.parse("2018-02-03T04:05:06");
+
+ assertThat(fromMillis).isEqualToIgnoringNanos(fromIso);
+ }
}
diff --git a/ebean-csv-reader/pom.xml b/ebean-csv-reader/pom.xml
index fce50e8e76..dde65303a1 100644
--- a/ebean-csv-reader/pom.xml
+++ b/ebean-csv-reader/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-csv-reader
@@ -14,21 +14,21 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-csv-reader/src/test/java/io/ebean/csv/reader/CsvReaderTest.java b/ebean-csv-reader/src/test/java/io/ebean/csv/reader/CsvReaderTest.java
index 8a871a62da..6504c219e5 100644
--- a/ebean-csv-reader/src/test/java/io/ebean/csv/reader/CsvReaderTest.java
+++ b/ebean-csv-reader/src/test/java/io/ebean/csv/reader/CsvReaderTest.java
@@ -11,10 +11,11 @@
import java.net.URL;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
+import java.util.Locale;
class CsvReaderTest {
- final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy");
+ final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy", Locale.ENGLISH);
@Test
void test() throws Exception {
diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml
index ec4a234564..2291d4810b 100644
--- a/ebean-ddl-generator/pom.xml
+++ b/ebean-ddl-generator/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean ddl generation
@@ -28,14 +28,14 @@
io.ebeanebean-core-type
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
@@ -58,7 +58,7 @@
io.ebeanebean-platform-all
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DbMigrationPlugin.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DbMigrationPlugin.java
new file mode 100644
index 0000000000..1839cf4917
--- /dev/null
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DbMigrationPlugin.java
@@ -0,0 +1,56 @@
+package io.ebeaninternal.dbmigration;
+
+import java.io.IOException;
+
+import io.ebean.plugin.Plugin;
+import io.ebean.plugin.SpiServer;
+
+/**
+ * Plugin to generate db-migration scripts automatically.
+ * @author Roland Praml, FOCONIS AG
+ */
+public class DbMigrationPlugin implements Plugin {
+
+ private DefaultDbMigration dbMigration;
+
+ private static String lastMigration;
+ private static String lastInit;
+
+ @Override
+ public void configure(SpiServer server) {
+ dbMigration = new DefaultDbMigration();
+ dbMigration.setServer(server);
+ }
+
+ @Override
+ public void online(boolean online) {
+ try {
+ lastInit = null;
+ lastMigration = null;
+ if (dbMigration.generate) {
+ String tmp = lastMigration = dbMigration.generateMigration();
+ if (tmp == null) {
+ return;
+ }
+ }
+ if (dbMigration.generateInit) {
+ lastInit = dbMigration.generateInitMigration();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Error while generating migration", e);
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ dbMigration = null;
+ }
+
+ public static String getLastInit() {
+ return lastInit;
+ }
+
+ public static String getLastMigration() {
+ return lastMigration;
+ }
+}
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java
index 7567ad4cf4..67c3b8f9a2 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DdlGenerator.java
@@ -63,11 +63,10 @@ public DdlGenerator(SpiEbeanServer server) {
if (!config.getTenantMode().isDdlEnabled() && config.isDdlRun()) {
log.log(WARNING, "DDL can't be run on startup with TenantMode " + config.getTenantMode());
this.runDdl = false;
- this.useMigrationStoredProcedures = false;
} else {
this.runDdl = config.isDdlRun();
- this.useMigrationStoredProcedures = config.getDatabasePlatform().useMigrationStoredProcedures();
}
+ this.useMigrationStoredProcedures = config.getDatabasePlatform() != null && config.getDatabasePlatform().useMigrationStoredProcedures();
this.scriptTransform = createScriptTransform(config);
this.baseDir = initBaseDir();
}
@@ -85,7 +84,7 @@ private File initBaseDir() {
@Override
public void execute(boolean online) {
generateDdl();
- if (online) {
+ if (online && runDdl) {
runDdl();
}
}
@@ -105,16 +104,15 @@ protected void generateDdl() {
/**
* Run the DDL drop and DDL create scripts if properties have been set.
*/
- protected void runDdl() {
- if (runDdl) {
- Connection connection = null;
- try {
- connection = obtainConnection();
- runDdlWith(connection);
- } finally {
- JdbcClose.rollback(connection);
- JdbcClose.close(connection);
- }
+ @Override
+ public void runDdl() {
+ Connection connection = null;
+ try {
+ connection = obtainConnection();
+ runDdlWith(connection);
+ } finally {
+ JdbcClose.rollback(connection);
+ JdbcClose.close(connection);
}
}
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java
index a96957d0ab..73857a05ee 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java
@@ -1,6 +1,7 @@
package io.ebeaninternal.dbmigration;
import io.avaje.applog.AppLog;
+import io.avaje.classpath.scanner.core.Location;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.annotation.Platform;
@@ -12,6 +13,7 @@
import io.ebean.config.dbplatform.DatabasePlatformProvider;
import io.ebean.dbmigration.DbMigration;
import io.ebean.util.IOUtils;
+import io.ebean.util.StringHelper;
import io.ebeaninternal.api.DbOffline;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
@@ -26,10 +28,7 @@
import java.io.File;
import java.io.IOException;
import java.io.Writer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-import java.util.ServiceLoader;
+import java.util.*;
import static io.ebeaninternal.api.PlatformMatch.matchPlatform;
import static java.lang.System.Logger.Level.*;
@@ -61,8 +60,8 @@ public class DefaultDbMigration implements DbMigration {
private static final String initialVersion = "1.0";
private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY";
- private final List platformProviders = new ArrayList<>();
- protected final boolean online;
+ private List platformProviders = new ArrayList<>();
+ protected boolean online;
private boolean logToSystemOut = true;
protected SpiEbeanServer server;
protected String pathToResources = "src/main/resources";
@@ -77,8 +76,10 @@ public class DefaultDbMigration implements DbMigration {
protected List platforms = new ArrayList<>();
protected DatabaseConfig databaseConfig;
protected DbConstraintNaming constraintNaming;
+ @Deprecated
protected Boolean strictMode;
- protected Boolean includeGeneratedFileComment;
+ protected boolean includeGeneratedFileComment;
+ @Deprecated
protected String header;
protected String applyPrefix = "";
protected String version;
@@ -88,6 +89,9 @@ public class DefaultDbMigration implements DbMigration {
private int lockTimeoutSeconds;
protected boolean includeBuiltInPartitioning = true;
protected boolean includeIndex;
+ protected boolean generate = false;
+ protected boolean generateInit = false;
+ private boolean keepLastInit = true;
/**
* Create for offline migration generation.
@@ -123,12 +127,66 @@ public void setServerConfig(DatabaseConfig config) {
if (constraintNaming == null) {
this.constraintNaming = databaseConfig.getConstraintNaming();
}
+ if (databasePlatform == null) {
+ this.databasePlatform = databaseConfig.getDatabasePlatform();
+ }
Properties properties = config.getProperties();
if (properties != null) {
- PropertiesWrapper props = new PropertiesWrapper("ebean", config.getName(), properties, null);
+ PropertiesWrapper props = new PropertiesWrapper("ebean", config.getName(), properties, config.getClassLoadConfig());
migrationPath = props.get("migration.migrationPath", migrationPath);
migrationInitPath = props.get("migration.migrationInitPath", migrationInitPath);
pathToResources = props.get("migration.pathToResources", pathToResources);
+ addForeignKeySkipCheck = props.getBoolean("migration.addForeignKeySkipCheck", addForeignKeySkipCheck);
+ applyPrefix = props.get("migration.applyPrefix",applyPrefix);
+ databasePlatform = props.createInstance(DatabasePlatform.class, "migration.databasePlatform", databasePlatform);
+ generatePendingDrop = props.get("migration.generatePendingDrop", generatePendingDrop);
+ includeBuiltInPartitioning = props.getBoolean("migration.includeBuiltInPartitioning", includeBuiltInPartitioning);
+ includeGeneratedFileComment = props.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
+ includeIndex = props.getBoolean("migration.includeIndex", includeIndex);
+ lockTimeoutSeconds = props.getInt("migration.lockTimeoutSeconds", lockTimeoutSeconds);
+ logToSystemOut = props.getBoolean("migration.logToSystemOut", logToSystemOut);
+ modelPath = props.get("migration.modelPath", modelPath);
+ modelSuffix = props.get("migration.modelSuffix", modelSuffix);
+ name = props.get("migration.name", name);
+ online = props.getBoolean("migration.online", online);
+ vanillaPlatform = props.getBoolean("migration.vanillaPlatform", vanillaPlatform);
+ version = props.get("migration.version", version);
+ generate = props.getBoolean("migration.generate", generate);
+ generateInit = props.getBoolean("migration.generateInit", generateInit);
+ // header & strictMode must be configured at DatabaseConfig level
+ parsePlatforms(props, config);
+ }
+ }
+
+ protected void parsePlatforms(PropertiesWrapper props, DatabaseConfig config) {
+ String platforms = props.get("migration.platforms");
+ if (platforms == null || platforms.isEmpty()) {
+ return;
+ }
+ String[] tmp = StringHelper.splitNames(platforms);
+ for (String plat : tmp) {
+ DatabasePlatform dbPlatform;
+ String platformName = plat;
+ String platformPrefix = null;
+ int pos = plat.indexOf('=');
+ if (pos != -1) {
+ platformName = plat.substring(0, pos);
+ platformPrefix = plat.substring(pos + 1);
+ }
+
+ if (platformName.indexOf('.') == -1) {
+ // parse platform as enum value
+ Platform platform = Enum.valueOf(Platform.class, platformName.toUpperCase());
+ dbPlatform = platform(platform);
+ } else {
+ // parse platform as class
+ dbPlatform = (DatabasePlatform) config.getClassLoadConfig().newInstance(platformName);
+ }
+ if (platformPrefix == null) {
+ platformPrefix = dbPlatform.platform().name().toLowerCase();
+ }
+
+ addDatabasePlatform(dbPlatform, platformPrefix);
}
}
@@ -319,7 +377,18 @@ private String generateMigrationFor(boolean initMigration) throws IOException {
}
String pendingVersion = generatePendingDrop();
- if (pendingVersion != null) {
+ if ("auto".equals(pendingVersion)) {
+ StringJoiner sj = new StringJoiner(",");
+ String diff = generateDiff(request);
+ if (diff != null) {
+ sj.add(diff);
+ request = createRequest(initMigration);
+ }
+ for (String pendingDrop : request.getPendingDrops()) {
+ sj.add(generatePendingDrop(request, pendingDrop));
+ }
+ return sj.length() == 0 ? null : sj.toString();
+ } else if (pendingVersion != null) {
return generatePendingDrop(request, pendingVersion);
} else {
return generateDiff(request);
@@ -376,6 +445,7 @@ private void configurePlatforms() {
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException {
if (dbPlatform != null) {
if (tablePartitioning && includeBuiltInPartitioning) {
+
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning(), false);
}
// skip built-in migration stored procedures based on isUseMigrationStoredProcedures
@@ -384,6 +454,7 @@ private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, bo
}
}
+
private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl, boolean checkSkip) throws IOException {
if (extraDdl != null) {
List ddlScript = extraDdl.getDdlScript();
@@ -554,7 +625,7 @@ private String generateMigration(Request request, Migration dbMigration, String
return null;
} else {
if (!platforms.isEmpty()) {
- writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
+ writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir, request.initMigration && keepLastInit);
} else if (databasePlatform != null) {
// writer needs the current model to provide table/column details for
@@ -634,12 +705,17 @@ private String toUnderScore(String name) {
/**
* Write any extra platform ddl.
*/
- private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
+ private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath, boolean clear) throws IOException {
DdlOptions options = new DdlOptions(addForeignKeySkipCheck);
for (Pair pair : platforms) {
DdlWrite writer = new DdlWrite(new MConfiguration(), currentModel.read(), options);
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
File subPath = platformWriter.subPath(writePath, pair.prefix);
+ if (clear) {
+ for (File existing : subPath.listFiles()) {
+ existing.delete();
+ }
+ }
platformWriter.processMigration(dbMigration, writer, subPath, fullVersion);
}
}
@@ -657,7 +733,7 @@ private boolean writeMigrationXml(Migration dbMigration, File resourcePath, Stri
if (file.exists()) {
return false;
}
- String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null;
+ String comment = includeGeneratedFileComment ? GENERATED_COMMENT : null;
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
xmlWriter.write(dbMigration, file);
return true;
@@ -675,6 +751,7 @@ private void setDefaults() {
databasePlatform = server.databasePlatform();
}
if (databaseConfig != null) {
+ // FIXME: Copy header and StrictMode to databaseConfig
if (strictMode != null) {
databaseConfig.setDdlStrictMode(strictMode);
}
@@ -749,15 +826,20 @@ public File migrationDirectory() {
* Return the file path to write the xml and sql to.
*/
File migrationDirectory(boolean initMigration) {
- // path to src/main/resources in typical maven project
- File resourceRootDir = new File(pathToResources);
- if (!resourceRootDir.exists()) {
- String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath());
- throw new UnknownResourcePathException(msg);
- }
- String resourcePath = migrationPath(initMigration);
+ Location resourcePath = migrationPath(initMigration);
// expect to be a path to something like - src/main/resources/dbmigration
- File path = new File(resourceRootDir, resourcePath);
+ File path;
+ if (resourcePath.isClassPath()) {
+ // path to src/main/resources in typical maven project
+ File resourceRootDir = new File(pathToResources);
+ if (!resourceRootDir.exists()) {
+ String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath());
+ throw new UnknownResourcePathException(msg);
+ }
+ path = new File(resourceRootDir, resourcePath.path());
+ } else {
+ path = new File(resourcePath.path());
+ }
if (!path.exists()) {
if (!path.mkdirs()) {
logInfo("Warning - Unable to ensure migration directory exists at %s", path.getAbsolutePath());
@@ -766,8 +848,9 @@ File migrationDirectory(boolean initMigration) {
return path;
}
- private String migrationPath(boolean initMigration) {
- return initMigration ? migrationInitPath : migrationPath;
+ private Location migrationPath(boolean initMigration) {
+ // remove classpath: or filesystem: prefix
+ return new Location(initMigration ? migrationInitPath : migrationPath);
}
/**
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java
index e9fafccc41..9d81397150 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java
@@ -142,7 +142,7 @@ private String translate(String ddl, String tableName, String columnName, String
private void handleStrictError(String tableName, String columnName) {
if (strictMode) {
- String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or specify dbMigration.setStrictMode(false)";
+ String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or disable strict mode for migration";
throw new IllegalArgumentException(message);
}
}
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java
index 3ff9034d18..350c584e8c 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java
@@ -42,7 +42,7 @@ private static String buildExtra(Platform platform, boolean drops, ExtraDdl read
StringBuilder sb = new StringBuilder(300);
for (DdlScript script : read.getDdlScript()) {
if (script.isDrop() == drops && matchPlatform(platform, script.getPlatforms())) {
- logger.log(DEBUG, "include script {}", script.getName());
+ logger.log(DEBUG, "include script {0}", script.getName());
String value = script.getValue();
sb.append(value);
if (value.lastIndexOf(';') == -1) {
diff --git a/ebean-ddl-generator/src/main/java/module-info.java b/ebean-ddl-generator/src/main/java/module-info.java
index 1a7ef332e4..c5b6af08b2 100644
--- a/ebean-ddl-generator/src/main/java/module-info.java
+++ b/ebean-ddl-generator/src/main/java/module-info.java
@@ -1,5 +1,6 @@
module io.ebean.ddl.generator {
+ uses io.ebean.plugin.Plugin;
exports io.ebean.dbmigration;
provides io.ebean.dbmigration.DbMigration with io.ebeaninternal.dbmigration.DefaultDbMigration;
diff --git a/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebean.plugin.Plugin b/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebean.plugin.Plugin
new file mode 100644
index 0000000000..83ec94df48
--- /dev/null
+++ b/ebean-ddl-generator/src/main/resources/META-INF/services/io.ebean.plugin.Plugin
@@ -0,0 +1 @@
+io.ebeaninternal.dbmigration.DbMigrationPlugin
diff --git a/ebean-ddl-generator/src/test/resources/application-test.properties b/ebean-ddl-generator/src/test/resources/application-test.properties
index dfcb052ab0..af973bb3a5 100644
--- a/ebean-ddl-generator/src/test/resources/application-test.properties
+++ b/ebean-ddl-generator/src/test/resources/application-test.properties
@@ -5,32 +5,12 @@ datasource.default=h2
datasource.h2.username=sa
datasource.h2.password=
-datasource.h2.url=jdbc:h2:mem:h2AutoTune
+datasource.h2.url=jdbc:h2:mem:h2AutoTune;NON_KEYWORDS=KEY,VALUE
+
+datasource.db2.username=migtest
+datasource.db2.password=migtest
+datasource.db2.url=jdbc:db2://localhost:50005/migtest
datasource.pg.username=sa
datasource.pg.password=
datasource.pg.url=jdbc:h2:mem:h2AutoTune
-
-# parameters for migration test
-datasource.migrationtest.username=SA
-datasource.migrationtest.password=SA
-datasource.migrationtest.url=jdbc:h2:mem:migration
-ebean.migrationtest.applyPrefix=V
-ebean.migrationtest.ddl.generate=false
-ebean.migrationtest.ddl.run=false
-ebean.migrationtest.ddl.header=-- Migrationscripts for ebean unittest
-ebean.migrationtest.migration.appName=migrationtest
-ebean.migrationtest.migration.migrationPath=dbmigration/migrationtest
-ebean.migrationtest.migration.strict=true
-
-# parameters for migration test
-datasource.migrationtest-history.username=SA
-datasource.migrationtest-history.password=SA
-datasource.migrationtest-history.url=jdbc:h2:mem:migration
-ebean.migrationtest-history.applyPrefix=V
-ebean.migrationtest-history.ddl.generate=false
-ebean.migrationtest-history.ddl.run=false
-ebean.migrationtest-history.ddl.header=-- Migrationscripts for ebean unittest DbMigrationDropHistoryTest
-ebean.migrationtest-history.migration.appName=migrationtest-history
-ebean.migrationtest-history.migration.migrationPath=dbmigration/migrationtest-history
-ebean.migrationtest-history.migration.strict=true
diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml
index 90849e8418..6480c67d9f 100644
--- a/ebean-externalmapping-api/pom.xml
+++ b/ebean-externalmapping-api/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean external mapping api
diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml
index 4ad095b631..f6ad1507f4 100644
--- a/ebean-externalmapping-xml/pom.xml
+++ b/ebean-externalmapping-xml/pom.xml
@@ -4,12 +4,12 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
- scm:git:git@github.com:ebean-orm/ebean.git
- HEAD
+ scm:git:git@github.com:FOCONIS/ebean.git
+ ebean-parent-13.6.4-FOC1ebean external mapping xml
@@ -21,7 +21,7 @@
io.ebeanebean-externalmapping-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -54,21 +54,21 @@
io.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-ddl-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-jackson-jsonnode/pom.xml b/ebean-jackson-jsonnode/pom.xml
index bb9959ba1f..93905497b1 100644
--- a/ebean-jackson-jsonnode/pom.xml
+++ b/ebean-jackson-jsonnode/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-jackson-jsonnode
@@ -14,7 +14,7 @@
io.ebeanebean-core-type
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
diff --git a/ebean-jackson-mapper/pom.xml b/ebean-jackson-mapper/pom.xml
index 24e4f274b4..0e41387e38 100644
--- a/ebean-jackson-mapper/pom.xml
+++ b/ebean-jackson-mapper/pom.xml
@@ -3,7 +3,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT4.0.0
@@ -14,7 +14,7 @@
io.ebeanebean-core-type
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
diff --git a/ebean-joda-time/pom.xml b/ebean-joda-time/pom.xml
index f1196b1409..e7332c4fe3 100644
--- a/ebean-joda-time/pom.xml
+++ b/ebean-joda-time/pom.xml
@@ -3,7 +3,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT4.0.0
@@ -14,7 +14,7 @@
io.ebeanebean-core-type
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
diff --git a/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaDateMidnight.java b/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaDateMidnight.java
index e2520906da..61f71869b1 100644
--- a/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaDateMidnight.java
+++ b/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaDateMidnight.java
@@ -23,7 +23,7 @@ final class ScalarTypeJodaDateMidnight extends ScalarTypeBaseDate
@Override
protected String toIsoFormat(DateMidnight value) {
- return value.toString();
+ return value.toLocalDate().toString();
}
@Override
diff --git a/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaLocalDateTime.java b/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaLocalDateTime.java
index 3b0ec373b5..73b3c6e23b 100644
--- a/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaLocalDateTime.java
+++ b/ebean-joda-time/src/main/java/io/ebean/joda/time/ScalarTypeJodaLocalDateTime.java
@@ -27,6 +27,11 @@ protected String toJsonNanos(LocalDateTime value) {
protected String toJsonISO8601(LocalDateTime value) {
return value.toString();
}
+
+ @Override
+ protected LocalDateTime fromJsonISO8601(String value) {
+ return LocalDateTime.parse(value);
+ }
@Override
public long convertToMillis(LocalDateTime value) {
diff --git a/ebean-kotlin/pom.xml b/ebean-kotlin/pom.xml
index 1d6f391b48..9db4f9ac34 100644
--- a/ebean-kotlin/pom.xml
+++ b/ebean-kotlin/pom.xml
@@ -1,12 +1,10 @@
-
+4.0.0ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-kotlin
@@ -28,7 +26,7 @@
io.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
@@ -50,7 +48,7 @@
io.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml
index 4e6abbfad0..6082a55217 100644
--- a/ebean-postgis/pom.xml
+++ b/ebean-postgis/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean postgis
@@ -22,14 +22,14 @@
io.ebeanebean-platform-postgres
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
@@ -65,7 +65,7 @@
io.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml
index 7470185859..8262aad872 100644
--- a/ebean-querybean/pom.xml
+++ b/ebean-querybean/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean querybean
@@ -17,7 +17,7 @@
io.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovided
@@ -63,21 +63,21 @@
io.ebeanebean-ddl-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanquerybean-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml
index 5fc9f258d2..5df930f7c0 100644
--- a/ebean-redis/pom.xml
+++ b/ebean-redis/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-redis
@@ -22,35 +22,35 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanquerybean-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml
index 86f127f967..59b0218287 100644
--- a/ebean-test/pom.xml
+++ b/ebean-test/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean test
@@ -28,20 +28,20 @@
io.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTprovidedio.ebeanebean-ddl-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -105,28 +105,28 @@
io.ebeanebean-joda-time
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-jackson-jsonnode
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-jackson-mapper
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-platform-all
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/ebean-test/src/main/java/module-info.java b/ebean-test/src/main/java/module-info.java
index 428c3b3f4a..ed64a809eb 100644
--- a/ebean-test/src/main/java/module-info.java
+++ b/ebean-test/src/main/java/module-info.java
@@ -1,5 +1,5 @@
-
-module io.ebean.test {
+// module must be open, so tests will pass
+open module io.ebean.test {
exports io.ebean.test;
exports io.ebean.test.config;
diff --git a/ebean-test/src/test/java/io/ebean/test/config/TestServerOffline.java b/ebean-test/src/test/java/io/ebean/test/config/TestServerOffline.java
new file mode 100644
index 0000000000..950fbc57dc
--- /dev/null
+++ b/ebean-test/src/test/java/io/ebean/test/config/TestServerOffline.java
@@ -0,0 +1,154 @@
+package io.ebean.test.config;
+
+
+import io.ebean.Database;
+import io.ebean.DatabaseFactory;
+import io.ebean.annotation.Platform;
+import io.ebean.config.DatabaseConfig;
+import io.ebean.datasource.DataSourceAlert;
+import io.ebean.datasource.DataSourceInitialiseException;
+import io.ebean.xtest.ForPlatform;
+
+import io.ebean.xtest.base.PlatformCondition;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.tests.model.basic.EBasicVer;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Properties;
+
+import javax.persistence.PersistenceException;
+import javax.sql.DataSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+@ExtendWith(PlatformCondition.class)
+public class TestServerOffline {
+
+ @Test
+ @ForPlatform({Platform.H2})
+ public void testOffline_default() throws SQLException {
+
+ String url = "jdbc:h2:mem:testoffline1";
+ try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) {
+ Properties props = props(url);
+ DatabaseConfig config = config(props);
+
+ assertThatThrownBy(() -> DatabaseFactory.create(config))
+ .isInstanceOf(DataSourceInitialiseException.class);
+ }
+
+ }
+
+ private static class LazyDatasourceInitializer implements DataSourceAlert {
+
+ public Database server;
+
+ private boolean initialized;
+
+ @Override
+ public void dataSourceUp(DataSource dataSource) {
+ if (!initialized) {
+ initDatabase();
+ }
+ }
+
+ public synchronized void initDatabase() {
+ if (!initialized) {
+ server.runDdl();
+ initialized = true;
+ }
+ }
+
+ @Override
+ public void dataSourceDown(DataSource dataSource, SQLException reason) {}
+
+ @Override
+ public void dataSourceWarning(DataSource dataSource, String msg) {}
+
+ }
+
+ @Test
+ @ForPlatform({Platform.H2})
+ public void testOffline_recovery() throws SQLException {
+
+ String url = "jdbc:h2:mem:testoffline3";
+ try (Connection bootup = DriverManager.getConnection(url, "sa", "secret")) {
+
+ Properties props = props(url);
+
+ // to bring up ebean without a database, we must disable various things
+ // that happen on startup
+ props.setProperty("datasource.h2_offline.failOnStart", "false");
+ props.setProperty("ebean.h2_offline.skipDataSourceCheck", "true");
+ props.setProperty("ebean.h2_offline.ddl.run", "false");
+ DatabaseConfig config = config(props);
+
+ LazyDatasourceInitializer alert = new LazyDatasourceInitializer() ;
+ config.getDataSourceConfig().setAlert(alert);
+ config.getDataSourceConfig().setHeartbeatFreqSecs(1);
+
+ Database h2Offline = DatabaseFactory.create(config);
+ alert.server = h2Offline;
+ assertThat(h2Offline).isNotNull();
+ // DB is online now in offline mode
+
+ // Accessing the DB will throw a PE
+ assertThatThrownBy(() -> alert.initDatabase())
+ .isInstanceOf(PersistenceException.class)
+ .hasMessageContaining("Failed to obtain connection to run DDL");
+
+ assertThatThrownBy(() -> h2Offline.find(EBasicVer.class).findCount()).isInstanceOf(PersistenceException.class);
+
+ // so - reset the password so that the server can reconnect
+ try (Statement stmt = bootup.createStatement()) {
+ stmt.execute("alter user sa set password 'sa'");
+ }
+
+ assertThat(alert.initialized).isFalse();
+
+ // next access to ebean should bring DS online
+ h2Offline.find(EBasicVer.class).findCount();
+ assertThat(alert.initialized).isTrue();
+
+ // check if server is working (ie ddl was run)
+ EBasicVer bean = new EBasicVer("foo");
+ h2Offline.save(bean);
+ assertThat(h2Offline.find(EBasicVer.class).findCount()).isEqualTo(1);
+ h2Offline.delete(bean);
+ }
+ }
+
+ private Properties props(String url) {
+
+ Properties props = new Properties();
+
+ props.setProperty("datasource.h2_offline.username", "sa");
+ props.setProperty("datasource.h2_offline.password", "sa");
+ props.setProperty("datasource.h2_offline.url", url);
+ props.setProperty("datasource.h2_offline.driver", "org.h2.Driver");
+
+ props.setProperty("ebean.h2_offline.databasePlatformName", "h2");
+ props.setProperty("ebean.h2_offline.ddl.extra", "false");
+
+ props.setProperty("ebean.h2_offline.ddl.generate", "true");
+ props.setProperty("ebean.h2_offline.ddl.run", "true");
+
+ return props;
+ }
+
+ private DatabaseConfig config(Properties props) {
+ DatabaseConfig config = new DatabaseConfig();
+ config.setName("h2_offline");
+ config.loadFromProperties(props);
+ config.setDefaultServer(false);
+ config.setRegister(false);
+ config.getClasses().add(EBasicVer.class);
+ return config;
+ }
+
+}
diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java
index 0dcc4afc28..2915657281 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_MultiTenancy_Test.java
@@ -49,6 +49,39 @@ public void create_new_server_with_multi_tenancy_db() {
+ /**
+ * Tests using multi tenancy per database
+ */
+ @Test
+ public void create_new_server_with_multi_tenancy_db_with_master() {
+
+ String tenant = "customer";
+ CurrentTenantProvider tenantProvider = Mockito.mock(CurrentTenantProvider.class);
+ Mockito.doReturn(tenant).when(tenantProvider).currentId();
+
+ TenantDataSourceProvider dataSourceProvider = Mockito.mock(TenantDataSourceProvider.class);
+
+ DatabaseConfig config = new DatabaseConfig();
+
+ config.setName("h2");
+ config.loadFromProperties();
+ config.setRegister(false);
+ config.setDefaultServer(false);
+ config.setDdlGenerate(false);
+ config.setDdlRun(false);
+
+ config.setTenantMode(TenantMode.DB_WITH_MASTER);
+ config.setCurrentTenantProvider(tenantProvider);
+ config.setTenantDataSourceProvider(dataSourceProvider);
+
+ Mockito.doReturn(config.getDataSource()).when(dataSourceProvider).dataSource(tenant);
+
+ config.setDatabasePlatform(new PostgresPlatform());
+
+ final Database database = DatabaseFactory.create(config);
+ database.shutdown();
+ }
+
/**
* Tests using multi tenancy per schema
*/
diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java
index e48034d9cd..1135963311 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/base/EbeanServerFactory_ServerConfigStart_Test.java
@@ -4,13 +4,53 @@
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.ServerConfigStartup;
+import io.ebeaninternal.api.SpiLogger;
+import io.ebeaninternal.api.SpiLoggerFactory;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.UTDetail;
+import java.util.HashSet;
+import java.util.Set;
+
import static org.assertj.core.api.Assertions.assertThat;
public class EbeanServerFactory_ServerConfigStart_Test {
+
+ /**
+ * Demo, how to intercept logging for a certain database.
+ * In conjunction to ServiceLoader, you can add different loggers to different databases
+ */
+ private static class MySpiLoggerFactory implements SpiLoggerFactory {
+ Set loggers = new HashSet<>();
+
+ @Override
+ public SpiLogger create(String name) {
+ loggers.add(name);
+ // just return a dummy here
+ return new SpiLogger() {
+ @Override
+ public boolean isDebug() {
+ return false;
+ }
+
+ @Override
+ public boolean isTrace() {
+ return false;
+ }
+
+ @Override
+ public void debug(String msg) {
+ }
+
+ @Override
+ public void trace(String msg) {
+ }
+ };
+
+ }
+ }
+
@Test
public void test() throws InterruptedException {
@@ -30,9 +70,13 @@ public void test() throws InterruptedException {
// act - register an instance
OnStartup onStartup = new OnStartup();
config.addServerConfigStartup(onStartup);
+ MySpiLoggerFactory loggerFactory = new MySpiLoggerFactory();
+ config.putServiceObject(SpiLoggerFactory.class, loggerFactory);
Database db = DatabaseFactory.create(config);
+ assertThat(loggerFactory.loggers).containsExactlyInAnyOrder("io.ebean.SQL", "io.ebean.SUM", "io.ebean.TXN");
+
assertThat(onStartup.calledWithConfig).isSameAs(config);
assertThat(OnStartupViaClass.calledWithConfig).isSameAs(config);
diff --git a/ebean-test/src/test/java/io/ebean/xtest/base/ServerStartTest.java b/ebean-test/src/test/java/io/ebean/xtest/base/ServerStartTest.java
new file mode 100644
index 0000000000..b0dfe787b9
--- /dev/null
+++ b/ebean-test/src/test/java/io/ebean/xtest/base/ServerStartTest.java
@@ -0,0 +1,15 @@
+package io.ebean.xtest.base;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import io.ebean.DatabaseFactory;
+
+public class ServerStartTest {
+
+ @Test
+ @Disabled("run manually")
+ void testServerStartAndMigrateDb2() throws Exception {
+ DatabaseFactory.create("db2-migration").shutdown();
+ }
+}
diff --git a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java
index 4f5471c62c..df9850d57f 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationDropHistoryTest.java
@@ -79,13 +79,11 @@ public static void main(String[] args) throws IOException {
List pendingDrops = migration.getPendingDrops();
assertThat(pendingDrops).contains("1.1");
- //System.setProperty("ddl.migration.pendingDropsFor", "1.1");
migration.setGeneratePendingDrop("1.1");
assertThat(migration.generateMigration()).isEqualTo("1.2__dropsFor_1.1");
assertThatThrownBy(()->migration.generateMigration())
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("No 'pendingDrops'"); // subsequent call
- System.clearProperty("ddl.migration.pendingDropsFor");
server.shutdown();
logger.info("end");
diff --git a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java
index df70742162..40706eb8b3 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationGenerateTest.java
@@ -4,6 +4,8 @@
import io.ebean.DatabaseFactory;
import io.ebean.annotation.Platform;
import io.ebean.config.DatabaseConfig;
+import io.ebeaninternal.api.DbOffline;
+
import io.ebean.dbmigration.DbMigration;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -17,7 +19,6 @@
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
@@ -96,7 +97,7 @@ public static void run(String pathToResources) throws IOException {
config.getProperties().put("ebean.hana.generateUniqueDdl", "true"); // need to generate unique statements to prevent them from being filtered out as duplicates by the DdlRunner
config.setPackages(Arrays.asList("misc.migration.v1_0"));
- Database server = DatabaseFactory.create(config);
+ Database server = createServer(config);
migration.setServer(server);
// then we generate migration scripts for v1_0
@@ -107,43 +108,28 @@ public static void run(String pathToResources) throws IOException {
// and now for v1_1
config.setPackages(Arrays.asList("misc.migration.v1_1"));
server.shutdown();
- server = DatabaseFactory.create(config);
+ server = createServer(config);
migration.setServer(server);
- assertThat(migration.generateMigration()).isEqualTo("1.1");
- assertThat(migration.generateMigration()).isNull(); // subsequent call
-
-
-
- System.setProperty("ddl.migration.pendingDropsFor", "1.1");
- assertThat(migration.generateMigration()).isEqualTo("1.2__dropsFor_1.1");
-
- assertThatThrownBy(()->migration.generateMigration())
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("No 'pendingDrops'"); // subsequent call
-
- System.clearProperty("ddl.migration.pendingDropsFor");
+ assertThat(migration.generateMigration()).isEqualTo("1.1,1.2__dropsFor_1.1");
assertThat(migration.generateMigration()).isNull(); // subsequent call
// and now for v1_2 with
config.setPackages(Arrays.asList("misc.migration.v1_2"));
server.shutdown();
- server = DatabaseFactory.create(config);
+ server = createServer(config);
migration.setServer(server);
- assertThat(migration.generateMigration()).isEqualTo("1.3");
- assertThat(migration.generateMigration()).isNull(); // subsequent call
-
-
- System.setProperty("ddl.migration.pendingDropsFor", "1.3");
- assertThat(migration.generateMigration()).isEqualTo("1.4__dropsFor_1.3");
- assertThatThrownBy(migration::generateMigration)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("No 'pendingDrops'"); // subsequent call
-
- System.clearProperty("ddl.migration.pendingDropsFor");
+ assertThat(migration.generateMigration()).isEqualTo("1.3,1.4__dropsFor_1.3");
assertThat(migration.generateMigration()).isNull(); // subsequent call
server.shutdown();
logger.info("end");
}
+ private static Database createServer(DatabaseConfig config) {
+ DbOffline.setGenerateMigration();
+ Database server = DatabaseFactory.create(config);
+ DbOffline.reset();
+ return server;
+ }
+
}
diff --git a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java
index 9a3cff37c5..4e85418f85 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/dbmigration/DbMigrationTest.java
@@ -97,6 +97,10 @@ public void testRunMigration() throws IOException, SQLException {
runScript("I__create_tablespaces.sql");
}
+ if(isDb2()) {
+ runScript("I__create_tablespaces.sql");
+ }
+
if (!isOracle()) {
// oracle.getMetaData is too slow. So skip this test
assertThat(getTables()).doesNotContain("migtest_QuOtEd");
diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java
index 3fa7f84b8a..a7ac708037 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java
@@ -278,6 +278,11 @@ public int saveAll(Object... beans) throws OptimisticLockException {
return 0;
}
+ @Override
+ public void visitSave(Object start, PersistVisitor visitor) {
+
+ }
+
@Override
public boolean delete(Object bean) throws OptimisticLockException {
return false;
@@ -627,4 +632,9 @@ public void loadBeanL2(EntityBeanIntercept ebi) {
public void loadBean(EntityBeanIntercept ebi) {
}
+
+ @Override
+ public void runDdl() {
+
+ }
}
diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/JdbcTransactionTest.java b/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/JdbcTransactionTest.java
index ce9e420b89..b306fef945 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/JdbcTransactionTest.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/internal/server/transaction/JdbcTransactionTest.java
@@ -105,4 +105,25 @@ public void preCommit() {
assertThat(postCommitCallCount.get()).isEqualTo(2); // postcommit executed twice
}
}
+
+ @Test
+ public void testFlushInCallback() {
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setBatchMode(true);
+ DB.currentTransaction().register(
+ new TransactionCallbackAdapter() {
+
+ @Override
+ public void preCommit() {
+ EBasic basic = new EBasic("binner1");
+ DB.save(basic);
+ }
+ }
+ );
+ EBasic basic = new EBasic("bouter1");
+ DB.save(basic);
+ transaction.commit(); // transaction will fail if recursive post-commit is failing
+ }
+ assertThat(DB.find(EBasic.class).where().eq("name", "binner1").exists()).isTrue();
+ }
}
diff --git a/ebean-test/src/test/java/io/ebean/xtest/text/PathPropertiesTests.java b/ebean-test/src/test/java/io/ebean/xtest/text/PathPropertiesTests.java
index 379b5bd927..b4723dc66b 100644
--- a/ebean-test/src/test/java/io/ebean/xtest/text/PathPropertiesTests.java
+++ b/ebean-test/src/test/java/io/ebean/xtest/text/PathPropertiesTests.java
@@ -1,5 +1,6 @@
package io.ebean.xtest.text;
+import io.ebean.CountDistinctOrder;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Query;
@@ -10,6 +11,9 @@
import org.slf4j.LoggerFactory;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
+import org.tests.model.composite.Model;
+import org.tests.model.composite.ModelSubEntity;
+import org.tests.model.composite.RCustomer;
import java.util.List;
@@ -48,4 +52,54 @@ void test_withAllPropsQuery() {
assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.shipping_address_id, t1.id, t1.line_1 from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id;");
}
+ @Test
+ void test_withConcat() {
+
+ LoggedSql.start();
+ Query query = DB.find(Customer.class)
+ .select("concat(name,billingAddress.line1)");
+ //.fetch("billingAddress", "concat(line1, line2, t0.name)");
+ //
+ query.findSingleAttributeList();
+ List sql = LoggedSql.stop();
+ assertThat(sql).hasSize(1);
+ assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.name, t0.smallnote, t0.anniversary, t0.cretime, t0.updtime, t0.version, t0.shipping_address_id, t1.id, t1.line_1 from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id;");
+ }
+
+ @Test
+ void test_withEmbedededId() {
+ ModelSubEntity entity1 = new ModelSubEntity();
+ DB.save(entity1);
+ ModelSubEntity entity2 = new ModelSubEntity();
+ DB.save(entity2);
+ ModelSubEntity entity3 = new ModelSubEntity();
+ DB.save(entity3);
+
+ Model model1 = new Model();
+ model1.setFrom(entity1);
+ model1.setTo(entity2);
+ model1.setDescription("model 1");
+ DB.save(model1);
+
+ Model model2 = new Model();
+ model2.setFrom(entity3);
+ model2.setTo(entity2);
+ model2.setDescription("model 2");
+ DB.save(model2);
+
+ Query defaultQuery = DB.find(Model.class);
+ PathProperties root = new PathProperties();
+ root.addToPath(null,"id.fromId");
+ //root.addToPath("id"","fromId"); so sah mein erster Versuch aus
+ LoggedSql.start();
+ Query query = defaultQuery.copy();
+ query.apply(root).where().isNotNull("id.fromId");
+ query.setDistinct(true).setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC);
+ query.setMaxRows(1);
+ List returnList = query.findSingleAttributeList();
+ List sql = LoggedSql.stop();
+ assertThat(sql).hasSize(1);
+ assertThat(sql.get(0)).contains("select distinct r1.attribute_, count(*) from (select distinct t0.from_id, t0.to_id, null as attribute_ from model t0 where t0.from_id is not null) r1 group by r1.attribute_ order by count(*) desc, r1.attribute_ limit 1;");
+ }
+
}
diff --git a/ebean-test/src/test/java/org/tests/basic/TPersistVisitor.java b/ebean-test/src/test/java/org/tests/basic/TPersistVisitor.java
new file mode 100644
index 0000000000..0d61a13f8a
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/basic/TPersistVisitor.java
@@ -0,0 +1,69 @@
+package org.tests.basic;
+
+import java.util.Collection;
+
+import io.ebean.DB;
+import io.ebean.PersistVisitor;
+import io.ebean.bean.EntityBean;
+import io.ebean.plugin.Property;
+/**
+ * Sample persist visitor that converts the visited beans in a XML-like structure
+ */
+public class TPersistVisitor implements PersistVisitor {
+
+ private final StringBuilder sb;
+ private final String indent;
+ private final String tag;
+ private boolean empty = true;
+
+ public TPersistVisitor() {
+ this(new StringBuilder(), "", "root");
+ }
+ private TPersistVisitor(StringBuilder sb, String indent, String tag) {
+ this.sb = sb;
+ this.indent = indent;
+ this.tag = tag;
+ this.sb.append(indent).append('<').append(tag);
+ }
+
+ TPersistVisitor newVisitor(String tag) {
+ if (empty) {
+ sb.append(">\n");
+ empty = false;
+ }
+ return new TPersistVisitor(sb, indent + " ", tag);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (empty) {
+ sb.append("/>\n");
+ } else {
+ this.sb.append(indent).append("").append(tag).append(">\n");
+ }
+ }
+
+ TPersistVisitor attr(String attr, Object value) {
+ sb.append(' ').append(attr).append('=').append('\'').append(value).append('\'');
+ return this;
+ }
+
+ public TPersistVisitor visitBean(EntityBean bean) {
+ return newVisitor("bean").attr("type", bean.getClass().getSimpleName()).attr("newOrDirty", DB.beanState(bean).isNewOrDirty());
+ }
+
+ @Override
+ public PersistVisitor visitProperty(Property prop) {
+ return newVisitor("property").attr("name", prop.name());
+ }
+
+ @Override
+ public PersistVisitor visitCollection(Collection> collection) {
+ return newVisitor("collection").attr("size", collection.size());
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+ }
\ No newline at end of file
diff --git a/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java b/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java
index f3dc340dc0..13533f8b4a 100644
--- a/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java
+++ b/ebean-test/src/test/java/org/tests/basic/TestM2MCascadeOne.java
@@ -1,12 +1,14 @@
package org.tests.basic;
-import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Query;
+import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.MRole;
import org.tests.model.basic.MUser;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class TestM2MCascadeOne extends BaseTestCase {
@Test
@@ -30,8 +32,43 @@ public void test() {
u1.addRole(r0);
u1.addRole(r1);
+ TPersistVisitor tv = new TPersistVisitor();
+ DB.visitSave(u1, tv);
+ assertThat(tv.toString()).isEqualTo("\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "\n");
+
+
DB.save(u1);
+ u1 = DB.find(MUser.class, u.getUserid());
+
+ tv = new TPersistVisitor();
+ DB.visitSave(u1, tv);
+ // collection is unloaded
+ assertThat(tv.toString()).isEqualTo("\n"
+ + " \n"
+ + "\n");
+
+ r1 = DB.find(MRole.class, r1.getRoleid());
+ tv = new TPersistVisitor();
+ r1.getUsers().add(new MUser());
+
+ DB.visitSave(r1, tv);
+ assertThat(tv.toString()).isEqualTo("\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "\n");
}
@Test
diff --git a/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java b/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java
index c8a374302f..f4b35ae275 100644
--- a/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java
+++ b/ebean-test/src/test/java/org/tests/basic/TestManyOneInterface.java
@@ -5,27 +5,51 @@
import org.junit.jupiter.api.Test;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.interfaces.Address;
+import org.tests.model.interfaces.ExtPerson1and2;
import org.tests.model.interfaces.IAddress;
+import org.tests.model.interfaces.IExtPerson1;
+import org.tests.model.interfaces.IExtPerson2;
import org.tests.model.interfaces.IPerson;
import org.tests.model.interfaces.Person;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class TestManyOneInterface extends BaseTestCase {
@Test
- public void test() {
-
+ public void testEntityOverrideEntityImplements() {
ResetBasicData.reset();
- IAddress a = new Address("hello");
+ IAddress a = DB.getDefault().createEntityBean(IAddress.class);
+ assertThat(a).isInstanceOf(Address.class);
- IPerson p = new Person();
+ IPerson p = DB.getDefault().createEntityBean(Person.class);
+ assertThat(p).isInstanceOf(ExtPerson1and2.class);
+ p = DB.getDefault().pluginApi().createEntityBean(IPerson.class);
+ assertThat(p).isInstanceOf(ExtPerson1and2.class);
p.setDefaultAddress(a);
- DB.save(a);
+ IAddress ea1 = DB.getDefault().createEntityBean(IAddress.class);
+ IAddress ea2 = DB.getDefault().createEntityBean(IAddress.class);
+
+ p.getExtraAddresses().add(ea1);
+ p.getExtraAddresses().add(ea2);
+
DB.save(p);
- //Assert.assertTrue();
+ IAddress a2 = DB.find(IAddress.class, a.getOid());
+ IPerson p2 = DB.find(IPerson.class, p.getOid());
+
+ assertThat(a2).isInstanceOf(Address.class);
+ assertThat(p2).isNotNull().isInstanceOf(ExtPerson1and2.class);
+ assertThat(p2.getDefaultAddress()).isInstanceOf(Address.class);
+
+ // some more checks
+ IExtPerson1 pe1 = DB.getDefault().pluginApi().createEntityBean(IExtPerson1.class);
+ IExtPerson2 pe2 = DB.getDefault().pluginApi().createEntityBean(IExtPerson2.class);
+ assertThat(pe1).isInstanceOf(ExtPerson1and2.class);
+ assertThat(pe2).isInstanceOf(ExtPerson1and2.class);
}
}
diff --git a/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java b/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java
index feff29a687..6e254ea6c7 100644
--- a/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java
+++ b/ebean-test/src/test/java/org/tests/cache/TestQueryCache.java
@@ -91,12 +91,12 @@ public void findSingleAttributeList() {
// ensure that findCount & findSingleAttribute use different
// slots in cache. If not a "Cannot cast List to int" should happen.
int count = DB
- .find(EColAB.class)
- .setUseQueryCache(true)
- .select("columnA")
- .where()
- .eq("columnB", "SingleAttribute")
- .findCount();
+ .find(EColAB.class)
+ .setUseQueryCache(true)
+ .select("columnA")
+ .where()
+ .eq("columnB", "SingleAttribute")
+ .findCount();
assertThat(count).isEqualTo(2);
}
@@ -305,7 +305,7 @@ public void testReadOnlyFind() {
assertSame(list, list2B);
List list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where()
- .ilike("name", "Rob").findList();
+ .ilike("name", "Rob").findList();
assertNotSame(list, list3);
BeanCollection bc3 = (BeanCollection) list3;
@@ -349,10 +349,10 @@ public void findIds() {
// and now, ensure that we hit the database
LoggedSql.start();
colA_second = DB.find(EColAB.class)
- .setUseQueryCache(CacheMode.PUT)
- .where()
- .eq("columnB", "someId")
- .findIds();
+ .setUseQueryCache(CacheMode.PUT)
+ .where()
+ .eq("columnB", "someId")
+ .findIds();
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
@@ -361,15 +361,15 @@ public void findIds() {
@Test
public void findCountDifferentQueriesBit() {
DB.getDefault().pluginApi().cacheManager().clearAll();
- differentFindCount(q->q.bitwiseAny("id",1), q->q.bitwiseAny("id",0));
- differentFindCount(q->q.bitwiseAll("id",1), q->q.bitwiseAll("id",0));
+ differentFindCount(q -> q.bitwiseAny("id", 1), q -> q.bitwiseAny("id", 0));
+ differentFindCount(q -> q.bitwiseAll("id", 1), q -> q.bitwiseAll("id", 0));
// differentFindCount(q->q.bitwiseNot("id",1), q->q.bitwiseNot("id",0)); NOT 1 == AND 1 = 0
- differentFindCount(q->q.bitwiseAnd("id",1, 0), q->q.bitwiseAnd("id",1, 1));
+ differentFindCount(q -> q.bitwiseAnd("id", 1, 0), q -> q.bitwiseAnd("id", 1, 1));
- differentFindCount(q->q.bitwiseAnd("id",2, 0), q->q.bitwiseAnd("id",4, 0));
- differentFindCount(q->q.bitwiseAnd("id",2, 1), q->q.bitwiseAnd("id",4, 1));
+ differentFindCount(q -> q.bitwiseAnd("id", 2, 0), q -> q.bitwiseAnd("id", 4, 0));
+ differentFindCount(q -> q.bitwiseAnd("id", 2, 1), q -> q.bitwiseAnd("id", 4, 1));
// Will produce hash collision
- differentFindCount(q->q.bitwiseAnd("id",10, 0), q->q.bitwiseAnd("id",0, 928210));
+ differentFindCount(q -> q.bitwiseAnd("id", 10, 0), q -> q.bitwiseAnd("id", 0, 928210));
}
diff --git a/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java b/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java
index 35f0bf271e..cd5e0415aa 100644
--- a/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java
+++ b/ebean-test/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java
@@ -5,11 +5,13 @@
import io.ebean.DataIntegrityException;
import io.ebean.xtest.IgnorePlatform;
import io.ebean.annotation.Platform;
+import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class TestInsertDataIntegrityException extends BaseTestCase {
@@ -26,7 +28,10 @@ public void insert_invalidForeignKey() {
Order order = new Order();
order.setStatus(Order.Status.NEW);
order.setCustomer(invalidCustomer);
+ assertThatThrownBy(() -> DB.save(order))
+ .isInstanceOf(DataIntegrityException.class)
+ .hasMessageContaining("insert into o_order")
+ .hasMessageContaining("900000");
- assertThrows(DataIntegrityException.class, () -> DB.save(order));
}
}
diff --git a/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java b/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java
index 27434601ef..0e6c7d60fb 100644
--- a/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java
+++ b/ebean-test/src/test/java/org/tests/json/TestDbJson_List.java
@@ -2,19 +2,33 @@
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
+import io.ebean.Database;
+import io.ebean.DatabaseFactory;
+import io.ebean.ValuePair;
import io.ebean.xtest.ForPlatform;
+import io.ebean.annotation.MutationDetection;
import io.ebean.annotation.Platform;
+import io.ebean.config.DatabaseConfig;
import io.ebean.test.LoggedSql;
import io.ebean.text.TextException;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.tests.model.json.EBasicJsonList;
import org.tests.model.json.PlainBean;
import javax.persistence.PersistenceException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
public class TestDbJson_List extends BaseTestCase {
@@ -231,4 +245,43 @@ public void testNullToEmpty() {
assertThat(bean.getTags()).isEmpty();
assertThat(bean.getBeanMap()).isEmpty();
}
+
+ @Test
+ @ForPlatform(Platform.H2)
+ @Disabled("breaks everything")
+ public void testDirtyValues() {
+ DatabaseConfig config = new DatabaseConfig();
+ config.loadFromProperties();
+ config.setDefaultServer(true);
+ config.setRegister(true);
+ config.setDdlRun(false);
+ config.setJsonMutationDetection(MutationDetection.SOURCE);
+ Database db = DatabaseFactory.create(config);
+ try {
+ assertThat(db).isNotNull();
+
+ EBasicJsonList bean = new EBasicJsonList();
+ bean.getTags().add("aa");
+ bean.getTags().add("bb");
+
+ db.save(bean);
+ bean = db.find(EBasicJsonList.class, bean.getId());
+
+ bean.getTags().add("cc");
+ final Map dirtyValues = db.beanState(bean).dirtyValues();
+ assertThat(dirtyValues).containsOnlyKeys("tags");
+
+ final ValuePair diff = dirtyValues.get("tags");
+ assertThat(diff.getOldValue()).isInstanceOf(List.class).asList()
+ .containsExactly("aa", "bb");
+ assertThat(diff.getNewValue()).isInstanceOf(List.class).asList()
+ .containsExactly("aa", "bb", "cc");
+ } finally {
+ if (db != null) {
+ db.shutdown();
+ }
+ }
+
+
+ }
}
diff --git a/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java b/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java
index 8f36be0cbc..280b4221cb 100644
--- a/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java
+++ b/ebean-test/src/test/java/org/tests/lazyforeignkeys/MainEntityRelation.java
@@ -1,6 +1,7 @@
package org.tests.lazyforeignkeys;
import io.ebean.annotation.DbForeignKey;
+import io.ebean.annotation.NotNull;
import org.tests.model.basic.Cat;
import javax.persistence.*;
@@ -29,6 +30,12 @@ public class MainEntityRelation {
@DbForeignKey(noConstraint = true)
private Cat cat;
+ @ManyToOne
+ @NotNull
+ @JoinColumn(name = "cat2_id")
+ @DbForeignKey(noConstraint = true)
+ private Cat cat2;
+
private String attr1;
public MainEntity getEntity1() {
@@ -55,6 +62,14 @@ public void setCat(Cat cat) {
this.cat = cat;
}
+ public Cat getCat2() {
+ return cat2;
+ }
+
+ public void setCat2(Cat cat2) {
+ this.cat2 = cat2;
+ }
+
public String getAttr1() {
return attr1;
}
diff --git a/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java b/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java
index f88f148297..771862a953 100644
--- a/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java
+++ b/ebean-test/src/test/java/org/tests/lazyforeignkeys/TestLazyForeignKeys.java
@@ -10,9 +10,11 @@
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Cat;
+import javax.persistence.EntityNotFoundException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;
public class TestLazyForeignKeys extends BaseTestCase {
@@ -32,6 +34,10 @@ public void prepare() {
rel1.setEntity1(e1);
rel1.setEntity2(e2);
+
+ Cat cat = new Cat();
+ cat.setId(4711L);
+ rel1.setCat2(cat);
DB.save(rel1);
}
@@ -57,13 +63,25 @@ public void testFindOne() throws Exception {
List sql = LoggedSql.stop();
assertThat(sql).hasSize(3);
- assertSql(sql.get(0)).contains("select t0.id, t0.attr1, t0.id1, t0.id2, t1.species, t0.cat_id from main_entity_relation t0 left join animal t1 on t1.id = t0.cat_id");
+ assertSql(sql.get(0)).contains("select t0.id, t0.attr1, t0.id1, t0.id2, t1.species, t0.cat_id, t2.species, t0.cat2_id "
+ + "from main_entity_relation t0 left join animal t1 on t1.id = t0.cat_id left join animal t2 on t2.id = t0.cat2_id");
if (isSqlServer() || isOracle()) {
assertSql(sql.get(1)).contains("select t0.id, t0.attr1, t0.attr2, CASE WHEN t0.id is null THEN 1 ELSE 0 END from main_entity t0");
} else {
assertSql(sql.get(1)).contains("select t0.id, t0.attr1, t0.attr2, t0.id is null from main_entity t0");
assertSql(sql.get(2)).contains("select t0.id, t0.attr1, t0.attr2, t0.id is null from main_entity t0");
}
+
+ assertThat(rel1.getCat2().getId()).isEqualTo(4711L);
+ assertThatThrownBy(() -> rel1.getCat2().getName()).isInstanceOf(EntityNotFoundException.class);
+
+ Cat cat = new Cat();
+ cat.setId(4711L);
+ cat.setName("miau");
+ DB.save(cat);
+
+ DB.refresh(rel1);
+ assertThat(rel1.getCat2().getName()).isEqualTo("miau");
}
@Test
diff --git a/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java b/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java
new file mode 100644
index 0000000000..010d378603
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/basic/MDateTime.java
@@ -0,0 +1,88 @@
+package org.tests.model.basic;
+
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.MonthDay;
+import java.time.OffsetDateTime;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+
+import javax.annotation.Nullable;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@Entity
+public class MDateTime {
+
+ @Id
+ private Integer id;
+
+ @Nullable
+ private LocalTime localTime;
+
+ @Nullable
+ private LocalDateTime localDateTime;
+
+ @Nullable
+ private LocalDate localDate;
+
+ @Nullable
+ private OffsetDateTime offsetDateTime;
+
+ @Nullable
+ private ZonedDateTime zonedDateTime;
+
+ @Nullable
+ private YearMonth propYearMonth;
+
+ @Nullable
+ private MonthDay propMonthDay;
+
+ @Nullable
+ private Year propYear;
+
+ @Nullable
+ private Instant propInstant;
+
+ @Nullable
+ private Calendar propCalendar;
+
+ @Nullable
+ private Timestamp propTimestamp;
+
+ @Nullable
+ private java.sql.Date sqlDate;
+
+ @Nullable
+ private java.sql.Time sqlTime;
+
+ @Nullable
+ private java.util.Date utilDate;
+
+ @Nullable
+ private org.joda.time.DateTime jodaDateTime;
+
+ @Nullable
+ private org.joda.time.LocalDateTime jodaLocalDateTime;
+
+ @Nullable
+ private org.joda.time.LocalDate jodaLocalDate;
+
+ @Nullable
+ private org.joda.time.LocalTime jodaLocalTime;
+
+ @Nullable
+ private org.joda.time.DateMidnight jodaDateMidnight;
+
+ public Integer getId() {
+ return id;
+ }
+ public void setId(Integer id) {
+ this.id = id;
+ }
+}
diff --git a/ebean-test/src/test/java/org/tests/model/basic/MRole.java b/ebean-test/src/test/java/org/tests/model/basic/MRole.java
index c66a1db607..47ded509d9 100644
--- a/ebean-test/src/test/java/org/tests/model/basic/MRole.java
+++ b/ebean-test/src/test/java/org/tests/model/basic/MRole.java
@@ -12,7 +12,7 @@ public class MRole {
String roleName;
- @ManyToMany(cascade = CascadeType.ALL)
+ @ManyToMany
List users;
public MRole() {
diff --git a/ebean-test/src/test/java/org/tests/model/composite/Model.java b/ebean-test/src/test/java/org/tests/model/composite/Model.java
new file mode 100644
index 0000000000..84904cf261
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/composite/Model.java
@@ -0,0 +1,62 @@
+package org.tests.model.composite;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+
+/*
+ * Licensed Materials - Property of FOCONIS AG
+ * (C) Copyright FOCONIS AG.
+ * @author
+ */
+@Entity
+public class Model {
+
+ @EmbeddedId
+ private ModelKey id;
+
+ private String description;
+
+ @ManyToOne
+ private ModelSubEntity from;
+
+ @ManyToOne
+ private ModelSubEntity to;
+
+ public Model() {
+
+ }
+
+ public ModelKey getId() {
+ return id;
+ }
+
+ public void setId(ModelKey id) {
+ this.id = id;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public ModelSubEntity getFrom() {
+ return from;
+ }
+
+ public void setFrom(ModelSubEntity from) {
+ this.from = from;
+ }
+
+ public ModelSubEntity getTo() {
+ return to;
+ }
+
+ public void setTo(ModelSubEntity to) {
+ this.to = to;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/composite/ModelKey.java b/ebean-test/src/test/java/org/tests/model/composite/ModelKey.java
new file mode 100644
index 0000000000..696041158f
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/composite/ModelKey.java
@@ -0,0 +1,54 @@
+package org.tests.model.composite;
+
+import javax.persistence.Embeddable;
+import java.util.UUID;
+
+/*
+ * Licensed Materials - Property of FOCONIS AG
+ * (C) Copyright FOCONIS AG.
+ * @author
+ */
+@Embeddable
+public class ModelKey {
+
+ @io.ebean.annotation.NotNull
+ private UUID fromId;
+
+ @io.ebean.annotation.NotNull
+ private UUID toId;
+
+ public UUID getFromId() {
+ return fromId;
+ }
+
+ public void setFromId(UUID fromId) {
+ this.fromId = fromId;
+ }
+
+ public UUID getToId() {
+ return toId;
+ }
+
+ public void setToId(UUID toId) {
+ this.toId = toId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+
+ return hash;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/composite/ModelSubEntity.java b/ebean-test/src/test/java/org/tests/model/composite/ModelSubEntity.java
new file mode 100644
index 0000000000..a1785bd4f5
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/composite/ModelSubEntity.java
@@ -0,0 +1,27 @@
+package org.tests.model.composite;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import java.util.UUID;
+
+/*
+ * Licensed Materials - Property of FOCONIS AG
+ * (C) Copyright FOCONIS AG.
+ * @author
+ */
+@Entity
+public class ModelSubEntity {
+ @Id
+ @GeneratedValue
+ private UUID id;
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/Address.java b/ebean-test/src/test/java/org/tests/model/interfaces/Address.java
index 052ef63f87..454616feac 100644
--- a/ebean-test/src/test/java/org/tests/model/interfaces/Address.java
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/Address.java
@@ -1,10 +1,14 @@
package org.tests.model.interfaces;
+import io.ebean.annotation.ext.EntityImplements;
+
import javax.persistence.Entity;
import javax.persistence.Id;
+import javax.persistence.ManyToOne;
import javax.persistence.Version;
@Entity
+@EntityImplements(IAddress.class)
public class Address implements IAddress {
@Id
@@ -15,6 +19,9 @@ public class Address implements IAddress {
private String street;
+ @ManyToOne
+ private Person extraAddress;
+
public Address(String street) {
this.street = street;
}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1.java b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1.java
new file mode 100644
index 0000000000..dc9fac9e09
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1.java
@@ -0,0 +1,25 @@
+package org.tests.model.interfaces;
+
+import io.ebean.annotation.ext.EntityImplements;
+import io.ebean.annotation.ext.EntityOverride;
+
+import javax.persistence.Entity;
+
+@Entity()
+@EntityImplements(IExtPerson1.class)
+@EntityOverride(priority = 30)
+public class ExtPerson1 extends Person implements IExtPerson1 {
+
+ private int myField1;
+
+ @Override
+ public int getMyField1() {
+ return myField1;
+ }
+
+ @Override
+ public void setMyField1(int myField1) {
+ this.myField1 = myField1;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1and2.java b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1and2.java
new file mode 100644
index 0000000000..a6d2661fd8
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson1and2.java
@@ -0,0 +1,26 @@
+package org.tests.model.interfaces;
+
+import io.ebean.annotation.ext.EntityImplements;
+import io.ebean.annotation.ext.EntityOverride;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity()
+@Table(name = "person")
+@EntityImplements(IExtPerson2.class)
+@EntityOverride(priority = -30)
+public class ExtPerson1and2 extends ExtPerson1 implements IExtPerson2 {
+
+ private int myField2;
+
+ @Override
+ public int getMyField2() {
+ return myField2;
+ }
+
+ @Override
+ public void setMyField2(int myField2) {
+ this.myField2 = myField2;
+ }
+}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson2.java b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson2.java
new file mode 100644
index 0000000000..670f73ad36
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/ExtPerson2.java
@@ -0,0 +1,27 @@
+package org.tests.model.interfaces;
+
+import io.ebean.annotation.ext.EntityImplements;
+import io.ebean.annotation.ext.EntityOverride;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@EntityOverride(priority = 20)
+@EntityImplements(IExtPerson2.class)
+@Table(name = "person")
+public class ExtPerson2 extends Person implements IExtPerson2 {
+
+ private int myField2;
+
+ @Override
+ public int getMyField2() {
+ return myField2;
+ }
+
+ @Override
+ public void setMyField2(int myField2) {
+ this.myField2 = myField2;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java b/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java
index 78b6d20050..9097d23d76 100644
--- a/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/IAddress.java
@@ -1,6 +1,9 @@
package org.tests.model.interfaces;
public interface IAddress {
+
+ long getOid();
+
String getStreet();
void setStreet(String s);
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson1.java b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson1.java
new file mode 100644
index 0000000000..b81a5266d6
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson1.java
@@ -0,0 +1,7 @@
+package org.tests.model.interfaces;
+
+public interface IExtPerson1 extends IPerson {
+ int getMyField1();
+
+ void setMyField1(int myField1);
+}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson2.java b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson2.java
new file mode 100644
index 0000000000..49bf601339
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/IExtPerson2.java
@@ -0,0 +1,7 @@
+package org.tests.model.interfaces;
+
+public interface IExtPerson2 extends IPerson {
+ int getMyField2();
+
+ void setMyField2(int myField2);
+}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java b/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java
index 0d07ecc6b1..3c1366185d 100644
--- a/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/IPerson.java
@@ -1,7 +1,16 @@
package org.tests.model.interfaces;
+import java.util.List;
+
public interface IPerson {
+
+ long getOid();
+
IAddress getDefaultAddress();
void setDefaultAddress(IAddress address);
+
+ List getExtraAddresses();
+
+ List getAddressLinks();
}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/Person.java b/ebean-test/src/test/java/org/tests/model/interfaces/Person.java
index 5edbb68c7c..a2e5e8dafd 100644
--- a/ebean-test/src/test/java/org/tests/model/interfaces/Person.java
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/Person.java
@@ -1,11 +1,19 @@
package org.tests.model.interfaces;
+import io.ebean.annotation.ext.EntityImplements;
+
+import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
+import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
import javax.persistence.Version;
+import java.util.ArrayList;
+import java.util.List;
@Entity
+@EntityImplements(IPerson.class)
public class Person implements IPerson {
@Id
private long oid;
@@ -13,9 +21,15 @@ public class Person implements IPerson {
@Version
private int version;
- @ManyToOne(targetEntity = Address.class)
+ @ManyToOne(cascade = CascadeType.PERSIST)
private IAddress defaultAddress;
+ @OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true)
+ private List extraAddresses = new ArrayList<>();
+
+ @ManyToMany(cascade = CascadeType.PERSIST)
+ private List addressLinks = new ArrayList<>();
+
@Override
public IAddress getDefaultAddress() {
return defaultAddress;
@@ -42,4 +56,14 @@ public void setVersion(int version) {
this.version = version;
}
+ @Override
+ public List getExtraAddresses() {
+ return extraAddresses;
+ }
+
+ @Override
+ public List getAddressLinks() {
+ return addressLinks;
+ }
+
}
diff --git a/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java b/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java
index aa7030ef1d..f2ed05c573 100644
--- a/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java
+++ b/ebean-test/src/test/java/org/tests/model/interfaces/TestTargetEntity.java
@@ -30,7 +30,7 @@ public void test() {
private Person setup() {
Address address = new Address("street");
DB.save(address);
- Person person = new Person();
+ Person person = DB.getDefault().createEntityBean(Person.class);
person.setDefaultAddress(address);
DB.save(person);
return person;
diff --git a/ebean-test/src/test/java/org/tests/model/m2m/MnyEdge.java b/ebean-test/src/test/java/org/tests/model/m2m/MnyEdge.java
index 28e7636a49..c78b3a2cc7 100644
--- a/ebean-test/src/test/java/org/tests/model/m2m/MnyEdge.java
+++ b/ebean-test/src/test/java/org/tests/model/m2m/MnyEdge.java
@@ -20,6 +20,20 @@ public class MnyEdge {
@ManyToOne
private MnyNode to;
+ public MnyEdge() {
+ }
+
+ public MnyEdge(Object from, Object to) {
+ this.from = (MnyNode) from;
+ this.to = (MnyNode) to;
+ this.id = this.from.id * 10000 + this.to.id;
+ this.flags = this.from.id + this.to.id;
+ }
+
+ public static MnyEdge createReverseRelation(Object to, MnyNode from) {
+ return new MnyEdge(from, to);
+ }
+
private int flags;
public Integer getId() {
diff --git a/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java b/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java
index fabb580403..71444320fe 100644
--- a/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java
+++ b/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java
@@ -3,6 +3,7 @@
import io.ebean.annotation.Identity;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Where;
+import io.ebean.annotation.ext.IntersectionFactory;
import javax.persistence.*;
import java.util.List;
@@ -16,16 +17,19 @@ public class MnyNode {
String name;
- @ManyToMany
+ @ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"))
+ @IntersectionFactory(MnyEdge.class)
+ @Where(clause = "${mta}.flags != 12345 and '${dbTableName}' = 'mny_node'")
List allRelations;
- @ManyToMany
+ @ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"))
+ @IntersectionFactory(value =MnyEdge.class, factoryMethod = "createReverseRelation")
List allReverseRelations;
@ManyToMany
diff --git a/ebean-test/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java b/ebean-test/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java
index d9ac37ffcc..2557d35e72 100644
--- a/ebean-test/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java
+++ b/ebean-test/src/test/java/org/tests/model/m2m/TestM2MWithWhere.java
@@ -1,8 +1,8 @@
package org.tests.model.m2m;
-import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
+import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -14,10 +14,78 @@
* Tests M2M with complex where queries.
*
* @author Roland Praml, FOCONIS AG
- *
*/
public class TestM2MWithWhere extends BaseTestCase {
+ @Test
+ public void testModify() throws Exception {
+
+ MnyNode node1 = new MnyNode();
+ node1.setName("node1");
+ node1.setId(111);
+ MnyNode node2 = new MnyNode();
+ node2.setName("node2");
+ node2.setId(222);
+ MnyNode node3 = new MnyNode();
+ node3.setName("node3");
+ node3.setId(333);
+ MnyNode node4 = new MnyNode();
+ node4.setName("node4");
+ node4.setId(444);
+
+ node1.getAllReverseRelations().add(node2);
+ node1.getAllRelations().add(node2);
+ node2.getAllRelations().add(node3);
+ node3.getAllRelations().add(node4);
+ DB.save(node1);
+ DB.save(node1);
+
+ DB.refresh(node2);
+ DB.refresh(node3);
+ assertThat(node2.getAllRelations()).containsExactlyInAnyOrder(node1, node3);
+ assertThat(node3.getAllReverseRelations()).containsExactlyInAnyOrder(node2);
+
+ DB.refresh(node1);
+ node1.getAllReverseRelations().clear();
+ System.out.println("Clearing");
+ DB.save(node1);
+ DB.refresh(node2);
+ assertThat(node2.getAllRelations()).containsExactlyInAnyOrder(node3);
+
+ node2.getAllRelations().clear();
+ node2.getAllRelations().add(node3);
+ LoggedSql.start();
+ DB.save(node2);
+ LoggedSql.stop().forEach(System.out::println);
+
+ }
+
+ @Test
+ public void testAccessAndModify() throws Exception {
+ createTestData();
+
+ MnyNode node = DB.find(MnyNode.class, 1);
+ node.setName("fooBarBaz");
+ MnyNode removed = node.getAllRelations().remove(0);
+
+ LoggedSql.start();
+ DB.save(node);
+ List sql = LoggedSql.stop();
+ assertThat(sql).hasSize(3);
+ assertThat(sql.get(0)).contains("update mny_node set name=? where id=?; -- bind(fooBarBaz");
+ assertThat(sql.get(1)).contains("delete from mny_edge where from_id = ? and to_id = ? and mny_edge.flags != 12345 and 'mny_node' = 'mny_node'");
+ assertThat(sql.get(2)).contains("-- bind");
+
+ node.getAllRelations().add(removed);
+ LoggedSql.start();
+ DB.save(node);
+ sql = LoggedSql.stop();
+ assertThat(sql).hasSize(2);
+ assertThat(sql.get(0)).contains("insert into mny_edge (id, flags, from_id, to_id) values (?,?,?,?)");
+ assertThat(sql.get(1)).contains("-- bind");
+
+ }
+
@Test
public void testQuery() throws Exception {
createTestData();
@@ -70,9 +138,9 @@ public void testGetter() throws Exception {
// prefetch everything
LoggedSql.start();
node = DB.find(MnyNode.class)
- .fetch("bit1Relations","*")
- .fetch("bit1ReverseRelations","*")
- .where().idEq(3).findOne();
+ .fetch("bit1Relations", "*")
+ .fetch("bit1ReverseRelations", "*")
+ .where().idEq(3).findOne();
sqls = LoggedSql.stop();
assertThat(sqls).hasSize(2);
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoBMaster.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoBMaster.java
index d9a6944765..d7b5d39f62 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/OtoBMaster.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoBMaster.java
@@ -10,7 +10,7 @@ public class OtoBMaster {
String name;
- @OneToOne(cascade = CascadeType.ALL, mappedBy = "master", fetch = FetchType.LAZY)
+ @OneToOne(cascade = CascadeType.ALL, mappedBy = "master", fetch = FetchType.LAZY, optional = false)
OtoBChild child;
public Long getId() {
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrime.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrime.java
index 36d7104c2d..afc224ab45 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrime.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrime.java
@@ -1,9 +1,6 @@
package org.tests.model.onetoone;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.OneToOne;
-import javax.persistence.Version;
+import javax.persistence.*;
import java.util.UUID;
@Entity
@@ -17,7 +14,7 @@ public class OtoUBPrime {
/**
* Master side of bi-directional PrimaryJoinColumn.
*/
- @OneToOne(mappedBy = "prime")
+ @OneToOne(mappedBy = "prime", optional = false)
OtoUBPrimeExtra extra;
@Version
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java
index df14dee017..ef8532a15f 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUBPrimeExtra.java
@@ -14,7 +14,7 @@ public class OtoUBPrimeExtra {
/**
* Child side of bi-directional PrimaryJoinColumn.
*/
- @OneToOne
+ @OneToOne(optional = false)
@PrimaryKeyJoinColumn
OtoUBPrime prime;
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrime.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrime.java
index e4371304fa..f8ad34b0fd 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrime.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrime.java
@@ -13,15 +13,27 @@ public class OtoUPrime {
String name;
+
/**
* Effectively Ebean automatically sets Cascade PERSIST and mapped by for PrimaryKeyJoinColumn.
- * This OneToOne is optional so left join to extra.
+ * This OneToOne is not optional so use inner join to extra (unless DbForeignkey(noConstraint = true) is set)
+ * Note: Violating the contract (Storing OtoUPrime without extra) may cause problems:
+ * - due the inner join, you might not get results from the query
+ * - you might get a "Beah has been deleted" if lazy load occurs on 'extra'
*/
- @OneToOne
+ @OneToOne(orphanRemoval = true, optional = false)
@PrimaryKeyJoinColumn
+ // enforcing left join - without 'noConstraint = true', an inner join is used
@DbForeignKey(noConstraint = true)
OtoUPrimeExtra extra;
+ /**
+ * This OneToOne is optional so left join to extra.
+ * Setting FetchType.LAZY will NOT add the left join by default to the query.
+ */
+ @OneToOne(mappedBy = "prime", fetch = FetchType.LAZY, orphanRemoval = true, optional = true)
+ OtoUPrimeOptionalExtra optionalExtra;
+
@Version
Long version;
@@ -65,4 +77,12 @@ public Long getVersion() {
public void setVersion(Long version) {
this.version = version;
}
+
+ public OtoUPrimeOptionalExtra getOptionalExtra() {
+ return optionalExtra;
+ }
+
+ public void setOptionalExtra(OtoUPrimeOptionalExtra optionalExtra) {
+ this.optionalExtra = optionalExtra;
+ }
}
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtra.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtra.java
index edd72b0ae3..311a4f6dbe 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtra.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtra.java
@@ -1,8 +1,8 @@
package org.tests.model.onetoone;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Version;
+import io.ebean.annotation.Formula;
+
+import javax.persistence.*;
import java.util.UUID;
@Entity
@@ -22,7 +22,7 @@ public OtoUPrimeExtra(String extra) {
@Override
public String toString() {
- return "exId:"+ eid +" "+extra;
+ return "exId:" + eid + " " + extra;
}
public UUID getEid() {
@@ -48,4 +48,5 @@ public Long getVersion() {
public void setVersion(Long version) {
this.version = version;
}
+
}
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtraWithConstraint.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtraWithConstraint.java
new file mode 100644
index 0000000000..9b69a3e7c7
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeExtraWithConstraint.java
@@ -0,0 +1,52 @@
+package org.tests.model.onetoone;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+import java.util.UUID;
+
+@Entity
+public class OtoUPrimeExtraWithConstraint {
+
+ @Id
+ UUID eid;
+
+ String extra;
+
+ @Version
+ Long version;
+
+ public OtoUPrimeExtraWithConstraint(String extra) {
+ this.extra = extra;
+ }
+
+ @Override
+ public String toString() {
+ return "exId:" + eid + " " + extra;
+ }
+
+ public UUID getEid() {
+ return eid;
+ }
+
+ public void setEid(UUID eid) {
+ this.eid = eid;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeOptionalExtra.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeOptionalExtra.java
new file mode 100644
index 0000000000..7ec0d0f1e3
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeOptionalExtra.java
@@ -0,0 +1,61 @@
+package org.tests.model.onetoone;
+
+import javax.persistence.*;
+import java.util.UUID;
+
+@Entity
+public class OtoUPrimeOptionalExtra {
+
+ @Id
+ UUID eid;
+
+ String extra;
+
+ @OneToOne(optional = false)
+ @PrimaryKeyJoinColumn
+ private OtoUPrime prime;
+
+ @Version
+ Long version;
+
+ public OtoUPrimeOptionalExtra(String extra) {
+ this.extra = extra;
+ }
+
+ @Override
+ public String toString() {
+ return "exId:" + eid + " " + extra;
+ }
+
+ public UUID getEid() {
+ return eid;
+ }
+
+ public void setEid(UUID eid) {
+ this.eid = eid;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public OtoUPrime getPrime() {
+ return prime;
+ }
+
+ public void setPrime(OtoUPrime prime) {
+ this.prime = prime;
+ }
+}
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeWithConstraint.java b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeWithConstraint.java
new file mode 100644
index 0000000000..29babe5685
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/OtoUPrimeWithConstraint.java
@@ -0,0 +1,63 @@
+package org.tests.model.onetoone;
+
+import javax.persistence.*;
+import java.util.UUID;
+
+@Entity
+public class OtoUPrimeWithConstraint {
+
+ @Id
+ UUID pid;
+
+ String name;
+
+ @OneToOne(orphanRemoval = true, optional = false)
+ // @DbForeignKey(noConstraint = true) see OtoUPrime
+ @PrimaryKeyJoinColumn
+ OtoUPrimeExtraWithConstraint extra;
+
+ @Version
+ Long version;
+
+ public OtoUPrimeWithConstraint(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "id:" + pid + " name:" + name + " extra:" + extra;
+ }
+
+ public UUID getPid() {
+ return pid;
+ }
+
+ public void setPid(UUID pid) {
+ this.pid = pid;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public OtoUPrimeExtraWithConstraint getExtra() {
+ return extra;
+ }
+
+ public void setExtra(OtoUPrimeExtraWithConstraint extra) {
+ this.extra = extra;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOneImportedPkNative.java b/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOneImportedPkNative.java
index 122e298e93..1aa765092e 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOneImportedPkNative.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOneImportedPkNative.java
@@ -34,7 +34,7 @@ public void findWithLazyOneToOne() {
String sql = sqlOf(query);
assertThat(sql).contains("select t0.id, t0.name from oto_bmaster t0 where t0.id ");
- assertThat(sql).doesNotContain("left join oto_bchild");
+ assertThat(sql).doesNotContain("join oto_bchild");
assertThat(one).isNotNull();
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java b/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java
index 81e8905ed7..63dd38bdde 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinBidi.java
@@ -45,7 +45,7 @@ public void insertUpdateDelete() {
OtoUBPrime oneWith = queryWithFetch.findOne();
assertThat(oneWith).isNotNull();
- assertThat(sqlOf(queryWithFetch, 10)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 left join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
+ assertThat(sqlOf(queryWithFetch, 10)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_ubprime t0 join oto_ubprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
.as("we join to oto_prime_extra");
assertThat(oneWith.getExtra().getExtra()).isEqualTo("v" + desc);
diff --git a/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java b/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java
index 39cbfa7309..3a9a4d6df2 100644
--- a/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java
+++ b/ebean-test/src/test/java/org/tests/model/onetoone/TestOneToOnePrimaryKeyJoinOptional.java
@@ -1,14 +1,21 @@
package org.tests.model.onetoone;
-import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Query;
+import io.ebean.plugin.Property;
import io.ebean.test.LoggedSql;
+import io.ebean.xtest.BaseTestCase;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import javax.persistence.EntityNotFoundException;
+import javax.persistence.PersistenceException;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.*;
public class TestOneToOnePrimaryKeyJoinOptional extends BaseTestCase {
@@ -21,23 +28,120 @@ private OtoUPrime insert(String desc) {
return prime;
}
- @Test
- public void insertWithoutExtra() {
+ @BeforeEach
+ void prepare() {
+ OtoUPrime p1Single = new OtoUPrime("Prime without optional");
+ p1Single.setExtra(new OtoUPrimeExtra("Non optional prime required"));
+ DB.save(p1Single);
+
+ OtoUPrimeExtra p2 = new OtoUPrimeExtra("SinglePrimeExtra");
+ try {
+ DB.save(p2);
+ fail("PrimExtra cannot exist without Prime");
+ } catch (PersistenceException pe) {
+
+ }
String desc = "" + System.currentTimeMillis();
OtoUPrime p1 = new OtoUPrime("u" + desc);
+ p1.setExtra(new OtoUPrimeExtra("u" + desc));
+ p1.setOptionalExtra(new OtoUPrimeOptionalExtra("This one has also an optional"));
DB.save(p1);
+ }
- Query query = DB.find(OtoUPrime.class)
- .setId(p1.getPid())
- .fetch("extra", "eid");
+ @AfterEach
+ void cleanup() {
+ DB.find(OtoUPrime.class).delete();
+ assertThat(DB.find(OtoUPrimeExtra.class).findList()).isEmpty();
+ assertThat(DB.find(OtoUPrimeOptionalExtra.class).findList()).isEmpty();
+ }
- OtoUPrime found = query.findOne();
+ public void doTest1(boolean extraFetch, boolean optionalFetch) {
+
+ // Query for "fetch" case - extra bean joined by left join
+
+ Query query1 = DB.find(OtoUPrime.class);
+ if (extraFetch) {
+ query1.fetch("extra");
+ }
+ if (optionalFetch) {
+ query1.fetch("optionalExtra");
+ }
+ List primes = query1.findList();
+ if (extraFetch && optionalFetch) {
+ assertThat(query1.getGeneratedSql()).isEqualTo("select t0.pid, t0.name, t0.version, " +
+ "t1.eid, t1.extra, t1.version, " +
+ "t2.eid, t2.extra, t2.version, t2.eid " +
+ "from oto_uprime t0 " +
+ "left join oto_uprime_extra t1 on t1.eid = t0.pid " + // left join on non-optional, because DbForeignKey(noConstraint=true) is set
+ "left join oto_uprime_optional_extra t2 on t2.eid = t0.pid"); // left join on optional
+ } else if (extraFetch) {
+ assertThat(query1.getGeneratedSql()).isEqualTo("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid");
+ } else if (optionalFetch) {
+ assertThat(query1.getGeneratedSql()).isEqualTo("select t0.pid, t0.name, t0.pid, t0.version, t1.eid, t1.extra, t1.version, t1.eid from oto_uprime t0 left join oto_uprime_optional_extra t1 on t1.eid = t0.pid");
+
+ } else {
+ assertThat(query1.getGeneratedSql()).isEqualTo("select t0.pid, t0.name, t0.pid, t0.version from oto_uprime t0");
+ }
- if (found.getExtra() != null) {
- found.getExtra().getExtra(); // fails here, because getExtra should be null
+ List versions = new ArrayList<>();
+ for (OtoUPrime prime : primes) {
+ if (prime.getOptionalExtra() != null) {
+ versions.add(prime.getOptionalExtra().getVersion());
+ }
}
- assertThat(found.getExtra()).isNull();
+ assertThat(primes).hasSize(2);
+ assertThat(versions).containsExactly(1L);
+ }
+
+ public void doTest2(boolean withFetch) {
+
+ Query query2 = DB.find(OtoUPrimeOptionalExtra.class);
+ if (withFetch) {
+ query2.fetch("prime");
+ }
+ List extraPrimes = query2.findList();
+ if (withFetch) {
+ assertThat(query2.getGeneratedSql()).isEqualTo("select t0.eid, t0.extra, t0.version, t1.pid, t1.name, t1.pid, t1.version from oto_uprime_optional_extra t0 join oto_uprime t1 on t1.pid = t0.eid");
+ } else {
+ assertThat(query2.getGeneratedSql()).isEqualTo("select t0.eid, t0.extra, t0.version, t0.eid from oto_uprime_optional_extra t0");
+ }
+ List versions = new ArrayList<>();
+ for (OtoUPrimeOptionalExtra extraPrime : extraPrimes) {
+ versions.add(extraPrime.getPrime().getVersion());
+ }
+ assertThat(extraPrimes).hasSize(1);
+ assertThat(versions).containsExactly(1L);
+ }
+
+ @Test
+ void testWithExtraFetch1() {
+ doTest1(true, false);
+ }
+
+ @Test
+ void testWithOptionalFetch1() {
+ doTest1(false, true);
+ }
+
+ @Test
+ void testWithBothFetch1() {
+ doTest1(true, true);
+ }
+
+ @Test
+ void testWithoutFetch1() {
+ doTest1(false, false);
+ }
+
+ @Test
+ void testWithFetch2() {
+ doTest2(true);
+ }
+
+ @Test
+ void testWithoutFetch2() {
+ doTest2(false);
}
@Test
@@ -54,7 +158,7 @@ public void insertUpdateDelete() {
OtoUPrime found = query.findOne();
assertThat(found).isNotNull();
- assertThat(sqlOf(query, 4)).contains("select t0.pid, t0.name, t0.version, t0.pid from oto_uprime t0 where t0.pid = ?")
+ assertThat(sqlOf(query, 4)).contains("select t0.pid, t0.name, t0.pid, t0.version from oto_uprime t0 where t0.pid = ?")
.as("we don't join to oto_uprime_extra");
assertThat(found.getName()).isEqualTo("u" + desc);
@@ -66,7 +170,8 @@ public void insertUpdateDelete() {
OtoUPrime oneWith = queryWithFetch.findOne();
assertThat(oneWith).isNotNull();
- assertThat(sqlOf(queryWithFetch, 6)).contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
+ assertThat(sqlOf(queryWithFetch, 6))
+ .contains("select t0.pid, t0.name, t0.version, t1.eid, t1.extra, t1.version from oto_uprime t0 left join oto_uprime_extra t1 on t1.eid = t0.pid where t0.pid = ?")
.as("we join to oto_prime_extra");
@@ -98,8 +203,77 @@ private void thenDelete(OtoUPrime found) {
DB.delete(bean);
List sql = LoggedSql.stop();
- assertThat(sql).hasSize(2);
+
+ assertThat(sql).hasSize(3);
assertSql(sql.get(0)).contains("delete from oto_uprime_extra where");
- assertSql(sql.get(1)).contains("delete from oto_uprime where");
+ assertSql(sql.get(1)).contains("delete from oto_uprime_optional_extra where");
+ assertSql(sql.get(2)).contains("delete from oto_uprime where");
+ }
+
+ @Test
+ void testDdl() {
+ Collection extends Property> props = DB.getDefault().pluginApi().beanType(OtoUPrime.class).allProperties();
+
+ for (Property prop : props) {
+ System.out.println(prop);
+ }
+ }
+
+ @Test
+ void testContractViolation1() {
+
+ OtoUPrime p1 = new OtoUPrime("Prime having no extra");
+ // extra is "optional=false" - and this is a violating of the contract
+ DB.save(p1);
+
+ Query query = DB.find(OtoUPrime.class).setId(p1.pid);
+
+ OtoUPrime found1 = query.findOne();
+ assertThat(query.getGeneratedSql()).doesNotContain("join");
+
+ assertThat(found1.getExtra()).isNotNull();
+ assertThatThrownBy(() -> found1.getExtra().getVersion()).isInstanceOf(EntityNotFoundException.class);
+
+ query.fetch("extra");
+ OtoUPrime found2 = query.findOne();
+ // Note: We use "left join" here, because 'DbForeignKey(noConstraint=true)' is set oh the property
+ // if this annotation is not preset, an inner join would be used and 'found2' would be 'null' then
+ assertThat(query.getGeneratedSql()).contains("from oto_uprime t0 left join oto_uprime_extra");
+ assertThat(found2.getExtra()).isNull();
+
+ }
+
+ @Test
+ void testContractViolation2() {
+
+ OtoUPrimeExtraWithConstraint p1Const = new OtoUPrimeExtraWithConstraint("test");
+ try {
+ // a foreign key prevents from saving
+ DB.save(p1Const);
+ fail("PrimExtra cannot exist without Prime");
+ } catch (PersistenceException pe) {
+ // OK
+ }
+
+ OtoUPrimeWithConstraint p1 = new OtoUPrimeWithConstraint("Prime having no extra");
+ // extra is "optional=false" - and this is a violating of the contract
+ // Note there is no real foreign key in the database, that would prevent saving this entity
+ DB.save(p1);
+
+ Query query = DB.find(OtoUPrimeWithConstraint.class).setId(p1.pid);
+
+ OtoUPrimeWithConstraint found1 = query.findOne();
+ assertThat(query.getGeneratedSql()).doesNotContain("join");
+
+ assertThat(found1.getExtra()).isNotNull();
+ assertThatThrownBy(() -> found1.getExtra().getVersion()).isInstanceOf(EntityNotFoundException.class);
+
+ query.fetch("extra");
+ OtoUPrimeWithConstraint found2 = query.findOne();
+ // Note: We use "left join" here, because 'DbForeignKey(noConstraint=true)' is set oh the property
+ // if this annotation is not preset, an inner join would be used and 'found2' would be 'null' then
+ assertThat(query.getGeneratedSql()).contains("from oto_uprime_with_constraint t0 join oto_uprime_extra_with_constraint");
+ assertThat(found2).isNull();
+
}
}
diff --git a/ebean-test/src/test/java/org/tests/model/tevent/CustomFormulaAnnotationParser.java b/ebean-test/src/test/java/org/tests/model/tevent/CustomFormulaAnnotationParser.java
new file mode 100644
index 0000000000..3f392aed06
--- /dev/null
+++ b/ebean-test/src/test/java/org/tests/model/tevent/CustomFormulaAnnotationParser.java
@@ -0,0 +1,61 @@
+package org.tests.model.tevent;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import io.ebean.annotation.Formula;
+import io.ebean.config.dbplatform.DatabasePlatform;
+import io.ebean.plugin.CustomDeployParser;
+import io.ebean.plugin.DeployBeanDescriptorMeta;
+import io.ebean.plugin.DeployBeanPropertyMeta;
+import io.ebean.util.AnnotationUtil;
+import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
+
+/**
+ * Custom Annotation parser which parses @Count annotation
+ *
+ * @author Roland Praml, FOCONIS AG
+ */
+public class CustomFormulaAnnotationParser implements CustomDeployParser {
+
+ private int counter;
+
+
+ @Target(FIELD)
+ @Retention(RUNTIME)
+ @Formula(select="TODO", join = "TODO") // meta-formula
+ public @interface Count {
+ String value();
+ }
+
+
+
+ @Override
+ public void parse(final DeployBeanDescriptorMeta descriptor, final DatabasePlatform databasePlatform) {
+ for (DeployBeanPropertyMeta prop : descriptor.propertiesAll()) {
+ readField(descriptor, prop);
+ }
+ }
+
+ private void readField(DeployBeanDescriptorMeta descriptor, DeployBeanPropertyMeta prop) {
+ Count countAnnot = AnnotationUtil.get(prop.getField(), Count.class);
+ if (countAnnot != null) {
+ // @Count found, so build the (complex) count formula
+ DeployBeanPropertyAssocMany> countProp = (DeployBeanPropertyAssocMany>) descriptor.getBeanProperty(countAnnot.value());
+ counter++;
+ String tmpTable = "f"+counter;
+ String sqlSelect = "coalesce(" + tmpTable + ".child_count, 0)";
+ String parentId = countProp.getMappedBy() + "_id";
+ String tableName = countProp.getBeanTable().getBaseTable();
+ String sqlJoin = "left join (select " + parentId +", count(*) as child_count from " + tableName + " GROUP BY " + parentId + " )"
+ + " " + tmpTable + " on " + tmpTable + "." +parentId + " = ${ta}." + descriptor.idProperty().getDbColumn();
+ prop.setSqlFormula(sqlSelect, sqlJoin);
+// prop.setSqlFormula("f1.child_count",
+// "join (select parent_id, count(*) as child_count from child_entity GROUP BY parent_id) f1 on f1.parent_id = ${ta}.id");
+ }
+ }
+
+}
diff --git a/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java b/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java
index bcbad8ef2e..678ca9677c 100644
--- a/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java
+++ b/ebean-test/src/test/java/org/tests/model/tevent/TEventOne.java
@@ -42,6 +42,11 @@ public enum Status {
@OneToMany(mappedBy = "event", cascade = CascadeType.ALL)
List logs;
+ @CustomFormulaAnnotationParser.Count("logs")
+ //@Formula(select = "f1.child_count",
+ //join = "left join (select event_id, count(*) as child_count from tevent_many GROUP BY event_id ) as f1 on f1.event_id = ${ta}.id")
+ Long customFormula;
+
public TEventOne(String name, Status status) {
this.name = name;
this.status = status;
@@ -64,6 +69,10 @@ public Long getCount() {
return count;
}
+ public Long getCustomFormula() {
+ return customFormula;
+ }
+
public BigDecimal getTotalUnits() {
return totalUnits;
}
diff --git a/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java b/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java
index 8636d48d6f..9dde14d07f 100644
--- a/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java
+++ b/ebean-test/src/test/java/org/tests/query/aggregation/TestAggregationCount.java
@@ -49,7 +49,7 @@ public void testBaseSelect() {
List list = query.findList();
String sql = sqlOf(query, 5);
- assertThat(sql).contains("select t0.id, t0.name, t0.status, t0.version, t0.event_id from tevent_one t0");
+ assertThat(sql).contains("select t0.id, t0.name, t0.status, coalesce(f1.child_count, 0), t0.version, t0.event_id from tevent_one t0");
for (TEventOne eventOne : list) {
// lazy loading on Aggregation properties
diff --git a/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java b/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java
index db02e5edaa..b1d380ba6a 100644
--- a/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java
+++ b/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java
@@ -489,7 +489,20 @@ void distinctWithOrderByPkWithId() {
Query query = DB.find(Contact.class)
.setDistinct(true)
.select("customer.id")
- .order().desc("customer.id");
+ .order().desc("concat(customer.id, customer.billingAddress)");
+
+ query.findSingleAttributeList();
+
+ assertThat(sqlOf(query)).contains("select distinct t0.customer_id from contact t0 order by t0.customer_id desc");
+ }
+
+ @Test
+ void distinctWithOrderByPkWithId2() {
+ ResetBasicData.reset();
+
+ Query query = DB.find(Contact.class)
+ .setDistinct(true)
+ .select("concat(customer.id, customer.billingAddress)");
query.findSingleAttributeList();
@@ -825,19 +838,24 @@ void setup() {
e3.setAttr1("a1");
DB.save(e3);
+ Cat cat = new Cat();
+ cat.setId(4711L);
MainEntityRelation rel = new MainEntityRelation();
rel.setEntity1(e1);
rel.setEntity2(e1);
+ rel.setCat2(cat);
DB.save(rel);
rel = new MainEntityRelation();
rel.setEntity1(e2);
rel.setEntity2(e2);
+ rel.setCat2(cat);
DB.save(rel);
rel = new MainEntityRelation();
rel.setEntity1(e3);
rel.setEntity2(e3);
+ rel.setCat2(cat);
DB.save(rel);
}
diff --git a/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java b/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java
index 17cf86356e..9f37c010ff 100644
--- a/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java
+++ b/ebean-test/src/test/java/org/tests/rawsql/TestRawSqlNamedParams.java
@@ -44,9 +44,12 @@ public void testMySqlColonEquals() throws SQLException {
Transaction transaction = DB.beginTransaction();
+ System.out.println(transaction.connection().getMetaData().getDriverName());
try {
- if ("MariaDB connector/J".equals(transaction.connection().getMetaData().getDriverName())) {
- return; // MariaDb only supports callable statements in the form "? = call function x(?)"
+ if ("MariaDB Connector/J".equals(transaction.connection().getMetaData().getDriverName())) {
+ // CHECKME: Should we report/fail if we are running mysql-tests with mariadb driver
+ // BTW: This happens with java 8 - the drivers in drivermanager are in different order
+ return; // MariaDB Connector/J only supports callable statements in the form "? = call function x(?)"
}
CallableSql callableSql = DB.createCallableSql("set @total = 0");
DB.getDefault().execute(callableSql);
diff --git a/ebean-test/src/test/resources/ebean.properties b/ebean-test/src/test/resources/ebean.properties
index 9dec823eed..e4eefd3c51 100644
--- a/ebean-test/src/test/resources/ebean.properties
+++ b/ebean-test/src/test/resources/ebean.properties
@@ -196,6 +196,7 @@ datasource.hana.username=EBEAN_TEST
datasource.hana.password=Eb3an_test
datasource.hana.url=jdbc:sap://hxehost:39013/?databaseName=HXE
#datasource.hana.driver=com.sap.db.jdbc.Driver
+#
# parameters for migration test
datasource.migrationtest.username=SA
@@ -207,6 +208,21 @@ ebean.migrationtest.ddl.run=false
ebean.migrationtest.ddl.header=-- Migrationscripts for ebean unittest
ebean.migrationtest.migration.appName=migrationtest
ebean.migrationtest.migration.migrationPath=migrationtest/dbmigration
+ebean.migrationtest.migration.migrationInitPath=migrationtest/dbinit
+ebean.migrationtest.migration.strict=true
+ebean.migrationtest.migration.generate=true
+ebean.migrationtest.migration.run=false
+ebean.migrationtest.migration.includeIndex=true
+ebean.migrationtest.migration.generateInit=true
+ebean.migrationtest.migration.generatePendingDrop=auto
+ebean.migrationtest.migration.platforms=db2luw,h2,hsqldb,mysql,mysql55,mariadb,postgres,oracle,sqlite,sqlserver17,hana,yugabyte
+#migration.migrationtest.db2luw.prefix=db2
+#migration.migrationtest.sqlserver17.prefix=sqlserver
+dbmigration.platform.mariadb.useMigrationStoredProcedures=true
+dbmigration.platform.mysql.useMigrationStoredProcedures=true
+
+
+
ebean.migrationtest.migration.strict=true
# enable stored procedures f
dbmigration.platform.mariadb.useMigrationStoredProcedures=true
@@ -222,4 +238,17 @@ ebean.migrationtest-history.ddl.run=false
ebean.migrationtest-history.ddl.header=-- Migrationscripts for ebean unittest DbMigrationDropHistoryTest
ebean.migrationtest-history.migration.appName=migrationtest-history
ebean.migrationtest-history.migration.migrationPath=migrationtest-history/dbmigration
+ebean.migrationtest-history.migration.migrationInitPath=migrationtest-history/dbinit
ebean.migrationtest-history.migration.strict=true
+
+# ServerStartTest - can we run the migrations and do we find the correct ones!
+datasource.db2-migration.username=unit
+datasource.db2-migration.password=test
+datasource.db2-migration.url=jdbc:db2://127.0.0.1:50000/unit
+ebean.db2-migration.ddl.generate=false
+ebean.db2-migration.ddl.run=false
+ebean.db2-migration.migration.run=true
+ebean.db2-migration.databasePlatformName=db2luw
+# workaround for https://github.com/ebean-orm/ebean-migration/issues/102
+ebean.db2-migration.migration.migrationPath=migrationtest/dbmigration/db2
+ebean.db2-migration.migration.migrationInitPath=migrationtest/dbinit/db2
\ No newline at end of file
diff --git a/ebean-test/src/test/resources/extra-ddl.xml b/ebean-test/src/test/resources/extra-ddl.xml
index 130de7fac6..44d49d1589 100644
--- a/ebean-test/src/test/resources/extra-ddl.xml
+++ b/ebean-test/src/test/resources/extra-ddl.xml
@@ -64,6 +64,15 @@
create index ix_ebasic_jmjb_gin2 on ebasic_json_map_json_b using gin(content jsonb_path_ops);
+
+delimiter $$
+BEGIN
+IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN
+call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA );
+END IF;
+END;$$
+
+
delimiter $$
BEGIN
@@ -101,4 +110,5 @@ END IF;
END
$$
+
diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/R__db2_explain_tables.sql b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/R__db2_explain_tables.sql
new file mode 100644
index 0000000000..b2539a5ea9
--- /dev/null
+++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/R__db2_explain_tables.sql
@@ -0,0 +1,8 @@
+
+delimiter $$
+BEGIN
+IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN
+call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA );
+END IF;
+END;$$
+
\ No newline at end of file
diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations
index 16c7b2cec2..39e65b2d4b 100644
--- a/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations
+++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2fori/idx_db2.migrations
@@ -3,5 +3,6 @@
-1187336846, 1.2__dropsFor_1.1.sql
-150875853, 1.3.sql
946163478, 1.4__dropsFor_1.3.sql
+-133543359, R__db2_explain_tables.sql
561281075, R__order_views.sql
diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/R__db2_explain_tables.sql b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/R__db2_explain_tables.sql
new file mode 100644
index 0000000000..b2539a5ea9
--- /dev/null
+++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/R__db2_explain_tables.sql
@@ -0,0 +1,8 @@
+
+delimiter $$
+BEGIN
+IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN
+call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA );
+END IF;
+END;$$
+
\ No newline at end of file
diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations
index 146c2c1d8a..a0380e2e7c 100644
--- a/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations
+++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2luw/idx_db2.migrations
@@ -4,5 +4,6 @@
-1187336846, 1.2__dropsFor_1.1.sql
1976888196, 1.3.sql
946163478, 1.4__dropsFor_1.3.sql
+-133543359, R__db2_explain_tables.sql
561281075, R__order_views.sql
diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/R__db2_explain_tables.sql b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/R__db2_explain_tables.sql
new file mode 100644
index 0000000000..b2539a5ea9
--- /dev/null
+++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/R__db2_explain_tables.sql
@@ -0,0 +1,8 @@
+
+delimiter $$
+BEGIN
+IF NOT EXISTS (SELECT * FROM SYSCAT.TABLES WHERE TABSCHEMA = CURRENT SCHEMA AND TABNAME = 'EXPLAIN_STREAM') THEN
+call SYSPROC.SYSINSTALLOBJECTS( 'EXPLAIN', 'C' , '', CURRENT SCHEMA );
+END IF;
+END;$$
+
\ No newline at end of file
diff --git a/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations
index 2d8e70ad25..382351279d 100644
--- a/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations
+++ b/ebean-test/src/test/resources/migrationtest/dbmigration/db2zos/idx_db2.migrations
@@ -3,5 +3,6 @@
-1187336846, 1.2__dropsFor_1.1.sql
1976888196, 1.3.sql
946163478, 1.4__dropsFor_1.3.sql
+-133543359, R__db2_explain_tables.sql
561281075, R__order_views.sql
diff --git a/ebean-test/testconfig/ebean-oracle.properties b/ebean-test/testconfig/ebean-oracle.properties
index 32b3af2491..24764943b2 100644
--- a/ebean-test/testconfig/ebean-oracle.properties
+++ b/ebean-test/testconfig/ebean-oracle.properties
@@ -1,3 +1,4 @@
ebean.test.platform=oracle
ebean.test.dbName=test_eb
+ebean.test.dbPassword=test
datasource.default=oracle
diff --git a/ebean-test/testplatforms.sh b/ebean-test/testplatforms.sh
new file mode 100644
index 0000000000..1e19717c0e
--- /dev/null
+++ b/ebean-test/testplatforms.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# A small script, to run a certain test on all platforms
+# invoke with ./testplatforms.sh -Dtest=DbMigrationTest
+# Hint: in case of DbMigrationTest, you may disable ddl.run temporary
+
+# default H2 platform
+set -e
+mvn test "$@"
+
+mvn surefire:test -Dprops.file=testconfig/ebean-mysql.properties "$@"
+
+mvn surefire:test -Dprops.file=testconfig/ebean-mariadb.properties "$@"
+mvn surefire:test -Dprops.file=testconfig/ebean-mariadb-10.3.properties "$@"
+
+mvn surefire:test -Dprops.file=testconfig/ebean-sqlserver17.properties "$@"
+mvn surefire:test -Dprops.file=testconfig/ebean-sqlserver19.properties "$@"
+
+mvn surefire:test -Dprops.file=testconfig/ebean-postgres.properties "$@"
+
+#mvn surefire:test -Dprops.file=testconfig/ebean-oracle.properties "$@"
+
+#mvn surefire:test -Dprops.file=testconfig/ebean-sqlite.properties "$@"
+
+mvn surefire:test -Dprops.file=testconfig/ebean-hana.properties "$@"
+
+mvn surefire:test -Dprops.file=testconfig/ebean-db2.properties "$@"
+
+## Test ignored
+## mvn surefire:test -Dprops.file=testconfig/ebean-yugabyte.properties "$@"
+
+## Scripts are not correct
+## mvn surefire:test -Dprops.file=testconfig/ebean-cockroach.properties "$@"
+
+## Transactions are not supported
+## mvn surefire:test -Dprops.file=testconfig/ebean-clickhouse.properties "$@"
+
+## I cannot start nuodb
+## mvn surefire:test -Dprops.file=testconfig/ebean-nuodb.properties.properties "$@"
+
+
diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml
index 550421666c..e8b80acb42 100644
--- a/kotlin-querybean-generator/pom.xml
+++ b/kotlin-querybean-generator/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTkotlin querybean generator
@@ -29,7 +29,7 @@
io.ebeanebean-querybean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
@@ -43,7 +43,7 @@
io.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
@@ -64,14 +64,14 @@
io.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-ddl-generator
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/platforms/all/pom.xml b/platforms/all/pom.xml
index b656bddd39..a7234f5dec 100644
--- a/platforms/all/pom.xml
+++ b/platforms/all/pom.xml
@@ -4,7 +4,7 @@
platformsio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-platform-all
@@ -14,67 +14,67 @@
io.ebeanebean-platform-h2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-clickhouse
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-db2
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-hana
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-hsqldb
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-mysql
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-mariadb
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-nuodb
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-oracle
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-postgres
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-sqlanywhere
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-sqlite
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTio.ebeanebean-platform-sqlserver
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/platforms/clickhouse/pom.xml b/platforms/clickhouse/pom.xml
index ab5caf43ed..585078042f 100644
--- a/platforms/clickhouse/pom.xml
+++ b/platforms/clickhouse/pom.xml
@@ -4,7 +4,7 @@
platformsio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-platform-clickhouse
@@ -14,7 +14,7 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/platforms/db2/pom.xml b/platforms/db2/pom.xml
index af0b33ad1e..2822888a7e 100644
--- a/platforms/db2/pom.xml
+++ b/platforms/db2/pom.xml
@@ -4,7 +4,7 @@
platformsio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-platform-db2
@@ -14,7 +14,7 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
diff --git a/platforms/h2/pom.xml b/platforms/h2/pom.xml
index d43c650cf4..12ef49081e 100644
--- a/platforms/h2/pom.xml
+++ b/platforms/h2/pom.xml
@@ -4,7 +4,7 @@
platformsio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTebean-platform-h2
@@ -14,7 +14,7 @@
io.ebeanebean-api
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
+
+
+ foconis-release
+ FOCONIS Release Repository
+ https://mvnrepo.foconis.de/repository/release/
+
+
+ foconis-snapshot
+ FOCONIS Snapshot Repository
+ https://mvnrepo.foconis.de/repository/snapshot/
+
+
diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml
index 97a4023e54..94328c5355 100644
--- a/querybean-generator/pom.xml
+++ b/querybean-generator/pom.xml
@@ -4,7 +4,7 @@
ebean-parentio.ebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTquerybean generator
diff --git a/release.md b/release.md
new file mode 100644
index 0000000000..423bdb9ba6
--- /dev/null
+++ b/release.md
@@ -0,0 +1,18 @@
+## Release command
+
+We @foconis use this command to release.
+
+ mvn versions:set -DgenerateBackupPoms=false -DnewVersion=13.6.0-FOC2-SNAPSHOT
+ mvn release:prepare release:perform -Darguments="-Dgpg.skip -DskipTests" -PfoconisRelease
+
+ # RELEASE klappt nun, sollte es failen, ist wie folgt vorzugehen:
+ # um bei einen Fehler zu release ist dann ins target/checkout Verzeichnis zu gehen und
+ mvn clean source:jar install org.apache.maven.plugins:maven-deploy-plugin:deploy -DskipTests
+ # auszuführen. Aber auch das failed bei Kotlin.
+
+ # nach dem Release müssen die Versionen in ebean-kotlin/pom.xml, tests/test-java16/pom.xml und tests/test-kotlin/pom.xml manuell angepasst werden
+
+generate Java classes from .xsd:
+
+ export JAVA_TOOL_OPTIONS="-Duser.language=en -Duser.country=US -Dfile.encoding=UTF-8"
+ /c/Program\ Files/Java/jdk1.8.0_201/bin/xjc.exe src/main/resources/ebean-dbmigration-1.0.xsd -d src/main/java -p io.ebeaninternal.dbmigration.migration
diff --git a/tests/pom.xml b/tests/pom.xml
index ac7678298b..ca3d3d123e 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -5,7 +5,7 @@
org.avajejava8-oss3.3
-
+ io.ebean
diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml
index ad13be4672..a102ebcf7f 100644
--- a/tests/test-java16/pom.xml
+++ b/tests/test-java16/pom.xml
@@ -1,6 +1,5 @@
-
+4.0.0io.ebean
@@ -15,7 +14,7 @@
io.ebeanebean
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOT
@@ -27,7 +26,7 @@
io.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
diff --git a/tests/test-kotlin/pom.xml b/tests/test-kotlin/pom.xml
index 1d6caa2052..a65f752d10 100644
--- a/tests/test-kotlin/pom.xml
+++ b/tests/test-kotlin/pom.xml
@@ -1,13 +1,11 @@
-
+4.0.0org.avajejava11-oss3.7
-
+ test-kotlin
@@ -35,14 +33,14 @@
io.ebeanebean-test
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtestio.ebeanebean-core
- 13.10.1-SNAPSHOT
+ 13.10.1-FOC4-SNAPSHOTtest
@@ -71,6 +69,11 @@
src/test/kotlin
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ false
+ org.jetbrains.kotlinkotlin-maven-plugin
@@ -107,5 +110,4 @@
-