Compare commits

..
Author SHA1 Message Date
rob bygrave 30c44d0928 [maven-release-plugin] prepare release ebean-12.1.1 2019-10-13 12:22:44 +13:00
rob bygrave 443c5dba58 Bump ebean-migration version 2019-10-13 12:20:59 +13:00
rob bygrave f24b4dba71 #1837 (#1740 and #1744) - Inheritance on both sides of @ManyToOne
This change also relates back to #1740 and #1744 and changes the way that issue was fixed (by SqlTreeNodeBean on partially populated non-id inheritance bean).
2019-10-13 11:46:11 +13:00
Rob BygraveandGitHub a474880735 #1837 - Inheritance on both sides of @ManyToOne confuses columns (#1843)
* #1837 - Inheritance on both sides of @ManyToOne confuses columns

* #1837 - Inheritance on both sides of @ManyToOne confuses columns

Tidy up whitespace in SQL
2019-10-12 00:25:53 +13:00
rob bygrave 4cfab3a2de #1842 - Refactor persisting to delay the setting of generated Id value (sequences and deferred imported ids) 2019-10-11 20:50:47 +13:00
rob bygrave 4807070147 #1841 - SqlServer findCount with @Aggregation gives SQLException:No column name was specified for column 2 of 'c' 2019-10-11 20:26:35 +13:00
rob bygrave e00aa479ae #1841 - SqlServer findCount with @Aggregation gives SQLException:No column name was specified for column 2 of 'c' 2019-10-11 20:18:18 +13:00
rob bygrave b665d8375c #1831 - Update query with maxRows does not include the limit in the generated SQL 2019-10-11 17:01:30 +13:00
Rob BygraveandGitHub 4e74c68603 Merge pull request #1840 from FOCONIS/exists-use-query-copy
exists() limits the max rows on the query, this should be done on a copy
2019-10-10 23:28:48 +13:00
Manuel Danisch 2694aa3fe1 Fixed test 2019-10-10 11:28:24 +02:00
rob bygrave f7a28aa904 Javadoc fixes 2019-10-10 22:07:14 +13:00
rob bygrave 4bc2559e8e Refactor tidy PersistRequestBean 2019-10-10 21:51:31 +13:00
rob bygrave c2faf0e0a7 No effective change - tidy tests 2019-10-10 21:42:34 +13:00
Manuel Danisch 895b331e8f exists() limits the max rows on the query, this should be done on a copy 2019-10-10 10:42:04 +02:00
rob bygrave 8890079b80 #1839 - Fix JSON marshalling of @ElementCollection maps and scalar 2019-10-10 21:32:44 +13:00
rob bygrave 20dd33ebca Fix DbMigration test for MySql without I__create_procs.sql 2019-10-10 19:07:36 +13:00
rob bygrave e2a1489743 Fix DbMigration test for MySql without I__create_procs.sql 2019-10-10 15:26:50 +13:00
rob bygrave d6bab46ac3 #1838 - Fix for Joda LocalTime timezone handling for MySql 2019-10-10 15:16:12 +13:00
rob bygrave 573673ef2b Fix for tests, make MySql like escape consistent with others 2019-10-10 15:14:38 +13:00
rob bygrave eb69499ca3 #1835 Fix with Refactor batch escalation for deferred imported Id 2019-10-10 15:13:21 +13:00
rob bygrave d9323b8ab6 Fix DbMigration test for MySql without I__create_procs.sql 2019-10-10 15:11:25 +13:00
rob bygrave a01ba128e5 Fix test for TestDbArray_basic.nullItems() for DB's that don't support ARRAY type 2019-10-10 15:10:44 +13:00
rob bygrave fdc67b3ce5 #1823 - ENH: Add query.findStream() and query.findLargeStream() 2019-10-10 13:44:29 +13:00
rob bygrave 5d433ff603 Make it easier for people building using JDK11
Note that our javadoc doclet isn't JDK11 compatible yet
2019-10-10 12:04:09 +13:00
rob bygrave a3f9c4c889 #1836 - NPE with findCount() on bean with @Aggregation, Regression in 11.45.1 2019-10-10 08:55:01 +13:00
Rob BygraveandGitHub 769fc485ec Merge pull request #1835 from ebean-orm/feature/persistCascadeEscalate
Refactor JDBC batch escalation and detection of deferred persist relationship (circular imported ids)
2019-10-09 17:08:09 +13:00
rob bygrave 61ef4fa420 #1835 Refactor JDBC batch escalation 2019-10-09 00:37:06 +13:00
rob bygrave 8cf37e2f0a #1835 Refactor JDBC batch escalation 2019-10-09 00:18:15 +13:00
rob bygrave 80a764a448 #1835 Refactor JDBC batch escalation 2019-10-09 00:17:34 +13:00
rob bygrave 6621c5dfbb Refactor detection of deferred persist relationship (circular imported ids) 2019-10-08 21:49:22 +13:00
rob bygrave 5e7dda1ec8 #1834 - DDL for ElementCollection that is referenced multiple times includes foreign key constraint 2019-10-08 15:53:29 +13:00
rob bygrave 7bc6368f69 #1833 - When cascade persisting twice to ElementCollection ... we get do extra delete and inserts 2019-10-08 13:25:33 +13:00
rob bygrave fb5a69adea #1833 - When cascade persisting twice to ElementCollection ... we get do extra delete and inserts 2019-10-08 13:25:18 +13:00
rob bygrave 0e7607bb27 Log warning when initSql or seedSql is not found as a resource 2019-10-08 09:15:20 +13:00
rob bygrave 1098b572fe #1832 - Cascade insert @ElementCollection when marshalled from JSON 2019-10-07 15:48:31 +13:00
rob bygrave 7a6662a2dc #1830 - Bump ebean-migration dependency to 11.22.1 2019-10-07 13:49:20 +13:00
rob bygrave 1c6c812d4d Update tests change AssertJ StrictAssertions to Assertions 2019-10-06 16:34:44 +13:00
rob bygrave e58a85f0af #1356 - allQuotedIdentifiers must be respected by DDL 2019-10-06 16:32:06 +13:00
rob bygrave e3551152bb #1829 - Bump Jackson core dependency from 2.9.9 to 2.10.0 2019-10-05 23:41:16 +13:00
rob bygrave 5138d1723f Merge branch 'feature/1656' 2019-10-05 23:20:26 +13:00
rob bygrave 9b5ab542d7 #1827 - @UniqueConstraint columnNames should be respecting ebean.allQuotedIdentifiers 2019-10-05 22:37:06 +13:00
rob bygrave f0bc4c786c #1826 - Remove PrivateOwned, migrate to orphanRemoval=true attribute on OneToMany 2019-09-24 22:36:17 +12:00
rob bygrave b98739bb49 #1656 #1824 Stateless updates - Remove update deleteMissingChildren option, instead always use orphanRemoval
Adjust test that doesn't have orphanRemoval (so missing children are not removed here now)
2019-09-20 17:18:07 +12:00
rob bygrave fcadc63338 #1656 #1824 Stateless updates - Remove update deleteMissingChildren option, instead always use orphanRemoval 2019-09-20 17:12:32 +12:00
rob bygrave 3574479627 #1821 - Remove serverConfig updateChangesOnly mode ... as always true 2019-09-15 21:29:25 +12:00
rob bygrave cecf2fc650 [maven-release-plugin] prepare for next development iteration 2019-09-12 23:09:28 +12:00
rob bygrave a1dab24414 [maven-release-plugin] prepare release ebean-11.45.1 2019-09-12 23:09:17 +12:00
rob bygrave c12ea28ea9 #1818 - Cannot auto-generate UUID 2019-09-12 22:02:22 +12:00
rob bygrave a2b418df8b #1820 - ENH: For DB Migration add setLockTimeout(10); ... to add Postgres set lock_timeout = to generated migrations 2019-09-12 20:59:52 +12:00
rob bygrave 2fb533a606 #1819 - ENH: For DB Migration support dbMigration.setAddForeignKeySkipCheck(true); 2019-09-12 20:34:37 +12:00
rob bygrave 5d89897bbd #1817 - Rename @DbHstore to @DbMap 2019-09-12 17:02:53 +12:00
rob bygrave 9640e02b6a #1817 - Rename @DbHstore to @DbMap 2019-09-12 16:58:15 +12:00
rob bygrave 8a0ef33f79 git push origin masterMerge branch 'Ryszard-Trojnacki-master' 2019-09-12 14:55:50 +12:00
rob bygrave d2ece76ca8 #1816 Fix for OneToMany relation to non-cached target bean. 2019-09-12 14:55:37 +12:00
rob bygrave 3495d03706 Merge branch 'master' of https://github.com/Ryszard-Trojnacki/avaje-ebeanorm into Ryszard-Trojnacki-master 2019-09-12 13:58:50 +12:00
Rob BygraveandGitHub bc90fa50d7 #1802 - Completely remove MySQL init stored procedures for drop column with foreign key (#1815) 2019-09-12 09:34:14 +12:00
Ryszard Trojnacki 777fc0ff30 Merge branch 'ebean-orm-master' 2019-09-11 14:24:04 +02:00
Ryszard Trojnacki 9413f72939 Merge from origin. 2019-09-11 14:22:47 +02:00
Ryszard Trojnacki 1416647d04 Test case for changing OneToMany collection in a cached bean, by editing target bean that is not cached (annotated with @Cache). 2019-09-11 14:05:31 +02:00
rob bygrave 63bddb4d54 #1810 - Whan paging over query with @Aggregation ( grouping by) wrong total count is calculated 2019-09-11 23:38:12 +12:00
rob bygrave 6fddd418a1 #1811 Fix for Entity with EmbeddedId not found in cache 2019-09-11 22:28:22 +12:00
RienandRob Bygrave faba55d772 Fix EmbeddedId not found cache (#1811) 2019-09-11 22:06:05 +12:00
rob bygrave ac6db11e3d #1802 - Change MySQL DDL to not use helper stored procedures by default - was auto-generated script containing create procedures fails 2019-09-11 22:04:30 +12:00
rob bygrave ca7d8ecfc1 #1814 - Support older MySQL 5.5 2019-09-11 21:38:16 +12:00
rob bygrave 46eeee68c9 #1814 - Support older MySQL 5.5 2019-09-11 21:37:52 +12:00
rob bygrave c1487daea8 #1812 No effective change - Format code only 2019-09-11 15:33:21 +12:00
Ryszard TrojnackiandRob Bygrave b329ae42db Allow for null items in @DbArray lists. (#1812)
* Added code to allow for null items in @DbArray lists.

* Test case for changing OneToMany collection by editing target bean.
2019-09-11 15:30:32 +12:00
Ryszard Trojnacki 13429daddc Test case for changing OneToMany collection by editing target bean. 2019-09-10 20:43:31 +02:00
Ryszard Trojnacki 98a80295fe Added code to allow for null items in @DbArray lists. 2019-09-10 20:07:33 +02:00
Ryszard TrojnackiandGitHub f8587368ee Merge pull request #2 from ebean-orm/master
Update to master
2019-09-10 19:39:27 +02:00
rob bygrave 8ae2ae7f8b [maven-release-plugin] prepare for next development iteration 2019-08-31 00:16:48 +12:00
rob bygrave 46f3b74b38 [maven-release-plugin] prepare release ebean-11.44.1 2019-08-31 00:16:37 +12:00
rob bygrave df51ccd2c4 Bump to 11.44.1-SNAPSHOT 2019-08-31 00:15:03 +12:00
rob bygrave 730d43c008 #1807 - ENH: Add SerializableConflictException 2019-08-30 23:43:00 +12:00
rob bygrave 81c2c804a6 #1806 - SQLException:Data conversion error converting "c5866ffb-2e1c-457e-bdad-61ba4466f86e" [22018-197] Bind values:[1, ] Query was: 2019-08-26 21:12:07 +12:00
rob bygrave 533be356f2 [maven-release-plugin] prepare for next development iteration 2019-08-25 17:02:57 +12:00
rob bygrave 589191f3f4 [maven-release-plugin] prepare release ebean-11.43.3 2019-08-25 17:02:47 +12:00
rob bygrave 0e65efea57 #1804 - Kotlin: Generating collection with wildcard type - Get error BeanNotRegisteredException: Error with association to [null] from [...Foo.manyProperty]. Is null registered? 2019-08-25 15:58:46 +12:00
rob bygrave be66872249 #1800 Adjust to allow for null schema and table names 2019-08-25 11:32:08 +12:00
rob bygrave 85a0354d42 #1800 No effective change, test only, static imports and organise imports 2019-08-25 11:19:07 +12:00
rob bygrave 0b8613fd4c #1800 - Additional test exercising RawSqlBuilder.sqlRow(resultSet, ...); 2019-08-25 11:16:07 +12:00
Manuel DanischandRob Bygrave e734c0010c If the map already contains the column name, add schema and table names before the key to make it unique again (#1800) 2019-08-25 11:09:32 +12:00
rob bygrave e08333ef26 Improve error message when trying to use predicates etc when creating FetchGroup 2019-08-20 22:47:14 +12:00
rob bygrave 4a3eabbd6b [maven-release-plugin] prepare for next development iteration 2019-08-20 22:01:16 +12:00
rob bygrave ae68d41519 [maven-release-plugin] prepare release ebean-11.43.2 2019-08-20 22:01:04 +12:00
rob bygrave 722692d934 reduce logging for testing 2019-08-20 10:29:31 +12:00
rob bygrave 21a3f322e1 #1798 - Fix automatic query label set on nested secondary query when using profileLocation 2019-08-19 23:15:27 +12:00
rob bygrave 0d175557b0 No effective change - update test clearing data 2019-08-19 21:50:17 +12:00
rob bygrave 57ccd19fe6 #1797 - Bump ebean-migration to 11.20.1 ... which inserts a <init> row into db_migration table 2019-08-19 21:37:01 +12:00
rob bygrave 9674711516 #1796 - ENH: add query.fetchCache(path) ... for explicitly building part of graph from L2 cache 2019-08-19 21:36:19 +12:00
rob bygrave 9155716752 #1795 - Refactor bind padding for Id IN expressions (based on internal multiValueSupport and simple id) 2019-08-18 16:33:36 +12:00
rob bygrave d7d8c3e651 Refactor tidy on Load context 2019-08-18 12:17:36 +12:00
rob bygrave 16f6f2ee7f [maven-release-plugin] prepare for next development iteration 2019-08-16 00:00:42 +12:00
rob bygrave fea6dfd62b [maven-release-plugin] prepare release ebean-11.43.1 2019-08-16 00:00:31 +12:00
rob bygrave 464ed4322e #1794 - Also support "inRange" camelcase version of "inrange" in EQL 2019-08-15 23:58:58 +12:00
rob bygrave c3f556f0dc #1794 - Extend EQL to support inOrEmpty, eqOrNull, gtOrNull, ltOrNull 2019-08-15 23:40:19 +12:00
rob bygrave ba6810891e Bump to 11.43.1-SNAPSHOT 2019-08-15 22:20:09 +12:00
rob bygrave c530883e5e #1793 - ENH: Add filterMany() that takes expressions ... as a simpler option 2019-08-15 22:05:46 +12:00
rob bygrave 06542b031e Initial refactor to extract EqlWhereListener 2019-08-15 20:21:01 +12:00
rob bygrave 4b5365e708 #1792 - ENH: Support using Query beans for building FetchGroup (in a type safe manor) 2019-08-14 22:52:18 +12:00
Rob BygraveandGitHub 1d471fee92 #1768 - Initial refactor extracting CallOrigin interface (#1791) 2019-08-14 17:02:57 +12:00
rob bygrave 3825dea00a [maven-release-plugin] prepare for next development iteration 2019-08-12 21:12:45 +12:00
rob bygrave 3fd130ac4c [maven-release-plugin] prepare release ebean-11.42.3 2019-08-12 21:12:34 +12:00
rob bygrave 196645e7a2 #1789 - Add default constructor to EntityBeanIntercept for serialisation framework use only 2019-08-12 21:09:31 +12:00
rob bygrave f5459920e9 #1785 - Bean gets replaced on json deserialization with primitive 'null' id 2019-08-12 21:05:32 +12:00
rob bygrave ee6452cfb0 #1788 - ENH: Add support for NuoDB database platform 2019-08-12 20:41:45 +12:00
rob bygrave ad7e18561b #1788 - ENH: Add support for NuoDB database platform
Remove the extra truncate
2019-08-11 16:43:24 +12:00
rob bygrave c640524bfc #1788 - ENH: Add support for NuoDB database platform 2019-08-11 16:24:37 +12:00
rob bygrave fea840c859 #1782 - ENH: Add truncate(Class<?>... types) and truncate(String... tables)
Remove truncateTableBefore and truncateTableAfter for now
2019-08-10 21:44:48 +12:00
rob bygrave bb7df71391 #1782 - ENH: Add truncate(Class<?>... types) and truncate(String... tables) 2019-08-10 21:40:23 +12:00
rob bygrave cb6f0335b1 #1787 - Update CockroachDB support for DDL generation with autocommit and no history support 2019-08-08 23:22:58 +12:00
rob bygrave 762899ef10 [maven-release-plugin] prepare for next development iteration 2019-08-07 22:43:12 +12:00
rob bygrave 848d4f031e [maven-release-plugin] prepare release ebean-11.42.2 2019-08-07 22:42:59 +12:00
rob bygrave 481f828507 Bump provided scope jackson-databind version to 2.9.9.2 for security warning
This should have no effect as the dependency is scope provided.
2019-08-07 22:40:57 +12:00
rob bygrave b3d0c21a80 #1783 - Save bean with only *ToMany relations with user generated ID 2019-08-07 22:28:51 +12:00
rob bygrave a115cdf095 #1784 - Tests related to NPE with IDEA plugin and mockito 3.0.0 2019-08-07 21:38:52 +12:00
rob bygrave 410e9d475b [maven-release-plugin] prepare for next development iteration 2019-07-31 23:27:53 +12:00
398 changed files with 8538 additions and 3220 deletions
+29 -14
View File
@@ -4,26 +4,26 @@
<parent>
<groupId>org.avaje</groupId>
<artifactId>java8-parent</artifactId>
<version>1.3</version>
<version>1.4</version>
</parent>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.42.1</version>
<version>12.1.1</version>
<packaging>jar</packaging>
<name>ebean</name>
<url>http://ebean-orm.github.io/</url>
<properties>
<jackson-core.version>2.9.9</jackson-core.version>
<jackson-databind.version>2.9.9.1</jackson-databind.version>
<jackson-core.version>2.10.0</jackson-core.version>
<jackson-databind.version>2.10.0</jackson-databind.version>
<snakeyaml.version>1.21</snakeyaml.version>
</properties>
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.42.1</tag>
<tag>ebean-12.1.1</tag>
</scm>
<profiles>
@@ -118,7 +118,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>4.11</version>
<version>6.1</version>
</dependency>
<dependency>
@@ -130,7 +130,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>4.5.2</version>
<version>4.6.1</version>
</dependency>
<dependency>
@@ -142,7 +142,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.19.1</version>
<version>12.1.1</version>
</dependency>
<dependency>
@@ -220,6 +220,13 @@
<!-- Test scope -->
<dependency>
<groupId>com.nuodb.jdbc</groupId>
<artifactId>nuodb-jdbc</artifactId>
<version>20.2.0</version>
<scope>test</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>oracle</groupId>-->
<!--<artifactId>oracle-jdbc</artifactId>-->
@@ -230,7 +237,7 @@
<dependency>
<groupId>io.ebean.test</groupId>
<artifactId>ebean-test-docker</artifactId>
<version>2.8.1</version>
<version>2.10.3</version>
<scope>test</scope>
</dependency>
@@ -285,12 +292,18 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje.composite</groupId>
<artifactId>avaje-composite-testing</artifactId>
<artifactId>junit</artifactId>
<version>1.1</version>
<scope>test</scope>
<type>pom</type>
</dependency>
<dependency>
@@ -316,7 +329,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.42.1</version>
<version>12.1.1</version>
<executions>
<execution>
<id>test</id>
@@ -373,9 +386,9 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<version>3.1.1</version>
<configuration>
<doctitle>Ebean 11</doctitle>
<doctitle>Ebean 12</doctitle>
<overview>src/main/java/io/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
@@ -385,6 +398,8 @@
<artifactId>pygments-doclet</artifactId>
<version>1.0.0</version>
</docletArtifact>
<!-- for people using JDK11, our javadoc doclet isn't 11 compatible yet -->
<!-- <javadocExecutable>${env.JAVA8_HOME}/bin/javadoc</javadocExecutable>-->
<additionalparam>
-Xdoclint:none
-include-basedir ${project.basedir}
+14 -12
View File
@@ -396,15 +396,6 @@ public class DB {
* called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
* </p>
* <p>
* <b>{@link DatabaseConfig#setUpdatesDeleteMissingChildren(boolean)}: </b> When cascade saving to a
* OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children
* that are in the database but are not in the collection are deleted.
* </p>
* <p>
* <b>{@link DatabaseConfig#setUpdateChangesOnly(boolean)}: </b> The updateChangesOnly setting
* controls if only the changed properties are included in the update or if all the loaded
* properties are included instead.
* </p>
* <pre>{@code
*
* // A 'stateless update' example
@@ -414,9 +405,6 @@ public class DB {
* database.update(customer);
*
* }</pre>
*
* @see DatabaseConfig#setUpdatesDeleteMissingChildren(boolean)
* @see DatabaseConfig#setUpdateChangesOnly(boolean)
*/
public static void update(Object bean) throws OptimisticLockException {
getDefault().update(bean);
@@ -1237,4 +1225,18 @@ public class DB {
return getDefault().json();
}
/**
* Truncate the base tables for the given bean types.
*/
public static void truncate(Class<?>... types) {
getDefault().truncate(types);
}
/**
* Truncate the given tables.
*/
public static void truncate(String... tables) {
getDefault().truncate(tables);
}
}
+11 -24
View File
@@ -1196,17 +1196,6 @@ public interface Database {
* <b>Optimistic Locking: </b> Note that if the version property is not set when update() is
* called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
* </p>
* <p>
* <b>{@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: </b> When cascade saving to a
* OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children
* that are in the database but are not in the collection are deleted.
* </p>
* <p>
* <b>{@link ServerConfig#setUpdateChangesOnly(boolean)}: </b> The updateChangesOnly setting
* controls if only the changed properties are included in the update or if all the loaded
* properties are included instead.
* </p>
* <p>
* <pre>{@code
*
* // A 'stateless update' example
@@ -1216,9 +1205,6 @@ public interface Database {
* database.update(customer);
*
* }</pre>
*
* @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
* @see ServerConfig#setUpdateChangesOnly(boolean)
*/
void update(Object bean) throws OptimisticLockException;
@@ -1227,16 +1213,6 @@ public interface Database {
*/
void update(Object bean, Transaction transaction) throws OptimisticLockException;
/**
* Update a bean additionally specifying a transaction and the deleteMissingChildren setting.
*
* @param bean the bean to update
* @param transaction the transaction to use (can be null).
* @param deleteMissingChildren specify false if you do not want 'missing children' of a OneToMany
* or ManyToMany to be automatically deleted.
*/
void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException;
/**
* Update a collection of beans. If there is no current transaction one is created and used to
* update all the beans in the collection.
@@ -1580,4 +1556,15 @@ public interface Database {
* </p>
*/
<T> Set<String> validateQuery(Query<T> query);
/**
* Truncate all the given tables.
*/
void truncate(String... tables);
/**
* Truncate the base tables for the given bean types.
*/
void truncate(Class<?>... tables);
}
-3
View File
@@ -548,9 +548,6 @@ public final class Ebean {
* DB.update(customer);
*
* }</pre>
*
* @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
* @see ServerConfig#setUpdateChangesOnly(boolean)
*/
public static void update(Object bean) throws OptimisticLockException {
serverMgr.getDefaultServer().update(bean);
@@ -536,4 +536,12 @@ public interface ExpressionFactory {
*/
<T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent);
/**
* Add the expressions to the given expression list.
*
* @param where The expression list to add the expressions to
* @param expressions The expressions that are parsed
* @param params Bind parameters to match ? or ?1 bind positions.
*/
<T> void where(ExpressionList<T> where, String expressions, Object[] params);
}
+33 -6
View File
@@ -489,7 +489,26 @@ public interface ExpressionList<T> {
* Add some filter predicate expressions to the many property.
*/
@Nonnull
ExpressionList<T> filterMany(String prop);
ExpressionList<T> filterMany(String manyProperty);
/**
* Add filter expressions to the many property.
*
* <pre>{@code
*
* DB.find(Customer.class)
* .where()
* .eq("name", "Rob")
* .filterMany("orders", "status = ?", Status.NEW)
* .findList();
*
* }</pre>
*
* @param manyProperty The many property
* @param expressions Filter expressions with and, or and ? or ?1 type bind parameters
* @param params Bind parameters used in the expressions
*/
Query<T> filterMany(String manyProperty, String expressions, Object... params);
/**
* Specify specific properties to fetch on the main/root bean (aka partial
@@ -663,6 +682,14 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> where();
/**
* Add the expressions to this expression list.
*
* @param expressions The expressions that are parsed and added to this expression list
* @param params Bind parameters to match ? or ?1 bind positions.
*/
ExpressionList<T> where(String expressions, Object... params);
/**
* Path exists - for the given path in a JSON document.
* <pre>{@code
@@ -831,9 +858,9 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> inRange(String propertyName, Object value1, Object value2);
/**
* Between - property between the two given values.
*/
/**
* Between - property between the two given values.
*/
ExpressionList<T> between(String propertyName, Object value1, Object value2);
/**
@@ -1327,7 +1354,7 @@ public interface ExpressionList<T> {
*
* }</pre>
* <p>
* Note that we need to cast the Postgres array for UUID types like:
* Note that we need to cast the Postgres array for UUID types like:
* </p>
* <pre>{@code
*
@@ -1335,7 +1362,7 @@ public interface ExpressionList<T> {
*
* }</pre>
*
* @param raw The raw expression that is typically a subquery
* @param raw The raw expression that is typically a subquery
* @param values The values which is typically a list or set of id values.
*/
ExpressionList<T> rawOrEmpty(String raw, Collection<?> values);
@@ -13,6 +13,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* The extended API for Database.
@@ -107,6 +108,24 @@ public interface ExtendedServer {
@Nonnull
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Return the query result as a Stream using a single persistence context.
* <p>
* Note that the stream needs to be closed so use with try with resources.
* </p>
*/
@Nonnull
<T> Stream<T> findStream(Query<T> query, Transaction transaction);
/**
* Return the query result as a Stream (with multiple persistence contexts).
* <p>
* Note that the stream needs to be closed so use with try with resources.
* </p>
*/
@Nonnull
<T> Stream<T> findLargeStream(Query<T> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
+22
View File
@@ -148,6 +148,8 @@ public class FetchConfig implements Serializable {
private boolean queryAll;
private boolean cache;
/**
* Construct the fetch configuration object.
*/
@@ -188,6 +190,17 @@ public class FetchConfig implements Serializable {
return this;
}
/**
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
public FetchConfig cache() {
this.cache = true;
this.queryBatchSize = 0;
this.queryAll = true;
return this;
}
/**
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
@@ -247,6 +260,13 @@ public class FetchConfig implements Serializable {
return queryAll;
}
/**
* Return true if this uses L2 bean cache.
*/
public boolean isCache() {
return cache;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -255,6 +275,7 @@ public class FetchConfig implements Serializable {
FetchConfig that = (FetchConfig) o;
if (lazyBatchSize != that.lazyBatchSize) return false;
if (queryBatchSize != that.queryBatchSize) return false;
if (cache != that.cache) return false;
return queryAll == that.queryAll;
}
@@ -263,6 +284,7 @@ public class FetchConfig implements Serializable {
int result = lazyBatchSize;
result = 92821 * result + queryBatchSize;
result = 92821 * result + (queryAll ? 1 : 0);
result = 92821 * result + (cache ? 1 : 0);
return result;
}
}
+9
View File
@@ -1,5 +1,7 @@
package io.ebean;
import io.ebean.service.SpiFetchGroupQuery;
import javax.annotation.Nonnull;
/**
@@ -111,4 +113,11 @@ public interface FetchGroup<T> {
return XServiceProvider.fetchGroupOf(cls);
}
/**
* Return a query to be used by query beans for constructing FetchGroup.
*/
static <T> SpiFetchGroupQuery<T> queryFor(Class<T> beanType) {
return XServiceProvider.fetchGroupQueryFor(beanType);
}
}
@@ -67,12 +67,26 @@ public interface FetchGroupBuilder<T> {
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path);
/**
* Fetch the path including all its properties using L2 cache.
* Cache misses fallback to fetchQuery().
*/
@Nonnull
FetchGroupBuilder<T> fetchCache(String path);
/**
* Fetch the path including specified properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, String properties);
/**
* Fetch the path including specified properties using L2 cache.
* Cache misses fallback to fetchQuery().
*/
@Nonnull
FetchGroupBuilder<T> fetchCache(String path, String properties);
/**
* Fetch the path including all its properties lazily.
*/
+57
View File
@@ -11,6 +11,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Object relational query for finding a List, Set, Map or single entity bean.
@@ -501,6 +502,14 @@ public interface Query<T> {
*/
Query<T> fetchQuery(String path, String fetchProperties);
/**
* Fetch the path and properties using L2 bean cache.
*
* @param path The path of the beans we are fetching from L2 cache.
* @param fetchProperties The properties that should be loaded.
*/
Query<T> fetchCache(String path, String fetchProperties);
/**
* Fetch the path and properties lazily (via batch lazy loading).
* <p>
@@ -587,6 +596,11 @@ public interface Query<T> {
*/
Query<T> fetchQuery(String path);
/**
* Fetch the path eagerly using L2 cache.
*/
Query<T> fetchCache(String path);
/**
* Fetch the path lazily (via batch lazy loading).
* <p>
@@ -689,6 +703,49 @@ public interface Query<T> {
@Nonnull
QueryIterator<T> findIterate();
/**
* Execute the query returning the result as a Stream.
* <p>
* Note that this will hold all resulting beans in memory using a single
* persistence context. Use findLargeStream() for queries that expect to
* return a large number of results.
* </p>
* <pre>{@code
*
* // use try with resources to ensure Stream is closed
*
* try (Stream<Customer> stream = query.findStream()) {
* stream
* .map(...)
* .collect(...);
* }
*
* }</pre>
*/
@Nonnull
Stream<T> findStream();
/**
* Execute the query returning the result as a Stream.
* <p>
* Note that this uses multiple persistence contexts such that we can use
* it with a large number of results.
* </p>
* <pre>{@code
*
* // use try with resources to ensure Stream is closed
*
* try (Stream<Customer> stream = query.findLargeStream()) {
* stream
* .map(...)
* .collect(...);
* }
*
* }</pre>
*/
@Nonnull
Stream<T> findLargeStream();
/**
* Execute the query processing the beans one at a time.
* <p>
@@ -0,0 +1,18 @@
package io.ebean;
import javax.persistence.OptimisticLockException;
/**
* Thrown at SERIALIZABLE isolation level for non-recoverable concurrent conflict.
*/
public class SerializableConflictException extends OptimisticLockException {
private static final long serialVersionUID = 1L;
/**
* Create with a message and cause.
*/
public SerializableConflictException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.service.SpiFetchGroupQuery;
import io.ebean.service.SpiFetchGroupService;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebean.service.SpiRawSqlService;
@@ -71,4 +72,11 @@ class XServiceProvider {
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
return fetchGroupService.of(cls);
}
/**
* Return the FetchGroup Query for building fetch groups via query beans.
*/
static <T> SpiFetchGroupQuery<T> fetchGroupQueryFor(Class<T> cls) {
return fetchGroupService.queryFor(cls);
}
}
@@ -0,0 +1,22 @@
package io.ebean.bean;
/**
* A call origin for query execution profiling to collect graph use (for query tuning).
*/
public interface CallOrigin {
/**
* Return the top element. Typically the top stack element with class and line.
*/
String getTopElement();
/**
* Return the full description of the call origin.
*/
String getFullDescription();
/**
* Compute and return an origin key based on the query hash.
*/
String getOriginKey(int queryHash);
}
+22 -59
View File
@@ -3,6 +3,8 @@ package io.ebean.bean;
import java.io.Serializable;
import java.util.Arrays;
import static io.ebean.util.EncodeB64.enc;
/**
* Represent the call stack (stack trace elements).
* <p>
@@ -17,15 +19,15 @@ import java.util.Arrays;
* Note the call stack is trimmed to remove the common ebean internal elements.
* </p>
*/
public final class CallStack implements Serializable {
public final class CallStack implements Serializable, CallOrigin {
private static final long serialVersionUID = -8590644046907438579L;
private static final String NEWLINE = "\n";
private final String zeroHash;
private final String pathHash;
private final StackTraceElement[] callStack;
private final int hc;
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
@@ -37,12 +39,17 @@ public final class CallStack implements Serializable {
private int computeHashCode() {
int hc = 0;
for (StackTraceElement aCallStack : callStack) {
hc = 92821 * hc + aCallStack.hashCode();
for (StackTraceElement element : callStack) {
hc = 92821 * hc + element.hashCode();
}
return hc;
}
@Override
public String toString() {
return zeroHash + ":" + pathHash + ":" + callStack[0];
}
@Override
public int hashCode() {
return hc;
@@ -63,73 +70,29 @@ public final class CallStack implements Serializable {
/**
* Return the first element of the call stack.
*/
public StackTraceElement getFirstStackTraceElement() {
return callStack[0];
}
/**
* Return the call stack.
*/
public StackTraceElement[] getCallStack() {
return callStack;
}
/**
* Return the hash for the first stack element.
*/
public String getZeroHash() {
return zeroHash;
}
/**
* Return the hash for the stack elements (excluding first stack element).
*/
public String getPathHash() {
return pathHash;
}
@Override
public String toString() {
return zeroHash + ":" + pathHash + ":" + callStack[0];
public String getTopElement() {
return callStack[0].toString();
}
/**
* Return the call stack lines appended with the given newLine string.
*/
public String description(String newLine) {
@Override
public String getFullDescription() {
StringBuilder sb = new StringBuilder(400);
for (StackTraceElement aCallStack : callStack) {
sb.append(aCallStack.toString()).append(newLine);
for (int i = 0; i < callStack.length; i++) {
if (i > 0) {
sb.append(NEWLINE);
}
sb.append(callStack[i].toString());
}
return sb.toString();
}
@Override
public String getOriginKey(int queryHash) {
return enc(queryHash) + "." + zeroHash + "." + pathHash;
}
private static final int radix = 1 << 6;
private static final int mask = radix - 1;
/**
* Convert the integer to unsigned base 64.
*/
public static String enc(int i) {
char[] buf = new char[32];
int charPos = 32;
do {
buf[--charPos] = intToBase64[i & mask];
i >>>= 6;
} while (i != 0);
return new String(buf, charPos, (32 - charPos));
}
private static final char intToBase64[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
}
@@ -95,13 +95,10 @@ public final class EntityBeanIntercept implements Serializable {
private final byte[] flags;
private boolean fullyLoadedBean;
private boolean loadedFromCache;
private Object[] origValues;
private Exception[] loadErrors;
private int lazyLoadProperty = -1;
private Object ownerId;
private int sortOrder;
@@ -116,6 +113,14 @@ public final class EntityBeanIntercept implements Serializable {
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
public EntityBeanIntercept() {
this.owner = null;
this.flags = null;
}
/**
* Return the 'owning' entity bean.
*/
@@ -303,6 +308,22 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Set true when the bean has been loaded from L2 bean cache.
* The effect of this is that we should skip the cache if there
* is subsequent lazy loading (bean cache partially populated).
*/
public void setLoadedFromCache(boolean loadedFromCache) {
this.loadedFromCache = loadedFromCache;
}
/**
* Return true if this bean was loaded from L2 bean cache.
*/
public boolean isLoadedFromCache() {
return loadedFromCache;
}
/**
* Return true if the bean should be treated as readOnly. If a setter method
* is called when it is readOnly an Exception is thrown.
@@ -772,7 +793,7 @@ public final class EntityBeanIntercept implements Serializable {
}
public boolean[] getLoaded() {
boolean[] ret= new boolean[flags.length];
boolean[] ret = new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
@@ -16,7 +16,7 @@ public final class ObjectGraphOrigin implements Serializable {
private static final long serialVersionUID = 410937765287968708L;
private final CallStack callStack;
private final CallOrigin callOrigin;
private final String beanType;
@@ -24,11 +24,11 @@ public final class ObjectGraphOrigin implements Serializable {
private final String key;
public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) {
this.callStack = callStack;
public ObjectGraphOrigin(int queryHash, CallOrigin callOrigin, String beanType) {
this.callOrigin = callOrigin;
this.beanType = beanType;
this.queryHash = queryHash;
this.key = callStack.getOriginKey(queryHash);
this.key = callOrigin.getOriginKey(queryHash);
}
/**
@@ -49,22 +49,22 @@ public final class ObjectGraphOrigin implements Serializable {
/**
* The call stack involved.
*/
public CallStack getCallStack() {
return callStack;
public CallOrigin getCallOrigin() {
return callOrigin;
}
public String getFirstStackElement() {
return callStack.getFirstStackTraceElement().toString();
public String getTopElement() {
return callOrigin.getTopElement();
}
@Override
public String toString() {
return "key[" + key + "] type[" + beanType + "] " + callStack.getFirstStackTraceElement() + " ";
return "key[" + key + "] type[" + beanType + "] " + callOrigin.getTopElement();
}
@Override
public int hashCode() {
int hc = 92821 * callStack.hashCode();
int hc = 92821 * callOrigin.hashCode();
hc = 92821 * hc + beanType.hashCode();
hc = 92821 * hc + queryHash;
return hc;
@@ -82,6 +82,6 @@ public final class ObjectGraphOrigin implements Serializable {
ObjectGraphOrigin e = (ObjectGraphOrigin) obj;
return e.queryHash == queryHash
&& e.beanType.equals(beanType)
&& e.callStack.equals(callStack);
&& e.callOrigin.equals(callOrigin);
}
}
@@ -83,10 +83,13 @@ public abstract class AbstractNamingConvention implements NamingConvention {
}
@Override
public String getSequenceName(String tableName, String pkColumn) {
String s = TABLE_REPLACE.matcher(sequenceFormat).replaceAll(Matcher.quoteReplacement(tableName));
public String getSequenceName(String rawTableName, String pkColumn) {
final String tableNameUnquoted = databasePlatform.unQuote(rawTableName);
String s = TABLE_REPLACE.matcher(sequenceFormat).replaceAll(Matcher.quoteReplacement(tableNameUnquoted));
if (pkColumn == null) {
pkColumn = "";
} else {
pkColumn = databasePlatform.unQuote(pkColumn);
}
return COLUMN_REPLACE.matcher(s).replaceAll(Matcher.quoteReplacement(pkColumn));
}
@@ -36,8 +36,7 @@ public class MatchingNamingConvention extends AbstractNamingConvention {
@Override
public TableName getTableNameByConvention(Class<?> beanClass) {
return new TableName(getCatalog(), getSchema(), beanClass.getSimpleName());
return new TableName(quoteIdentifiers(getCatalog()), quoteIdentifiers(getSchema()), quoteIdentifiers(beanClass.getSimpleName()));
}
@Override
@@ -350,21 +350,11 @@ public class ServerConfig {
*/
private DbConstraintNaming constraintNaming = new DbConstraintNaming();
/**
* Behaviour of update to include on the change properties.
*/
private boolean updateChangesOnly = true;
/**
* Behaviour of updates in JDBC batch to by default include all properties.
*/
private boolean updateAllPropertiesInBatch;
/**
* Default behaviour for updates when cascade save on a O2M or M2M to delete any missing children.
*/
private boolean updatesDeleteMissingChildren = true;
/**
* Database platform configuration.
*/
@@ -2409,20 +2399,6 @@ public class ServerConfig {
this.skipCacheAfterWrite = skipCacheAfterWrite;
}
/**
* Return true to only update changed properties.
*/
public boolean isUpdateChangesOnly() {
return updateChangesOnly;
}
/**
* Set to true to only update changed properties.
*/
public void setUpdateChangesOnly(boolean updateChangesOnly) {
this.updateChangesOnly = updateChangesOnly;
}
/**
* Returns true if updates in JDBC batch default to include all properties by default.
*/
@@ -2442,22 +2418,6 @@ public class ServerConfig {
this.updateAllPropertiesInBatch = updateAllPropertiesInBatch;
}
/**
* Return true if updates by default delete missing children when cascading save to a OneToMany or
* ManyToMany. When not set this defaults to true.
*/
public boolean isUpdatesDeleteMissingChildren() {
return updatesDeleteMissingChildren;
}
/**
* Set if updates by default delete missing children when cascading save to a OneToMany or
* ManyToMany. When not set this defaults to true.
*/
public void setUpdatesDeleteMissingChildren(boolean updatesDeleteMissingChildren) {
this.updatesDeleteMissingChildren = updatesDeleteMissingChildren;
}
/**
* Return true if query statistics should be collected by ObjectGraphNode.
*/
@@ -2929,6 +2889,7 @@ public class ServerConfig {
}
loadDocStoreSettings(p);
maxCallStack = p.getInt("maxCallStack", maxCallStack);
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions);
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
@@ -2965,10 +2926,6 @@ public class ServerConfig {
skipCacheAfterWrite = p.getBoolean("skipCacheAfterWrite", skipCacheAfterWrite);
updateAllPropertiesInBatch = p.getBoolean("updateAllPropertiesInBatch", updateAllPropertiesInBatch);
updateChangesOnly = p.getBoolean("updateChangesOnly", updateChangesOnly);
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren);
updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren);
if (p.get("batch.mode") != null || p.get("persistBatching") != null) {
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
@@ -18,5 +18,10 @@ public enum DataErrorType {
/**
* Data integrity error like an invalid foreign key.
*/
DataIntegrity
DataIntegrity,
/**
* Non recoverable concurrency conflict.
*/
SerializableConflict
}
@@ -75,6 +75,11 @@ public class DatabasePlatform {
protected boolean caseSensitiveCollation = true;
/**
* Set true if the Database support LIMIT clause on sql update.
*/
protected boolean inlineSqlUpdateLimit;
/**
* For limit/offset, row_number etc limiting of SQL queries.
*/
@@ -139,9 +144,9 @@ public class DatabasePlatform {
*/
protected Platform platform = Platform.GENERIC;
protected String columnAliasPrefix = "c";
protected String truncateTable = "truncate table %s";
protected String tableAliasPlaceHolder = "${ta}";
protected String columnAliasPrefix = "c";
/**
* Use a BackTick ` at the beginning and end of table or column names that you
@@ -169,13 +174,14 @@ public class DatabasePlatform {
/**
* Characters escaped for startsWith, endsWith and contains.
*/
protected char[] likeSpecialCharacters = { '%', '_', '|' };
protected char[] likeSpecialCharacters = {'%', '_', '|'};
protected DbEncrypt dbEncrypt;
protected boolean idInExpandedForm;
protected boolean selectCountWithAlias;
protected boolean selectCountWithColumnAlias;
/**
* If set then use the FORWARD ONLY hint when creating ResultSets for
@@ -185,7 +191,7 @@ public class DatabasePlatform {
/**
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
*
* <p>
* This is {@code false} for HANA
*/
protected boolean supportsResultSetConcurrencyModeUpdatable = true;
@@ -337,6 +343,13 @@ public class DatabasePlatform {
return supportsSavepointId;
}
/**
* Return true if the platform supports LIMIT with sql update.
*/
public boolean isInlineSqlUpdateLimit() {
return inlineSqlUpdateLimit;
}
/**
* Return the maximum table name length.
* <p>
@@ -368,11 +381,11 @@ public class DatabasePlatform {
/**
* Return a DB Sequence based IdGenerator.
*
* @param be the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds the DataSource
* @param stepSize the sequence allocation size as defined by mapping (defaults to 50)
* @param seqName the name of the sequence
* @param be the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds the DataSource
* @param stepSize the sequence allocation size as defined by mapping (defaults to 50)
* @param seqName the name of the sequence
*/
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return null;
@@ -464,20 +477,6 @@ public class DatabasePlatform {
this.columnAliasPrefix = columnAliasPrefix;
}
/**
* Return the table alias placeholder.
*/
public String getTableAliasPlaceHolder() {
return tableAliasPlaceHolder;
}
/**
* Set the table alias placeholder.
*/
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
}
/**
* Return the close quote for quoted identifiers.
*/
@@ -577,7 +576,7 @@ public class DatabasePlatform {
/**
* Normally not needed - overridden in CockroachPlatform.
*/
public boolean isDdlCommitOnCreateIndex() {
public boolean isDdlAutoCommit() {
return false;
}
@@ -657,7 +656,7 @@ public class DatabasePlatform {
if (dbName != null && !dbName.isEmpty()) {
if (dbName.startsWith(openQuote)) {
// trim off the open and close quotes
return dbName.substring(1, dbName.length()-1);
return dbName.substring(1, dbName.length() - 1);
}
}
return dbName;
@@ -670,6 +669,14 @@ public class DatabasePlatform {
return selectCountWithAlias;
}
/**
* Return true if select count with subquery needs column alias (SQL Server).
*/
public boolean isSelectCountWithColumnAlias() {
return selectCountWithColumnAlias;
}
public String completeSql(String sql, Query<?> query) {
if (query.isForUpdate()) {
sql = withForUpdate(sql, query.getForUpdateMode());
@@ -708,6 +715,13 @@ public class DatabasePlatform {
return persistBatchOnCascade;
}
/**
* Return a statement to truncate a table.
*/
public String truncateStatement(String table) {
return String.format(truncateTable, table);
}
/**
* Create the DB schema if it does not exist.
*/
@@ -777,7 +791,7 @@ public class DatabasePlatform {
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
boolean escaped = false;
for (char escapeChar: likeSpecialCharacters) {
for (char escapeChar : likeSpecialCharacters) {
if (ch == escapeChar) {
if (sb == null) {
sb = new StringBuilder(value.substring(0, i));
@@ -3,6 +3,7 @@ package io.ebean.config.dbplatform;
import io.ebean.AcquireLockException;
import io.ebean.DataIntegrityException;
import io.ebean.DuplicateKeyException;
import io.ebean.SerializableConflictException;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
@@ -14,12 +15,12 @@ import java.util.Map;
*/
public class SqlCodeTranslator implements SqlExceptionTranslator {
private final Map<String,DataErrorType> map;
private final Map<String, DataErrorType> map;
/**
* Create given the map of SQLState codes to error types.
*/
public SqlCodeTranslator(Map<String,DataErrorType> map) {
public SqlCodeTranslator(Map<String, DataErrorType> map) {
this.map = map;
}
@@ -46,6 +47,8 @@ public class SqlCodeTranslator implements SqlExceptionTranslator {
return new DuplicateKeyException(message, e);
case DataIntegrity:
return new DataIntegrityException(message, e);
case SerializableConflict:
return new SerializableConflictException(message, e);
}
}
// return a generic exception
@@ -8,7 +8,7 @@ import java.util.Map;
*/
public class SqlErrorCodes {
private final Map<String,DataErrorType> map = new HashMap<>();
private final Map<String, DataErrorType> map = new HashMap<>();
/**
* Map the codes to AcquireLockException.
@@ -31,6 +31,13 @@ public class SqlErrorCodes {
return add(DataErrorType.DuplicateKey, codes);
}
/**
* Map the codes to SerializableConflictException.
*/
public SqlErrorCodes addSerializableConflict(String... codes) {
return add(DataErrorType.SerializableConflict, codes);
}
private SqlErrorCodes add(DataErrorType type, String... codes) {
for (String code : codes) {
map.put(code, type);
@@ -0,0 +1,4 @@
/**
* Clickhouse DB specific support.
*/
package io.ebean.config.dbplatform.clickhouse;
@@ -21,7 +21,7 @@ public class CockroachPlatform extends PostgresPlatform {
* Needs a commit after create index such that alter table add foreign key ... succeeds.
*/
@Override
public boolean isDdlCommitOnCreateIndex() {
public boolean isDdlAutoCommit() {
return true;
}
@@ -0,0 +1,4 @@
/**
* Cockroach DB specific support.
*/
package io.ebean.config.dbplatform.cockroach;
@@ -22,6 +22,7 @@ public class DB2Platform extends DatabasePlatform {
this.platform = Platform.DB2;
this.maxTableNameLength = 18;
this.maxConstraintNameLength = 18;
this.truncateTable = "truncate table %s reuse storage ignore delete triggers immediate";
this.sqlLimiter = new Db2SqlLimiter();
this.dbIdentity.setSupportsGetGeneratedKeys(true);
@@ -24,9 +24,9 @@ public class H2Platform extends DatabasePlatform {
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
this.supportsDeleteTableAlias = true;
this.inlineSqlUpdateLimit = true;
this.dbDefaultValue.setNow("now()");
this.columnAliasPrefix = null;
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("50200","HYT00")
@@ -18,7 +18,6 @@ public class HanaPlatform extends DatabasePlatform {
this.persistBatchOnCascade = PersistBatch.NONE;
this.supportsResultSetConcurrencyModeUpdatable = false;
this.columnAliasPrefix = null;
this.historySupport = new HanaHistorySupport();
this.basicSqlLimiter = new HanaBasicSqlLimiter();
@@ -21,7 +21,7 @@ public class HsqldbPlatform extends DatabasePlatform {
super();
this.platform = Platform.HSQLDB;
this.dbEncrypt = new H2DbEncrypt();
this.truncateTable = "delete from %s";
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
@@ -0,0 +1,14 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
public class MySql55Platform extends MySqlPlatform {
public MySql55Platform() {
super();
this.platform = Platform.MYSQL55;
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime"));
}
}
@@ -28,10 +28,10 @@ public class MySqlPlatform extends DatabasePlatform {
this.useExtraTransactionOnIterateSecondaryQueries = true;
this.selectCountWithAlias = true;
this.supportsSavepointId = false;
this.inlineSqlUpdateLimit = true;
this.dbEncrypt = new MySqlDbEncrypt();
this.historySupport = new MySqlHistorySupport();
this.columnAliasPrefix = null;
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
@@ -52,8 +52,8 @@ public class MySqlPlatform extends DatabasePlatform {
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClauseRaw = "like ? escape ''";
this.likeClauseEscaped = "like ? escape '|'";
this.likeClauseRaw = "like ? escape''";
this.likeClauseEscaped = "like ? escape'|'";
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
@@ -0,0 +1,10 @@
package io.ebean.config.dbplatform.nuodb;
import io.ebean.config.dbplatform.DbViewHistorySupport;
/**
* Runtime support for @History with NuoDB.
*/
public class NuoDbHistorySupport extends DbViewHistorySupport {
}
@@ -0,0 +1,62 @@
package io.ebean.config.dbplatform.nuodb;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
/**
* NuoDb specific platform.
*/
public class NuoDbPlatform extends DatabasePlatform {
public NuoDbPlatform() {
super();
this.platform = Platform.NUODB;
this.idInExpandedForm = true;
this.supportsResultSetConcurrencyModeUpdatable = false;
this.supportsDeleteTableAlias = true;
this.columnAliasPrefix = null;
this.likeClauseRaw = "like ?";
this.historySupport = new NuoDbHistorySupport();
//this.dbEncrypt = ...
dbIdentity.setIdType(IdType.IDENTITY);
dbIdentity.setSupportsIdentity(true);
dbIdentity.setSupportsSequence(true);
dbIdentity.setSupportsGetGeneratedKeys(true);
// No referential integrity exception to map
this.exceptionTranslator =
new SqlErrorCodes()
//.addAcquireLock("")
.addDuplicateKey("23000")
.addSerializableConflict("40002")
.build();
dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
}
@Override
public PlatformIdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, int stepSize, String seqName) {
return new NuoDbSequence(be, ds, seqName, stepSize);
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
switch (forUpdateMode) {
case NOWAIT:
return sql + " for update nowait";
case SKIPLOCKED:
default:
return sql + " for update";
}
}
}
@@ -0,0 +1,24 @@
package io.ebean.config.dbplatform.nuodb;
import io.ebean.BackgroundExecutor;
import io.ebean.config.dbplatform.SequenceStepIdGenerator;
import javax.sql.DataSource;
public class NuoDbSequence extends SequenceStepIdGenerator {
private final String nextSql;
/**
* Construct where batchSize is the sequence step size.
*/
public NuoDbSequence(BackgroundExecutor be, DataSource ds, String seqName, int stepSize) {
super(be, ds, seqName, stepSize);
this.nextSql = "select next value for " + seqName + " from dual";
}
@Override
public String getSql(int batchSize) {
return nextSql;
}
}
@@ -0,0 +1,4 @@
/**
* NuoDB specific support.
*/
package io.ebean.config.dbplatform.nuodb;
@@ -30,7 +30,7 @@ public class OraclePlatform extends DatabasePlatform {
this.sqlLimiter = new RownumSqlLimiter();
this.basicSqlLimiter = new BasicSqlAnsiLimiter();
this.historySupport = new OracleDbHistorySupport();
this.truncateTable = "truncate table %s cascade";
dbIdentity.setIdType(IdType.SEQUENCE);
dbIdentity.setSupportsSequence(true);
dbIdentity.setSupportsIdentity(true);
@@ -48,6 +48,7 @@ public class OraclePlatform extends DatabasePlatform {
//.addAcquireLock("")
.addDuplicateKey("1")
.addDataIntegrity("2291")
.addSerializableConflict("72000")
.build();
this.openQuote = "\"";
@@ -36,7 +36,7 @@ public class PostgresPlatform extends DatabasePlatform {
this.clobDbType = Types.VARCHAR;
this.nativeUuidType = true;
this.columnAliasPrefix = null;
this.truncateTable = "truncate table %s cascade";
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
@@ -52,6 +52,7 @@ public class PostgresPlatform extends DatabasePlatform {
.addAcquireLock("55P03")
.addDuplicateKey("23505")
.addDataIntegrity("23000","23502","23503","23514")
.addSerializableConflict("40001")
.build();
this.openQuote = "\"";
@@ -17,7 +17,7 @@ public class SQLitePlatform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(false);
this.dbIdentity.setSupportsSequence(false);
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
this.truncateTable = "delete from %s";
this.booleanDbType = Types.INTEGER;
this.likeClauseRaw = "like ?";
this.likeClauseEscaped = "like ?";
@@ -28,6 +28,7 @@ abstract class SqlServerBasePlatform extends DatabasePlatform {
this.persistBatchOnCascade = PersistBatch.NONE;
this.idInExpandedForm = true;
this.selectCountWithAlias = true;
this.selectCountWithColumnAlias = true;
this.sqlLimiter = new SqlServerSqlLimiter();
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
this.historySupport = new SqlServerHistorySupport();
@@ -136,6 +136,23 @@ public interface DbMigration {
*/
void setGeneratePendingDrop(String generatePendingDrop);
/**
* Set to true if ALTER TABLE ADD FOREIGN KEY should be generated with an option to skip validation.
* <p>
* Currently this is only useful for Postgres DDL adding the <code>NOT VALID</code> option.
* </p>
*/
void setAddForeignKeySkipCheck(boolean addForeignKeySkipCheck);
/**
* Set the lock timeout to be included with the DDL generation.
* <p>
* Currently this is only useful for Postgres migrations adding a <code>set lock_timeout</code>
* statement to the generated database migration.
* </p>
*/
void setLockTimeout(int seconds);
/**
* Add an additional platform to write the migration DDL.
* <p>
@@ -198,6 +215,7 @@ public interface DbMigration {
* An "init" migration can only be executed and used on a database that has had no
* prior migrations run on it.
* </p>
*
* @return the version of the generated migration
*/
String generateInitMigration() throws IOException;
@@ -41,6 +41,11 @@ public interface BeanPersistRequest<T> {
*/
Set<String> getUpdatedProperties();
/**
* Flags set for dirty properties (used by ElasticSearch integration).
*/
boolean[] getDirtyProperties();
/**
* Return true for an update request if at least one of dirty properties is contained
* in the given set of property names.
@@ -24,6 +24,11 @@ public interface BeanQueryRequest<T> {
*/
Query<T> getQuery();
/**
* Return true if an Id IN expression should have the bind parameters padded.
*/
boolean isPadInExpression();
/**
* Return true if multi-value binding using Array or Table Values is supported.
*/
@@ -2,6 +2,9 @@ package io.ebean.meta;
import io.ebean.ProfileLocation;
/**
* Meta data for captured query plan.
*/
public interface MetaQueryPlan {
/**
@@ -0,0 +1,15 @@
package io.ebean.service;
import io.ebean.FetchGroup;
import io.ebean.Query;
/**
* Extension of Query to build FetchGroup via query beans.
*/
public interface SpiFetchGroupQuery<T> extends Query<T> {
/**
* Build the fetch group with select and fetch clauses.
*/
FetchGroup<T> buildFetchGroup();
}
@@ -23,4 +23,10 @@ public interface SpiFetchGroupService {
* @return The FetchGroupBuilder to add additional select and fetch clauses
*/
<T> FetchGroupBuilder<T> of(Class<T> beanType);
/**
* Return a new FetchGroupQuery for building FetchGroup via query beans.
*/
<T> SpiFetchGroupQuery<T> queryFor(Class<T> beanType);
}
@@ -0,0 +1,32 @@
package io.ebean.util;
/**
* Integer to base64 encoder.
*/
public final class EncodeB64 {
private static final int radix = 1 << 6;
private static final int mask = radix - 1;
/**
* Convert the integer to unsigned base 64.
*/
public static String enc(int i) {
char[] buf = new char[32];
int charPos = 32;
do {
buf[--charPos] = intToBase64[i & mask];
i >>>= 6;
} while (i != 0);
return new String(buf, charPos, (32 - charPos));
}
private static final char intToBase64[] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};
}
@@ -4,8 +4,6 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
@@ -17,8 +15,6 @@ import java.util.Set;
*/
public class LoadBeanRequest extends LoadRequest {
private static final Logger logger = LoggerFactory.getLogger(LoadBeanRequest.class);
private final List<EntityBeanIntercept> batch;
private final LoadBeanBuffer loadBuffer;
@@ -27,11 +23,14 @@ public class LoadBeanRequest extends LoadRequest {
private final boolean loadCache;
private boolean loadedFromCache;
/**
* Construct for lazy load request.
*/
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, String lazyLoadProperty, boolean loadCache) {
this(LoadBuffer, null, true, lazyLoadProperty, loadCache);
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, EntityBeanIntercept ebi, boolean loadCache) {
this(LoadBuffer, null, true, ebi.getLazyLoadProperty(), loadCache);
this.loadedFromCache = ebi.isLoadedFromCache();
}
/**
@@ -56,10 +55,16 @@ public class LoadBeanRequest extends LoadRequest {
return loadBuffer.getBeanDescriptor().getBeanType();
}
public boolean isLoadCache() {
return loadCache;
/**
* Return true if the beans invoking lazy loading were previously loaded from cache.
*/
public boolean isLoadedFromCache() {
return loadedFromCache;
}
private boolean isLoadCache() {
return loadCache;
}
public String getDescription() {
return "path:" + loadBuffer.getFullPath() + " batch:" + batch.size();
@@ -75,17 +80,10 @@ public class LoadBeanRequest extends LoadRequest {
/**
* Return the load context.
*/
public LoadBeanBuffer getLoadContext() {
private LoadBeanBuffer getLoadContext() {
return loadBuffer;
}
/**
* Return the property that invoked the lazy loading.
*/
public String getLazyLoadProperty() {
return lazyLoadProperty;
}
public int getBatchSize() {
return getLoadContext().getBatchSize();
}
@@ -93,30 +91,14 @@ public class LoadBeanRequest extends LoadRequest {
/**
* Return the list of Id values for the beans in the lazy load buffer.
*/
public List<Object> getIdList(int batchSize) {
public List<Object> getIdList() {
List<Object> idList = new ArrayList<>(batchSize);
List<Object> idList = new ArrayList<>();
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
for (EntityBeanIntercept ebi : batch) {
EntityBean bean = ebi.getOwner();
idList.add(desc.getId(bean));
idList.add(desc.getId(ebi.getOwner()));
}
if (!desc.isMultiValueIdSupported() && !idList.isEmpty()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
// so we get the same query (for Ebean and the db)
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
// just add the first Id again
idList.add(firstId);
}
}
}
return idList;
}
@@ -5,6 +5,7 @@ import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
/**
* Controls the loading of reference objects for a query instance.
@@ -47,6 +48,11 @@ public interface LoadContext {
*/
void register(String path, EntityBeanIntercept ebi);
/**
* Register a Bean with inheritance.
*/
void register(String path, EntityBeanIntercept ebi, BeanPropertyAssocOne<?> property);
/**
* Register a collection for lazy loading.
*/
@@ -3,6 +3,7 @@ package io.ebeaninternal.api;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.util.StringHelper;
import io.ebeaninternal.server.core.BindPadding;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -65,13 +66,6 @@ public class LoadManyRequest extends LoadRequest {
return batch;
}
/**
* Return the load context.
*/
public LoadManyBuffer getLoadContext() {
return loadContext;
}
/**
* Return true if lazy loading should only load the id values.
* <p>
@@ -80,14 +74,14 @@ public class LoadManyRequest extends LoadRequest {
* used.
* </p>
*/
public boolean isOnlyIds() {
private boolean isOnlyIds() {
return onlyIds;
}
/**
* Return true if we should load the Collection ids into the cache.
*/
public boolean isLoadCache() {
private boolean isLoadCache() {
return loadCache;
}
@@ -98,22 +92,16 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBatchSize();
}
private List<Object> getParentIdList(int batchSize) {
private List<Object> getParentIdList() {
ArrayList<Object> idList = new ArrayList<>(batchSize);
List<Object> idList = new ArrayList<>();
BeanPropertyAssocMany<?> many = getMany();
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
}
if (!many.getTargetDescriptor().isMultiValueIdSupported()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
if (many.getTargetDescriptor().isPadInExpression()) {
BindPadding.padIds(idList);
}
return idList;
@@ -123,7 +111,7 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBeanProperty();
}
public SpiQuery<?> createQuery(SpiEbeanServer server, int batchSize) {
public SpiQuery<?> createQuery(SpiEbeanServer server) {
BeanPropertyAssocMany<?> many = getMany();
@@ -142,10 +130,7 @@ public class LoadManyRequest extends LoadRequest {
}
query.setLazyLoadForParents(many);
List<Object> idList = getParentIdList(batchSize);
many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());
many.addWhereParentIdIn(query, getParentIdList(), loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
@@ -184,7 +169,7 @@ public class LoadManyRequest extends LoadRequest {
Object parentId = desc.getId(ownerBean);
logger.debug("BeanCollection after lazy load was empty. type:" + ownerBean.getClass().getName() + " id:" + parentId + " owner:" + ownerBean);
}
} else if (isLoadCache()) {
} else if (isLoadCache() && many.isUseCache()) {
Object parentId = desc.getId(bc.getOwnerBean());
desc.cacheManyPropPut(many, bc, parentId);
}
@@ -11,7 +11,7 @@ import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.BeanLoader;
import io.ebean.bean.CallStack;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -81,7 +81,7 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader,
* graph costing.
* </p>
*/
CallStack createCallStack();
CallOrigin createCallOrigin();
/**
* Return the PersistenceContextScope to use defined at query or server level.
@@ -7,7 +7,7 @@ import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.bean.CallStack;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.readaudit.ReadEvent;
@@ -289,6 +289,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean selectAllForLazyLoadProperty();
/**
* Set the on a secondary query given the label, relativePath and profile location of the parent query.
*/
void setProfilePath(String label, String relativePath, ProfileLocation profileLocation);
/**
* Set the query mode.
*/
@@ -453,7 +458,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Set the query to be a delete query.
*/
void setDelete();
void setupForDeleteOrUpdate(boolean deleteRequest);
/**
* Set the query to be delete by ids due to cascading delete.
@@ -558,7 +563,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
* because the queryPlanHash is used to identify the query point.
* </p>
*/
ObjectGraphNode setOrigin(CallStack callStack);
ObjectGraphNode setOrigin(CallOrigin callOrigin);
/**
* Set the profile point of the bean or collection that is lazy loading.
@@ -75,9 +75,10 @@ public interface SpiTransaction extends Transaction {
boolean isRegisteredDeleteBean(Integer hash);
/**
* Unregister the persisted bean.
* Unregister the persisted beans. Expected after persisting top level beans
* and hence including all beans that were cascade persisted.
*/
void unregisterBean(Object bean);
void unregisterBeans();
/**
* Return true if this is a bean that has already been persisted in the
@@ -189,8 +189,8 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public void unregisterBean(Object bean) {
transaction.unregisterBean(bean);
public void unregisterBeans() {
transaction.unregisterBeans();
}
@Override
@@ -62,6 +62,16 @@ public interface SpiJsonWriter extends JsonWriter {
*/
void endAssocMany();
/**
* Start a Many.
*/
void beginAssocManyMap(String name, boolean elementCollection);
/**
* End a Many.
*/
void endAssocManyMap(boolean elementCollection);
/**
* Write value using underlying Jaskson object mapper if available.
*/
@@ -44,7 +44,7 @@ public class DdlGenerator {
private final boolean extraDdl;
private final boolean createOnly;
private final boolean jaxbPresent;
private final boolean ddlCommitOnCreateIndex;
private final boolean ddlAutoCommit;
private final String dbSchema;
private final ScriptTransform scriptTransform;
@@ -63,10 +63,10 @@ public class DdlGenerator {
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
this.runDdl = false;
this.ddlCommitOnCreateIndex = false;
this.ddlAutoCommit = false;
} else {
this.runDdl = serverConfig.isDdlRun();
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
this.ddlAutoCommit = server.getDatabasePlatform().isDdlAutoCommit();
}
this.scriptTransform = createScriptTransform(serverConfig.getMigrationConfig());
this.baseDir = initBaseDir();
@@ -160,13 +160,11 @@ public class DdlGenerator {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
try {
if (expectErrors) {
if (expectErrors || ddlAutoCommit) {
connection.setAutoCommit(true);
} else if (ddlCommitOnCreateIndex) {
runner.setCommitOnCreateIndex();
}
int count = runner.runAll(scriptTransform.transform(content), connection);
if (expectErrors) {
if (expectErrors || ddlAutoCommit) {
connection.setAutoCommit(false);
}
connection.commit();
@@ -259,7 +257,9 @@ public class DdlGenerator {
protected void runResourceScript(Connection connection, String sqlScript) throws IOException {
if (sqlScript != null) {
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
if (is != null) {
if (is == null) {
log.warn("sql script {} was not found as a resource", sqlScript);
} else {
String content = readContent(new InputStreamReader(is));
runScript(connection, false, content, sqlScript);
}
@@ -14,7 +14,9 @@ import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySql55Platform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.nuodb.NuoDbPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
@@ -24,6 +26,7 @@ import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import io.ebean.dbmigration.DbMigration;
import io.ebean.migration.MigrationVersion;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlWriter;
@@ -104,6 +107,8 @@ public class DefaultDbMigration implements DbMigration {
protected String version;
protected String name;
protected String generatePendingDrop;
private boolean addForeignKeySkipCheck;
private int lockTimeoutSeconds;
protected boolean includeBuiltInPartitioning = true;
@@ -178,6 +183,16 @@ public class DefaultDbMigration implements DbMigration {
this.name = name;
}
@Override
public void setAddForeignKeySkipCheck(boolean addForeignKeySkipCheck) {
this.addForeignKeySkipCheck = addForeignKeySkipCheck;
}
@Override
public void setLockTimeout(int seconds) {
this.lockTimeoutSeconds = seconds;
}
@Override
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
@@ -563,7 +578,8 @@ public class DefaultDbMigration implements DbMigration {
} else if (databasePlatform != null) {
// writer needs the current model to provide table/column details for
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
DdlOptions options = new DdlOptions(addForeignKeySkipCheck);
DdlWrite write = new DdlWrite(new MConfiguration(), request.current, options);
PlatformDdlWriter writer = createDdlWriter(databasePlatform);
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
@@ -621,8 +637,9 @@ public class DefaultDbMigration implements DbMigration {
*/
private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
DdlOptions options = new DdlOptions(addForeignKeySkipCheck);
for (Pair pair : platforms) {
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read(), options);
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
File subPath = platformWriter.subPath(writePath, pair.prefix);
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
@@ -630,7 +647,7 @@ public class DefaultDbMigration implements DbMigration {
}
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform) {
return new PlatformDdlWriter(platform, serverConfig, migrationConfig);
return new PlatformDdlWriter(platform, serverConfig, migrationConfig, lockTimeoutSeconds);
}
/**
@@ -786,6 +803,8 @@ public class DefaultDbMigration implements DbMigration {
return new HsqldbPlatform();
case POSTGRES:
return new PostgresPlatform();
case MYSQL55:
return new MySql55Platform();
case MYSQL:
return new MySqlPlatform();
case ORACLE:
@@ -804,6 +823,8 @@ public class DefaultDbMigration implements DbMigration {
return new SQLitePlatform();
case HANA:
return new HanaPlatform();
case NUODB:
return new NuoDbPlatform();
case COCKROACH:
return new CockroachPlatform();
case CLICKHOUSE:
@@ -0,0 +1,31 @@
package io.ebeaninternal.dbmigration.ddlgeneration;
/**
* Options used when generated DDL.
*/
public class DdlOptions {
private boolean foreignKeySkipCheck;
public DdlOptions(boolean foreignKeySkipCheck) {
this.foreignKeySkipCheck = foreignKeySkipCheck;
}
public DdlOptions() {
}
/**
* Return true if ADD FOREIGN KEY should use a skip check option.
*/
public boolean isForeignKeySkipCheck() {
return foreignKeySkipCheck;
}
/**
* Set to true if ADD FOREIGN KEY should use a skip check option.
*/
public void setForeignKeySkipCheck(boolean foreignKeySkipCheck) {
this.foreignKeySkipCheck = foreignKeySkipCheck;
}
}
@@ -26,17 +26,19 @@ public class DdlWrite {
private final DdlBuffer dropAll;
private final DdlOptions options;
/**
* Create without any configuration or current model (no history support).
*/
public DdlWrite() {
this(new MConfiguration(), new ModelContainer());
this(new MConfiguration(), new ModelContainer(), new DdlOptions());
}
/**
* Create with a configuration.
*/
public DdlWrite(MConfiguration configuration, ModelContainer currentModel) {
public DdlWrite(MConfiguration configuration, ModelContainer currentModel, DdlOptions options) {
this.currentModel = currentModel;
this.applyDropDependencies = new BaseDdlBuffer(configuration);
this.apply = new BaseDdlBuffer(configuration);
@@ -45,6 +47,14 @@ public class DdlWrite {
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
this.dropAll = new BaseDdlBuffer(configuration);
this.options = options;
}
/**
* Return the DDL options.
*/
public DdlOptions getOptions() {
return options;
}
/**
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.dbplatform.IdType;
import io.ebean.util.StringHelper;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.TableDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.IndexSet;
@@ -31,7 +32,6 @@ import io.ebeaninternal.dbmigration.model.MTable;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -462,7 +462,7 @@ public class BaseTableDdl implements TableDdl {
fkeyBuffer.appendStatement(platformDdl.createIndex(request.indexName(), tableName, request.cols()));
}
alterTableAddForeignKey(fkeyBuffer, request);
alterTableAddForeignKey(write.getOptions(), fkeyBuffer, request);
fkeyBuffer.end();
write.dropAllForeignKeys().appendStatement(platformDdl.alterTableDropForeignKey(tableName, request.fkName()));
@@ -473,9 +473,9 @@ public class BaseTableDdl implements TableDdl {
write.dropAllForeignKeys().end();
}
protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException {
protected void alterTableAddForeignKey(DdlOptions options, DdlBuffer buffer, WriteForeignKey request) throws IOException {
buffer.appendStatement(platformDdl.alterTableAddForeignKey(request));
buffer.appendStatement(platformDdl.alterTableAddForeignKey(options, request));
}
protected void appendColumns(String[] columns, DdlBuffer buffer) throws IOException {
@@ -635,7 +635,7 @@ public class BaseTableDdl implements TableDdl {
if (DdlHelp.isDropForeignKey(alterForeignKey.getColumnNames())) {
writer.apply().appendStatement(platformDdl.alterTableDropForeignKey(alterForeignKey.getTableName(), alterForeignKey.getName()));
} else {
writer.apply().appendStatement(platformDdl.alterTableAddForeignKey(new WriteForeignKey(alterForeignKey)));
writer.apply().appendStatement(platformDdl.alterTableAddForeignKey(writer.getOptions(), new WriteForeignKey(alterForeignKey)));
}
}
@@ -899,7 +899,7 @@ public class BaseTableDdl implements TableDdl {
protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException {
alterTableAddForeignKey(writer.apply(), new WriteForeignKey(alterColumn));
alterTableAddForeignKey(writer.getOptions(), writer.apply(), new WriteForeignKey(alterColumn));
}
protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException {
@@ -4,6 +4,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
import java.io.IOException;
@@ -40,7 +41,7 @@ public class ClickHouseDdl extends PlatformDdl {
}
@Override
public String alterTableAddForeignKey(WriteForeignKey request) {
public String alterTableAddForeignKey(DdlOptions options, WriteForeignKey request) {
return null;
}
@@ -10,7 +10,6 @@ public class CockroachDdl extends PlatformDdl {
public CockroachDdl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new PostgresHistoryDdl();
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
@@ -29,11 +29,11 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
protected String viewSuffix;
protected String historySuffix;
protected String currentTimestamp = "now(6)";
protected String sysPeriodType = "datetime(6)";
protected String now = "now(6)";
protected String sysPeriodEndValue = "now(6)";
public DbTriggerBasedHistoryDdl() {
DbTriggerBasedHistoryDdl() {
}
@Override
@@ -164,7 +164,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
protected void addSysPeriodColumns(DdlBuffer apply, String baseTableName, String whenCreatedColumn) throws IOException {
apply.append("alter table ").append(baseTableName).append(" add column ")
.append(sysPeriodStart).append(" ").append(sysPeriodType).append(" default ").append(currentTimestamp).endOfStatement();
.append(sysPeriodStart).append(" ").append(sysPeriodType).append(" default ").append(now).endOfStatement();
apply.append("alter table ").append(baseTableName).append(" add column ")
.append(sysPeriodEnd).append(" ").append(sysPeriodType).endOfStatement();
@@ -246,13 +246,12 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriodStart).append(",").append(sysPeriodEnd).append(",");
appendColumnNames(buffer, columns, "");
buffer.append(") values (OLD.").append(sysPeriodStart).append(", ").append(currentTimestamp).append(",");
buffer.append(") values (OLD.").append(sysPeriodStart).append(", ").append(sysPeriodEndValue).append(",");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}
protected void appendColumnNames(DdlBuffer buffer, List<String> columns, String columnPrefix) throws IOException {
void appendColumnNames(DdlBuffer buffer, List<String> columns, String columnPrefix) throws IOException {
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
buffer.append(", ");
@@ -265,8 +264,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
/**
* Append a single column to the buffer if it is not null.
*/
protected void appendColumnName(DdlBuffer buffer, String prefix, String columnName) throws IOException {
void appendColumnName(DdlBuffer buffer, String prefix, String columnName) throws IOException {
if (columnName != null) {
buffer.append(prefix).append(columnName);
}
@@ -280,8 +278,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
* the column.
* </p>
*/
protected List<String> columnNamesForApply(MTable table) {
List<String> columnNamesForApply(MTable table) {
return table.allHistoryColumns(true);
}
@@ -14,9 +14,10 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
public H2HistoryDdl() {
H2HistoryDdl() {
this.sysPeriodType = "timestamp";
this.currentTimestamp = "now()";
this.now = "now()";
this.sysPeriodEndValue = "now()";
}
@Override
@@ -42,16 +42,6 @@ public class MySqlDdl extends PlatformDdl {
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
}
/**
* It is rather complex to delete a column on MySql as there must not exist any foreign keys.
* That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
}
@Override
public String createCheckConstraint(String ckName, String checkConstraint) {
if (USE_CHECK_CONSTRAINT) {
@@ -60,6 +50,7 @@ public class MySqlDdl extends PlatformDdl {
return null;
}
}
@Override
public String alterTableAddCheckConstraint(String tableName, String checkConstraintName, String checkConstraint) {
if (USE_CHECK_CONSTRAINT) {
@@ -96,17 +87,13 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
// can't alter itself - done in alterColumnBaseAttributes()
return null;
}
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
// use alter
return "alter table " + tableName + " alter " + columnName + " " + suffix;
}
@@ -124,7 +111,6 @@ public class MySqlDdl extends PlatformDdl {
boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
String notnullClause = notnull ? " not null" : "";
// use modify
return "alter table " + tableName + " modify " + columnName + " " + type + notnullClause;
}
@@ -161,7 +147,7 @@ public class MySqlDdl extends PlatformDdl {
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
// alter comment currently not supported as it requires to repeat whole column definition
}
@@ -182,7 +168,6 @@ public class MySqlDdl extends PlatformDdl {
i++;
}
buffer.endOfStatement();
}
}
@@ -11,13 +11,11 @@ import java.io.IOException;
*/
public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
public MySqlHistoryDdl() {
MySqlHistoryDdl() {
}
@Override
protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException {
buffer.append("drop trigger ").append(updateTriggerName(baseTable)).endOfStatement();
buffer.append("drop trigger ").append(deleteTriggerName(baseTable)).endOfStatement();
}
@@ -0,0 +1,41 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
public class NuoDbDdl extends PlatformDdl {
public NuoDbDdl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new NuoDbHistoryDdl();
this.identitySuffix = " generated by default as identity";
this.dropConstraintIfExists = "drop constraint";
}
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
// do nothing
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
// do nothing
}
@Override
protected void appendForeignKeyOnUpdate(StringBuilder buffer, ConstraintMode mode) {
// do nothing
}
@Override
protected void appendForeignKeyOnDelete(StringBuilder buffer, ConstraintMode mode) {
if (mode == ConstraintMode.RESTRICT) {
// do nothing
} else if (mode == ConstraintMode.SET_NULL) {
// do nothing
} else {
super.appendForeignKeyOnDelete(buffer, mode);
}
}
}
@@ -0,0 +1,79 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.model.MTable;
import java.io.IOException;
/**
* NuoDB history support using DB triggers to maintain a history table.
*/
public class NuoDbHistoryDdl extends DbTriggerBasedHistoryDdl {
NuoDbHistoryDdl() {
this.now = "now()";
this.sysPeriodEndValue = "NEW.sys_period_start";
}
@Override
protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException {
buffer.append("drop trigger ").append(updateTriggerName(baseTable)).endOfStatement();
buffer.append("drop trigger ").append(deleteTriggerName(baseTable)).endOfStatement();
}
@Override
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
DbTriggerUpdate update = createDbTriggerUpdate(writer, table);
addBeforeUpdate(updateTriggerName(update.getBaseTable()), update);
addBeforeDelete(deleteTriggerName(update.getBaseTable()), update);
}
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
String baseTable = update.getBaseTable();
dropTriggers(buffer, baseTable);
addBeforeUpdate(updateTriggerName(baseTable), update);
addBeforeDelete(deleteTriggerName(baseTable), update);
}
private void addBeforeUpdate(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyTriggerBuffer();
addTriggerStart(triggerName, update, apply, " before update for each row as ");
apply.append(" NEW.sys_period_start = greatest(current_timestamp, date_add(OLD.sys_period_start, interval 1 microsecond))").endOfStatement();
appendInsertIntoHistory(apply, update.getHistoryTable(), update.getColumns());
addEndTrigger(apply);
}
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyTriggerBuffer();
addTriggerStart(triggerName, update, apply, " before delete for each row as");
appendInsertIntoHistory(apply, update.getHistoryTable(), update.getColumns());
addEndTrigger(apply);
}
private void addTriggerStart(String triggerName, DbTriggerUpdate update, DdlBuffer apply, String s) throws IOException {
apply
.append("delimiter $$").newLine()
.append("create or replace trigger ").append(triggerName).append(" for ").append(update.getBaseTable())
.append(s).newLine();
}
private void addEndTrigger(DdlBuffer apply) throws IOException {
apply.append("end_trigger")
.endOfStatement()
.append("$$").newLine()
.newLine();
}
}
@@ -11,6 +11,7 @@ import io.ebean.util.StringHelper;
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.PlatformTypeConverter;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.util.VowelRemover;
@@ -106,6 +107,8 @@ public class PlatformDdl {
protected String dropColumnSuffix = "";
protected String addForeignKeySkipCheck = "";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
*/
@@ -208,6 +211,13 @@ public class PlatformDdl {
return inlineForeignKeys;
}
/**
* By default this does nothing returning null / no lock timeout.
*/
public String setLockTimeout(int lockTimeoutSeconds) {
return null;
}
/**
* Write all the table columns converting to platform types as necessary.
*/
@@ -405,7 +415,7 @@ public class PlatformDdl {
/**
* Add foreign key.
*/
public String alterTableAddForeignKey(WriteForeignKey request) {
public String alterTableAddForeignKey(DdlOptions options, WriteForeignKey request) {
StringBuilder buffer = new StringBuilder(90);
buffer
@@ -418,6 +428,9 @@ public class PlatformDdl {
.append(lowerTableName(request.refTable()));
appendColumns(request.refCols(), buffer);
appendForeignKeySuffix(request, buffer);
if (options.isForeignKeySkipCheck()) {
buffer.append(addForeignKeySkipCheck);
}
return buffer.toString();
}
@@ -17,6 +17,11 @@ public class PostgresDdl extends PlatformDdl {
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
this.columnSetNull = "drop not null";
this.addForeignKeySkipCheck = " not valid";
}
public String setLockTimeout(int lockTimeoutSeconds) {
return "set lock_timeout = " + (lockTimeoutSeconds * 1000);
}
@Override
@@ -12,9 +12,9 @@ import java.util.List;
*/
public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
public PostgresHistoryDdl() {
this.currentTimestamp = "current_timestamp";
PostgresHistoryDdl() {
this.now = "current_timestamp";
this.sysPeriodEndValue = "current_timestamp";
}
/**
@@ -36,7 +36,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
protected void addSysPeriodColumns(DdlBuffer apply, String baseTableName, String whenCreatedColumn) throws IOException {
apply
.append("alter table ").append(baseTableName)
.append(" add column ").append(sysPeriod).append(" tstzrange not null default tstzrange(").append(currentTimestamp).append(", null)")
.append(" add column ").append(sysPeriod).append(" tstzrange not null default tstzrange(").append(now).append(", null)")
.endOfStatement();
if (whenCreatedColumn != null) {
@@ -2,6 +2,7 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
/**
* DB2 platform specific DDL.
@@ -25,7 +26,7 @@ public class SQLiteDdl extends PlatformDdl {
}
@Override
public String alterTableAddForeignKey(WriteForeignKey request) {
public String alterTableAddForeignKey(DdlOptions options, WriteForeignKey request) {
// not supported
return null;
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.dbmigration.model;
import io.ebean.config.DbConstraintNaming;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DefaultConstraintMaxLength;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
@@ -37,6 +38,8 @@ public class CurrentModel {
private DdlWrite write;
private DdlOptions ddlOptions = new DdlOptions();
/**
* Construct with a given EbeanServer instance for DDL create all generation, not migration.
*/
@@ -63,6 +66,10 @@ public class CurrentModel {
this.jaxbPresent = server.getServerConfig().getClassLoadConfig().isJavaxJAXBPresent();
}
public DdlOptions getDdlOptions() {
return ddlOptions;
}
/**
* Return true if the model contains tables that are partitioned.
*/
@@ -183,7 +190,7 @@ public class CurrentModel {
if (write == null) {
ChangeSet createChangeSet = getChangeSet();
write = new DdlWrite(new MConfiguration(), model);
write = new DdlWrite(new MConfiguration(), model, ddlOptions);
DdlHandler handler = handler();
handler.generateProlog(write);
@@ -261,6 +261,13 @@ public class MColumn {
return !draftOnly && !historyExclude;
}
public void clearForeignKey() {
this.references = null;
this.foreignKeyName = null;
this.fkeyOnDelete = null;
this.fkeyOnUpdate = null;
}
public Column createColumn() {
Column c = new Column();
@@ -273,8 +273,7 @@ public class MTable {
}
for (MCompoundUniqueConstraint constraint : uniqueConstraints) {
UniqueConstraint uq = constraint.getUniqueConstraint();
createTable.getUniqueConstraint().add(uq);
createTable.getUniqueConstraint().add(constraint.getUniqueConstraint());
}
return createTable;
@@ -789,4 +788,20 @@ public class MTable {
public void setPartitionMeta(PartitionMeta partitionMeta) {
this.partitionMeta = partitionMeta;
}
/**
* Clear foreign key as this element collection table logically references
* back to multiple tables.
*/
public MIndex setReusedElementCollection() {
MIndex index = null;
for (MColumn column : columns.values()) {
final String references = column.getReferences();
if (references != null) {
index = new MIndex(column.getForeignKeyIndex(), name, column.getName());
column.clearForeignKey();
}
}
return index;
}
}
@@ -312,11 +312,29 @@ public class ModelContainer {
return tables.put(table.getName(), table);
}
/**
* Add an element table taking into account if it is reused/references back
* to multiple bean types (and so can't have foreign key).
*/
public void addTableElementCollection(MTable table) {
final MTable reusedElementCollection = tables.get(table.getName());
if (reusedElementCollection != null) {
final MIndex index = reusedElementCollection.setReusedElementCollection();
if (index != null) {
indexes.put(index.getIndexName(), index);
}
} else {
if (table.isPartitioned()) {
partitionedTables.add(table);
}
tables.put(table.getName(), table);
}
}
/**
* Add a single column index.
*/
public void addIndex(String indexName, String tableName, String columnName) {
indexes.put(indexName, new MIndex(indexName, tableName, columnName));
}
@@ -324,7 +342,6 @@ public class ModelContainer {
* Add a multi column index.
*/
public void addIndex(String indexName, String tableName, String[] columnNames) {
indexes.put(indexName, new MIndex(indexName, tableName, columnNames));
}
@@ -33,10 +33,13 @@ public class PlatformDdlWriter {
private final PlatformDdl platformDdl;
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, DbMigrationConfig config) {
private final int lockTimeoutSeconds;
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, DbMigrationConfig config, int lockTimeoutSeconds) {
this.platformDdl = PlatformDdlBuilder.create(platform);
this.serverConfig = serverConfig;
this.config = config;
this.lockTimeoutSeconds = lockTimeoutSeconds;
}
/**
@@ -46,6 +49,13 @@ public class PlatformDdlWriter {
DdlHandler handler = handler();
handler.generateProlog(write);
if (lockTimeoutSeconds > 0) {
String lockSql = platformDdl.setLockTimeout(lockTimeoutSeconds);
if (lockSql != null) {
write.apply().append(lockSql).endOfStatement().newLine();
}
}
List<ChangeSet> changeSets = dbMigration.getChangeSet();
for (ChangeSet changeSet : changeSets) {
if (isApply(changeSet)) {
@@ -95,6 +95,10 @@ public class ModelBuildContext {
return model.addTable(table);
}
public void addTableElementCollection(MTable table) {
model.addTableElementCollection(table);
}
public void addIndex(String indexName, String tableName, String columnName) {
model.addIndex(indexName, tableName, columnName);
}
@@ -17,15 +17,11 @@ public class ModelBuildElementTable {
public static void build(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
BeanTable beanTable = manyProp.getBeanTable();
BeanDescriptor<?> targetDescriptor = manyProp.getTargetDescriptor();
MTable table = new MTable(beanTable.getBaseTable());
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
ctx.addTable(table);
ctx.addTableElementCollection(table);
}
}
@@ -149,7 +149,7 @@ public class AutoTuneDiffCollection {
origin.setKey(point.getKey());
origin.setBeanType(point.getBeanType());
origin.setDetail(entry.getDetail().toString());
origin.setCallStack(point.getCallStack().description("\n"));
origin.setCallStack(point.getCallOrigin().getFullDescription());
origin.setOriginal(query);
if (updateTuning) {
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.CallStack;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.AutoTuneConfig;
import io.ebean.config.AutoTuneMode;
@@ -97,7 +97,7 @@ public class BaseQueryTuner {
if (!useTuning(query)) {
if (profiling) {
profiling(query, server.createCallStack());
profiling(query, server.createCallOrigin());
}
return false;
}
@@ -110,8 +110,8 @@ public class BaseQueryTuner {
}
// create a query point to identify the query
CallStack stack = server.createCallStack();
ObjectGraphNode origin = query.setOrigin(stack);
CallOrigin callOrigin = server.createCallOrigin();
ObjectGraphNode origin = query.setOrigin(callOrigin);
if (profiling) {
if (profilingListener.isProfileRequest(origin, query)) {
@@ -154,10 +154,10 @@ public class BaseQueryTuner {
}
}
private void profiling(SpiQuery<?> query, CallStack stack) {
private void profiling(SpiQuery<?> query, CallOrigin call) {
// create a query point to identify the query
ObjectGraphNode origin = query.setOrigin(stack);
ObjectGraphNode origin = query.setOrigin(call);
if (profilingListener.isProfileRequest(origin, query)) {
// collect more profiling based on profiling rate etc
query.setProfilingListener(profilingListener);
@@ -177,7 +177,7 @@ public class CacheChangeSet {
final ManyKey key;
final List<Object> removes = new ArrayList<>();
final Set<Object> removes = new HashSet<>();
final Map<Object, CachedManyIds> puts = new LinkedHashMap<>();
@@ -13,6 +13,8 @@ public class CachedBeanDataToBean {
public static void load(BeanDescriptor<?> desc, EntityBean bean, CachedBeanData cacheBeanData, PersistenceContext context) {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
// any future lazy loading skips L2 bean cache
ebi.setLoadedFromCache(true);
BeanProperty idProperty = desc.getIdProperty();
if (desc.getInheritInfo() != null) {
@@ -0,0 +1,57 @@
package io.ebeaninternal.server.core;
import java.util.List;
/**
* Supports padding bind parameters for IN expressions.
* <p>
* We do this in order to get better hit ratio on DB query plans.
* </p>
*/
public final class BindPadding {
/**
* Pad out the Ids into common bucket sizes.
*
* @param idCollection The collection of Ids values being bound.
*/
public static void padIds(List<Object> idCollection) {
int extraIds = padding(idCollection.size());
if (extraIds > 0) {
// for performance make up the Id's to the batch size
// so we get the same query (for Ebean and the db)
Object firstId = idCollection.get(0);
for (int i = 0; i < extraIds; i++) {
// just add the first Id again
idCollection.add(firstId);
}
}
}
/**
* Extra padding on binding id's in order to get better hit ratio on DB prepared statements / query plans.
*/
static int padding(int size) {
if (size == 1) {
return 0;
}
if (size <= 5) {
return 5 - size;
}
if (size <= 10) {
return 10 - size;
}
if (size <= 20) {
return 20 - size;
}
if (size <= 40) {
return 40 - size;
}
if (size <= 50) {
return 50 - size;
}
return size <= 100 ? 100 - size : 0;
}
}
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.CallOrigin;
/**
* Creates CallOrigin based on the stack trace.
*/
public interface CallOriginFactory {
/**
* Create and return the CallStack given the stack trace elements.
*/
CallOrigin createCallOrigin();
}
@@ -1,14 +0,0 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.CallStack;
/**
* Creates CallStack based on the stack trace.
*/
public interface CallStackFactory {
/**
* Create and return the CallStack given the stack trace elements.
*/
CallStack createCallStack();
}
@@ -8,7 +8,9 @@ import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySql55Platform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.nuodb.NuoDbPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.Postgres8Platform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
@@ -81,6 +83,9 @@ public class DatabasePlatformFactory {
if (dbName.equals("mysql")) {
return new MySqlPlatform();
}
if (dbName.equals("mysql55")) {
return new MySql55Platform();
}
if (dbName.equals("postgres") || dbName.equals("postgres9")) {
return new PostgresPlatform();
}
@@ -108,6 +113,9 @@ public class DatabasePlatformFactory {
if (dbName.equals("clickhouse")) {
return new ClickHousePlatform();
}
if (dbName.equals("nuodb")) {
return new NuoDbPlatform();
}
if (dbName.equals("sqlite")) {
return new SQLitePlatform();
}
@@ -143,21 +151,24 @@ public class DatabasePlatformFactory {
*/
private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData, Connection connection) throws SQLException {
String dbProductName = metaData.getDatabaseProductName();
dbProductName = dbProductName.toLowerCase();
String dbProductName = metaData.getDatabaseProductName().toLowerCase();
final int majorVersion = metaData.getDatabaseMajorVersion();
final int minorVersion = metaData.getDatabaseMinorVersion();
if (dbProductName.contains("oracle")) {
return new OraclePlatform();
} else if (dbProductName.contains("microsoft")) {
throw new IllegalArgumentException("For SqlServer please explicitly choose either sqlserver16 or sqlserver17 as the platform via ServerConfig.setDatabasePlatformName. Refer to issue #1340 for more details");
} else if (dbProductName.contains("mysql")) {
return new MySqlPlatform();
return mysqlVersion(majorVersion, minorVersion);
} else if (dbProductName.contains("h2")) {
return new H2Platform();
} else if (dbProductName.contains("hsql database engine")) {
return new HsqldbPlatform();
} else if (dbProductName.contains("postgres")) {
return readPostgres(connection);
} else if (dbProductName.contains("nuo")) {
return new NuoDbPlatform();
} else if (dbProductName.contains("sqlite")) {
return new SQLitePlatform();
} else if (dbProductName.contains("db2")) {
@@ -174,10 +185,17 @@ public class DatabasePlatformFactory {
return new DatabasePlatform();
}
private DatabasePlatform mysqlVersion(int majorVersion, int minorVersion) {
if (majorVersion <= 5 && minorVersion <= 5) {
return new MySql55Platform();
}
return new MySqlPlatform();
}
/**
* Use a select version() query as it could be Postgres or CockroachDB.
*/
private static PostgresPlatform readPostgres(Connection connection) {
private static DatabasePlatform readPostgres(Connection connection) {
// Postgres driver uses a hardcoded product name so use version() query
PreparedStatement statement = null;
ResultSet resultSet = null;
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.CacheMode;
import io.ebean.ExpressionList;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
@@ -25,7 +26,7 @@ import java.util.List;
/**
* Helper to handle lazy loading and refreshing of beans.
*/
public class DefaultBeanLoader {
class DefaultBeanLoader {
private static final Logger logger = LoggerFactory.getLogger(DefaultBeanLoader.class);
@@ -33,63 +34,19 @@ public class DefaultBeanLoader {
private final boolean onIterateUseExtraTxn;
protected DefaultBeanLoader(DefaultServer server) {
DefaultBeanLoader(DefaultServer server) {
this.server = server;
this.onIterateUseExtraTxn = server.getDatabasePlatform().useExtraTransactionOnIterateSecondaryQueries();
}
/**
* Return a batch size that might be less than the requestedBatchSize.
* <p>
* This means we can have large and variable requestedBatchSizes.
* </p>
* <p>
* We want to restrict the number of different batch sizes as we want to
* re-use the query plan cache and get DB statement re-use.
* </p>
*/
private int getBatchSize(int batchSize) {
void loadMany(LoadManyRequest loadRequest) {
if (batchSize == 1) {
// there is only one bean/collection to load
return 1;
}
if (batchSize <= 5) {
// anything less than 5 becomes 5
return 5;
}
if (batchSize <= 10) {
return 10;
}
if (batchSize <= 20) {
return 20;
}
if (batchSize <= 50) {
return 50;
}
if (batchSize <= 100) {
return 100;
}
return batchSize;
}
public void refreshMany(EntityBean parentBean, String propertyName) {
refreshMany(parentBean, propertyName, null);
}
public void loadMany(LoadManyRequest loadRequest) {
List<BeanCollection<?>> batch = loadRequest.getBatch();
int batchSize = getBatchSize(batch.size());
SpiQuery<?> query = loadRequest.createQuery(server, batchSize);
SpiQuery<?> query = loadRequest.createQuery(server);
executeQuery(loadRequest, query);
loadRequest.postLoad();
}
public void loadMany(BeanCollection<?> bc, boolean onlyIds) {
void loadMany(BeanCollection<?> bc, boolean onlyIds) {
EntityBean parentBean = bc.getOwnerBean();
String propertyName = bc.getPropertyName();
@@ -97,8 +54,8 @@ public class DefaultBeanLoader {
loadManyInternal(parentBean, propertyName, null, false, onlyIds);
}
public void refreshMany(EntityBean parentBean, String propertyName, Transaction t) {
loadManyInternal(parentBean, propertyName, t, true, false);
void refreshMany(EntityBean parentBean, String propertyName) {
loadManyInternal(parentBean, propertyName, null, true, false);
}
private void loadManyInternal(EntityBean parentBean, String propertyName, Transaction t, boolean refresh, boolean onlyIds) {
@@ -125,7 +82,7 @@ public class DefaultBeanLoader {
parentDesc.contextPut(pc, parentId, parentBean);
}
boolean useManyIdCache = beanCollection != null && parentDesc.isManyPropCaching();
boolean useManyIdCache = beanCollection != null && parentDesc.isManyPropCaching() && many.isUseCache();
if (useManyIdCache) {
Boolean readOnly = null;
if (ebi.isReadOnly()) {
@@ -147,8 +104,7 @@ public class DefaultBeanLoader {
query.setLoadDescription("+lazy", null);
}
String idProperty = parentDesc.getIdBinder().getIdProperty();
query.select(idProperty);
query.select(parentDesc.getIdBinder().getIdProperty());
if (onlyIds) {
query.fetch(many.getName(), many.getTargetIdProperty());
@@ -185,16 +141,14 @@ public class DefaultBeanLoader {
/**
* Load a batch of beans for +query or +lazy loading.
*/
public void loadBean(LoadBeanRequest loadRequest) {
void loadBean(LoadBeanRequest loadRequest) {
List<EntityBeanIntercept> batch = loadRequest.getBatch();
if (batch.isEmpty()) {
throw new RuntimeException("Nothing in batch?");
}
int batchSize = getBatchSize(batch.size());
List<Object> idList = loadRequest.getIdList(batchSize);
List<Object> idList = loadRequest.getIdList();
if (idList.isEmpty()) {
// everything was loaded from cache
return;
@@ -202,6 +156,9 @@ public class DefaultBeanLoader {
SpiQuery<?> query = server.createQuery(loadRequest.getBeanType());
loadRequest.configureQuery(query, idList);
if (loadRequest.isLoadedFromCache()) {
query.setBeanCacheMode(CacheMode.PUT);
}
List<?> list = executeQuery(loadRequest, query);
loadRequest.postLoad(list);
@@ -228,7 +185,7 @@ public class DefaultBeanLoader {
refreshBeanInternal(bean, SpiQuery.Mode.REFRESH_BEAN, -1);
}
public void loadBean(EntityBeanIntercept ebi) {
void loadBean(EntityBeanIntercept ebi) {
refreshBeanInternal(ebi.getOwner(), SpiQuery.Mode.LAZYLOAD_BEAN, -1);
}
@@ -315,6 +272,5 @@ public class DefaultBeanLoader {
}
desc.resetManyProperties(dbBean);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.CallStack;
import java.util.Arrays;
@@ -7,7 +8,7 @@ import java.util.Arrays;
/**
* Default CallStackFactory where the Hash function for StackTraceElement includes the line number.
*/
public class DefaultCallStackFactory implements CallStackFactory {
public class DefaultCallOriginFactory implements CallOriginFactory {
private static final int IGNORE_LEADING_ELEMENTS = 5;
@@ -15,12 +16,12 @@ public class DefaultCallStackFactory implements CallStackFactory {
private final int maxCallStack;
DefaultCallStackFactory(int maxCallStack) {
DefaultCallOriginFactory(int maxCallStack) {
this.maxCallStack = maxCallStack;
}
@Override
public CallStack createCallStack() {
public CallOrigin createCallOrigin() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// ignore the first 6 as they are always avaje stack elements
@@ -58,7 +59,7 @@ public class DefaultCallStackFactory implements CallStackFactory {
return element.getMethodName().startsWith("_ebean_");
}
private CallStack createCallStack(StackTraceElement[] finalTrace) {
private CallOrigin createCallStack(StackTraceElement[] finalTrace) {
return new CallStack(finalTrace, finalTrace[0].hashCode(), pathHash(finalTrace));
}
@@ -35,7 +35,7 @@ import io.ebean.ValuePair;
import io.ebean.Version;
import io.ebean.annotation.TxIsolation;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.CallStack;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.ObjectGraphNode;
@@ -98,11 +98,11 @@ import io.ebeaninternal.server.query.CQueryEngine;
import io.ebeaninternal.server.query.CallableQueryCount;
import io.ebeaninternal.server.query.CallableQueryIds;
import io.ebeaninternal.server.query.CallableQueryList;
import io.ebeaninternal.server.query.DtoQueryEngine;
import io.ebeaninternal.server.query.LimitOffsetPagedList;
import io.ebeaninternal.server.query.QueryFutureIds;
import io.ebeaninternal.server.query.QueryFutureList;
import io.ebeaninternal.server.query.QueryFutureRowCount;
import io.ebeaninternal.server.query.DtoQueryEngine;
import io.ebeaninternal.server.querydefn.DefaultDtoQuery;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeaninternal.server.querydefn.DefaultOrmUpdate;
@@ -124,8 +124,10 @@ import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -134,11 +136,16 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.stream.StreamSupport.stream;
/**
* The default server side implementation of EbeanServer.
@@ -162,7 +169,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
private final ClockService clockService;
private final CallStackFactory callStackFactory;
private final CallOriginFactory callStackFactory;
/**
* Handles the save, delete, updateSql CallableSql.
@@ -310,12 +317,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Create the CallStackFactory depending if AutoTune is being used.
*/
private CallStackFactory initCallStackFactory(ServerConfig serverConfig) {
private CallOriginFactory initCallStackFactory(ServerConfig serverConfig) {
if (!serverConfig.getAutoTuneConfig().isActive()) {
// use a common CallStack for performance as we don't care with no AutoTune
return new NoopCallStackFactory();
return new NoopCallOriginFactory();
}
return new DefaultCallStackFactory(serverConfig.getMaxCallStack());
return new DefaultCallOriginFactory(serverConfig.getMaxCallStack());
}
private void configureServerPlugins() {
@@ -568,37 +575,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void refreshMany(Object parentBean, String propertyName) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName);
}
@Override
public void loadMany(LoadManyRequest loadRequest) {
beanLoader.loadMany(loadRequest);
}
@Override
public void loadMany(BeanCollection<?> bc, boolean onlyIds) {
beanLoader.loadMany(bc, onlyIds);
}
@Override
public void refresh(Object bean) {
beanLoader.refresh(checkEntityBean(bean));
}
@Override
public void loadBean(LoadBeanRequest loadRequest) {
beanLoader.loadBean(loadRequest);
}
@Override
public void loadBean(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
}
@@ -639,6 +640,36 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
externalModification(evt);
}
@Override
public void truncate(Class<?>... types) {
List<String> tableNames = new ArrayList<>();
for (Class<?> type : types) {
tableNames.add(getBeanDescriptor(type).getBaseTable());
}
truncate(tableNames.toArray(new String[0]));
}
@Override
public void truncate(String... tables) {
try (Connection connection = getDataSource().getConnection()) {
for (String table : tables) {
executeSql(connection, databasePlatform.truncateStatement(table));
}
connection.commit();
} catch (SQLException e) {
throw new PersistenceException("Error executing truncate", e);
}
}
private void executeSql(Connection connection, String sql) throws SQLException {
if (sql != null) {
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
transactionManager.log().sql().debug(sql);
stmt.execute();
}
}
}
/**
* Clear the query execution statistics.
*/
@@ -1103,7 +1134,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// if determine cost and no origin for AutoTune
if (query.getParentNode() == null) {
query.setOrigin(createCallStack());
query.setOrigin(createCallOrigin());
}
return new OrmQueryRequest<>(this, queryEngine, query, (SpiTransaction) t);
@@ -1320,8 +1351,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> boolean exists(SpiQuery<?> ormQuery, SpiTransaction transaction) {
ormQuery.setMaxRows(1);
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, ormQuery, transaction);
Query<?> ormQueryCopy = ormQuery.copy();
ormQueryCopy.setMaxRows(1);
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, ormQueryCopy, transaction);
try {
request.initTransIfRequired();
List<Object> ids = request.findIds();
@@ -1481,6 +1513,48 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> Stream<T> findLargeStream(Query<T> query, Transaction transaction) {
return findStreamWithSingleContext(false, query, transaction);
}
@Override
public <T> Stream<T> findStream(Query<T> query, Transaction transaction) {
return findStreamWithSingleContext(true, query, transaction);
}
private <T> Stream<T> findStreamWithSingleContext(boolean singleContext, Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, transaction);
if (singleContext) {
request.setIterateSingleContext();
}
try {
request.initTransIfRequired();
return toStream(request.findIterate());
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
}
private <T> Stream<T> toStream(QueryIterator<T> queryIterator) {
return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false)
.onClose(new QueryIteratorClose(queryIterator));
}
private static class QueryIteratorClose implements Runnable {
private final QueryIterator<?> iterator;
private QueryIteratorClose(QueryIterator<?> iterator) {
this.iterator = iterator;
}
@Override
public void run() {
iterator.close();
}
}
@Override
public <T> void findEach(Query<T> query, Consumer<T> consumer, Transaction t) {
@@ -1710,30 +1784,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
((EntityBean) bean)._ebean_getIntercept().setDirty(true);
}
/**
* Update the bean using the default 'updatesDeleteMissingChildren' setting.
*/
@Override
public void update(Object bean) {
update(bean, null);
}
/**
* Update the bean using the default 'updatesDeleteMissingChildren' setting.
*/
@Override
public void update(Object bean, Transaction t) {
persister.update(checkEntityBean(bean), t);
}
/**
* Update the bean specifying the deleteMissingChildren option.
*/
@Override
public void update(Object bean, Transaction t, boolean deleteMissingChildren) {
persister.update(checkEntityBean(bean), t, deleteMissingChildren);
}
@Override
public void updateAll(Collection<?> beans) throws OptimisticLockException {
updateAll(beans, null);
@@ -2277,8 +2337,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* </p>
*/
@Override
public CallStack createCallStack() {
return callStackFactory.createCallStack();
public CallOrigin createCallOrigin() {
return callStackFactory.createCallOrigin();
}
@Override
@@ -19,7 +19,7 @@ class DefaultSlowQueryListener implements SlowQueryListener {
String firstStack = "";
ObjectGraphNode node = event.getOriginNode();
if (node != null) {
firstStack = node.getOriginQueryPoint().getFirstStackElement();
firstStack = node.getOriginQueryPoint().getTopElement();
}
log.warn("Slow query warning - millis:{} rows:{} caller[{}] sql[{}]", event.getTimeMillis(), event.getRowCount(), firstStack, event.getSql());
}
@@ -514,7 +514,7 @@ public class InternalConfiguration {
String tz = serverConfig.getDataTimeZone();
if (tz == null) {
if (getPlatform() == Platform.MYSQL) {
if (isMySql(getPlatform())) {
return new LocalDataTimeZone();
}
return new NoDataTimeZone();
@@ -526,6 +526,10 @@ public class InternalConfiguration {
}
}
private boolean isMySql(Platform platform) {
return platform.base() == Platform.MYSQL;
}
public DataTimeZone getDataTimeZone() {
return dataTimeZone;
}
@@ -0,0 +1,17 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.CallStack;
/**
* A CallOriginFactory we can use when we don't use AutoTune.
*/
class NoopCallOriginFactory implements CallOriginFactory {
private final CallOrigin COMMON = new CallStack(Thread.currentThread().getStackTrace(), 0, 0);
@Override
public CallOrigin createCallOrigin() {
return COMMON;
}
}
@@ -1,16 +0,0 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.CallStack;
/**
* A CallStackFactory we can use when we don't use AutoTune.
*/
class NoopCallStackFactory implements CallStackFactory {
private final CallStack COMMON = new CallStack(Thread.currentThread().getStackTrace(), 0, 0);
@Override
public CallStack createCallStack() {
return COMMON;
}
}
@@ -87,6 +87,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private Set<String> dependentTables;
private boolean iterateSingleContext;
/**
* Create the InternalQueryRequest.
*/
@@ -97,12 +99,22 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
this.queryEngine = queryEngine;
this.query = query;
this.readOnly = query.isReadOnly();
this.persistenceContext = query.getPersistenceContext();
}
public PersistenceException translate(String bindLog, String sql, SQLException e) {
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public void setIterateSingleContext() {
this.iterateSingleContext = true;
}
public boolean isIterateSingleContext() {
return iterateSingleContext;
}
@Override
public boolean isDeleteByStatement() {
if (!transaction.isPersistCascade() || beanDescriptor.isDeleteByStatement()) {
@@ -115,6 +127,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
@Override
public boolean isPadInExpression() {
return beanDescriptor.isPadInExpression();
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
@@ -316,11 +333,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
* For iterate queries reset the persistenceContext and loadContext.
*/
public void flushPersistenceContextOnIterate() {
persistenceContext = new DefaultPersistenceContext();
loadContext.resetPersistenceContext(persistenceContext);
if (jsonRead != null) {
jsonRead.setPersistenceContext(persistenceContext);
jsonRead.setLoadContext(loadContext);
if (!iterateSingleContext) {
persistenceContext = new DefaultPersistenceContext();
loadContext.resetPersistenceContext(persistenceContext);
if (jsonRead != null) {
jsonRead.setPersistenceContext(persistenceContext);
jsonRead.setLoadContext(loadContext);
}
}
}
@@ -776,4 +795,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
dependentTables.addAll(tables);
}
}
/**
* Return true if no MaxRows or use LIMIT in SQL update.
*/
public boolean isInlineSqlUpdateLimit() {
return query.getMaxRows() < 1 || ebeanServer.getDatabasePlatform().isInlineSqlUpdateLimit();
}
}
@@ -20,7 +20,6 @@ public class PersistDeferredRelationship {
private final EntityBean bean;
public PersistDeferredRelationship(SpiEbeanServer ebeanServer, BeanDescriptor<?> beanDescriptor, EntityBean assocBean, ImportedId importedId, EntityBean bean) {
this.ebeanServer = ebeanServer;
this.beanDescriptor = beanDescriptor;
this.assocBean = assocBean;
@@ -41,13 +40,11 @@ public class PersistDeferredRelationship {
// bind the set clause for the importedId
int pos = importedId.bind(1, sqlUpdate, assocBean);
// bind the where clause for the bean
Object[] idValues = beanDescriptor.getIdBinder().getIdValues(bean);
for (int j = 0; j < idValues.length; j++) {
sqlUpdate.setParameter(pos + j, idValues[j]);
}
ebeanServer.execute(sqlUpdate, transaction);
}
}
@@ -83,6 +83,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private int flags;
private boolean saveRecurse;
private DocStoreMode docStoreMode;
private final ConcurrencyMode concurrencyMode;
@@ -104,8 +106,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private boolean notifyCache;
private boolean deleteMissingChildren;
/**
* Flag used to detect when only many properties where updated via a cascade. Used to ensure
* appropriate caches are updated in that case.
@@ -283,10 +283,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Check for batch escalation on cascade.
*/
public void checkBatchEscalationOnCascade() {
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
batchOnCascadeSet = !createdTransaction;
if (type == Type.UPDATE || beanDescriptor.isBatchEscalateOnCascade(type)) {
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
batchOnCascadeSet = !createdTransaction;
}
}
persistCascade = transaction.isPersistCascade();
}
@@ -425,6 +427,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Return the dirty properties on this request.
*/
@Override
public boolean[] getDirtyProperties() {
return dirtyProperties;
}
@@ -483,7 +486,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Collect L2 cache changes to be applied after the transaction has successfully committed.
*/
public void notifyCache(CacheChangeSet changeSet) {
private void notifyCache(CacheChangeSet changeSet) {
if (notifyCache) {
switch (type) {
case INSERT:
@@ -531,8 +534,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void addToQueue(DocStoreUpdates docStoreUpdates) {
switch (type) {
case INSERT:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case UPDATE:
case DELETE_SOFT:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
@@ -587,7 +588,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
public void unRegisterBean() {
transaction.unregisterBean(bean);
if (!saveRecurse) {
// only clear all persisted beans when persisting at the top level
transaction.unregisterBeans();
}
}
/**
@@ -629,20 +633,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return beanDescriptor;
}
/**
* Return true if a stateless update should also delete any missing details beans.
*/
public boolean isDeleteMissingChildren() {
return deleteMissingChildren;
}
/**
* Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
*/
public void setDeleteMissingChildren(boolean deleteMissingChildren) {
this.deleteMissingChildren = deleteMissingChildren;
}
/**
* Prepare the update after potential modifications in a BeanPersistController.
*/
@@ -751,14 +741,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return parentBean;
}
/**
* Return the controller if there is one associated with this type of bean. This returns null if
* there is no controller associated.
*/
public BeanPersistController getBeanController() {
return controller;
}
/**
* Return the intercept if there is one.
*/
@@ -1051,16 +1033,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Return true if the update DML/SQL must be dynamically generated.
* <p>
* This is the case for updates/deletes of partially populated beans.
* </p>
*/
public boolean isDynamicUpdateSql() {
return beanDescriptor.isUpdateChangesOnly() || !intercept.isFullyLoadedBean();
}
/**
* Return true if the property should be included in the update.
*/
@@ -1290,6 +1262,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
private void executeInsert() {
setGeneratedId();
setTenantId();
if (controller == null || controller.preInsert(this)) {
beanManager.getBeanPersister().insert(this);
@@ -1486,4 +1459,19 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
this.saveManyIntersections.add(saveManyIntersection);
}
public boolean isForcedUpdate() {
return Flags.isUpdateForce(flags);
}
/**
* Set when this request is from cascading persist.
*/
public void setSaveRecurse() {
saveRecurse = true;
}
public void setGeneratedId() {
beanDescriptor.setGeneratedId(entityBean, transaction);
}
}
@@ -30,11 +30,6 @@ public interface Persister {
*/
void update(EntityBean entityBean, Transaction t);
/**
* Update the bean specifying deleteMissingChildren.
*/
void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
/**
* Force an Insert using the given bean.
*/
@@ -8,6 +8,7 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HanaColumnStoreDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MySqlDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.NuoDbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.Oracle10Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.PlatformDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.PostgresDdl;
@@ -29,10 +30,13 @@ public class PlatformDdlBuilder {
return new H2Ddl(platform);
case DB2:
return new DB2Ddl(platform);
case MYSQL55:
case MYSQL:
return new MySqlDdl(platform);
case HSQLDB:
return new HsqldbDdl(platform);
case NUODB:
return new NuoDbDdl(platform);
case ORACLE:
return new Oracle10Ddl(platform);
case SQLITE:

Some files were not shown because too many files have changed in this diff Show More