Compare commits

..
Author SHA1 Message Date
rob bygrave 3ac7ea6e60 [maven-release-plugin] prepare release ebean-10.4.6 2017-09-13 23:02:25 +12:00
rob bygrave b2e0271ce7 #1128 - Allow "2 word SQL" like TRUNCATE t, ANALYSE t etc 2017-09-13 21:40:17 +12:00
rob bygrave c7a7651602 #1127 - Remove the ebean.query.globallimit of 1 million "backstop" 2017-09-13 21:22:03 +12:00
rob bygrave 1e6678cbb4 #1126 - Regression bug in 10.4.5 with new line character use with SqlQuery, SqlUpdate and CallableSql 2017-09-13 21:14:19 +12:00
Roland PramlandRob Bygrave a6e4e181bf Feature/cache improvements 2 (#1067)
* New Mode to control queryCache behavior

* Collections form the cache have to be read only

* findCount not queried in query cache

* return modifiable collections from the cache

* FIX: Compile errors

* No effective code change: reformat code / javadoc
2017-09-12 23:40:18 +12:00
Roland PramlandRob Bygrave b8e1dffca2 replaced findUnique by "default" method in interfaces. (#1124) 2017-09-12 23:20:04 +12:00
rob bygrave 7e8ed72b7b #1125 - ENH: Add a SpiContainerBootup API ... to enable automatic startup of docker containers (for testing usually) 2017-09-12 22:44:37 +12:00
rob bygrave ec8dd30a04 [maven-release-plugin] prepare for next development iteration 2017-09-11 23:12:43 +12:00
rob bygrave f81d5c9f3a [maven-release-plugin] prepare release ebean-10.4.5 2017-09-11 23:12:29 +12:00
rob bygrave 74b24b1db1 #1120 - Bump classpath scanner dependency to 2.2.4 (fix for NPE on empty directory) 2017-09-11 23:09:28 +12:00
rob bygrave 179ef8c8a0 No effective change - javadoc update on Ebean 2017-09-10 21:23:37 +12:00
rob bygrave f77f3c3d6e #1119 - Deprecate TxRunnable and TxCallable - migrate to plan JDK Runnable and Callable. Add explicit executeCall() method 2017-09-10 20:47:26 +12:00
Roland PramlandRob Bygrave 9a7646195c Featur/dbmigration 3 (#1118)
* ADD: more test classes for Softdelete and History

* update reference models after adding test classes

* DbMigration:Improved History generation for sqlserver

* FIX: When DB supports Sql2011 platform, we must not alter the history-table (as this is handled by DB itself)

* DbMigration: When dropping a table, drop the sequence also.

* DbMigration: dropColumn is handled by platformDdl now, as SqlServerDdl needs to delete dependent objects (default constraints...) first

* FIX: DbMigration is not executed on history table

* DbMigration: can drop comment now.

* No effective code change: completed ObjectFactory / fixed imports

* FIX: drop table comment

* Committed the reference models
2017-09-10 15:26:01 +12:00
rob bygrave 84d3578e33 No effective change - add a test for saveAll() with existing batch mode on
Assert that it does not flush the batch.  We want the batch to continue and flush normally.
2017-09-09 14:14:57 +12:00
rob bygrave ef05b81014 #1117 - Refactor - moved DbDefault and DbMigration annotations to ebean-annotation 2017-09-09 13:27:09 +12:00
rob bygrave 726c682984 #1116 - Remove references to annotation EbeanDDL which is no longer needed 2017-09-09 13:18:39 +12:00
rob bygrave 0c22a99fdf #1116 - Remove references to annotation EbeanDDL which is no longer needed 2017-09-09 13:12:40 +12:00
rob bygrave c062c8e001 #1054 Java 9 - adding "Automatic-Module-Name" to MANIFEST.MF initial step
Update dependency to ebean-migration 10.2.1, avaje-classpath-scanner and avaje-datasource to avoid package name clash
2017-09-09 01:53:47 +12:00
rob bygrave 06f3c000f5 #1115 - Trim SQL for SqlUpdate, SqlQuery and CallableSql - also replace \n with space 2017-09-08 23:45:28 +12:00
rob bygrave 8c0232fdb8 #1114 - Deprecate findUnique() - please migrate to findOne() - update tests 2017-09-08 22:48:46 +12:00
rob bygrave 2470486f20 #1114 - Deprecate findUnique() - please migrate to findOne() 2017-09-08 22:37:50 +12:00
rob bygrave 8fbf0374dd No effective change - tidy around Db migration changes 2017-09-08 22:02:56 +12:00
Roland PramlandRob Bygrave dd8fff0277 Feature/ddl migration (#1111)
* REFACTOR: use functions from ddl-help

* NEW: Default values are written to DDL

* fix testcase for sqlserver as default constraints will get names in future

* Reading DbMigration

* ADD: DbmIgrationGenerateTest

* Tables must be created first

* Sanity checks and some getters

* DdlMigration gets applied now

* ADD: Testcase and reference migration scripts

* no effective code changes: add comments
2017-09-08 21:47:33 +12:00
Rob Bygrave 73b3d1d977 [maven-release-plugin] prepare for next development iteration 2017-09-07 00:41:49 +12:00
Rob Bygrave 2a76ae2192 [maven-release-plugin] prepare release ebean-10.4.4 2017-09-07 00:41:36 +12:00
Rob Bygrave 39931a3a2c No effective change - add test example of markUnset on version property (to avoid occ check) 2017-09-07 00:40:19 +12:00
Rob Bygrave 58eb49617b #1113 - SqlUpdate with leading spaces leads to Invalid event detection (which table was update for L2 cache invalidation etc) 2017-09-07 00:36:52 +12:00
Rob Bygrave 9b621d2ec7 #1112 - Enhance JDBC batch behavior when cascade persist is turned off (manual master/detail batch persisting) 2017-09-06 23:57:54 +12:00
Roland PramlandRob Bygrave f2e8b652fa FIX: DDL process comment change correctly & drop references when index is dropped (#1109) 2017-09-01 20:45:06 +12:00
Roland PramlandRob Bygrave f342d9d561 API-CHANGE: DbMigration returns which version was generated (#1108) 2017-09-01 20:42:02 +12:00
Roland PramlandRob Bygrave 8009bdf099 REFACTOR: Created a "splitNames" in StringHelper and use it in various places. Also wrote some tests for StringHelper. (#1107) 2017-09-01 20:40:39 +12:00
Roland PramlandRob Bygrave 7336ce81bd FIX: Do not add AssocOne-generatedProperties if they should not. (#1106) 2017-09-01 20:38:13 +12:00
Roland PramlandRob Bygrave f805bec8f2 FIX: Using correct datatype for null value when json is serialized to varchar column (#1105) 2017-09-01 20:36:41 +12:00
Roland PramlandRob Bygrave 46cdc84e6b Refactor/findannotation (#1104)
* refactor: renamed findAnnotation method that searches recursive to findAnnotationRecursive

* CHG: searching namedQueries & indices also recursive in superclasses

* FIX: Using the AnnotationBase methods on several places and documented them
2017-09-01 20:25:10 +12:00
Roland PramlandRob Bygrave e908d16ba8 Pr/add tests (#1103)
* ADD: Testcase for Sqlserver-6.2.0 driver

* CHG: Removed Row-limit - so we need no special handling when sqlserver-platform generates a "select top 100" statement

* ADD: additional asserts to TestQueryCache

* CHG: Ignore test for sqlserver
2017-09-01 20:17:56 +12:00
rob bygrave 96e3f4138f #1064 - Query table alias not used in update() or delete() queries 2017-08-31 21:29:08 +12:00
rob bygrave 977dd52890 No effective change - code reformat only 2017-08-31 21:10:13 +12:00
Roland PramlandRob Bygrave a9b7bb2175 FIX: Passing MDC through background-executor (#1102) 2017-08-31 21:09:04 +12:00
rob bygrave f83c7fdac8 #1100 - Refactor OrmQueryPlanKey to use string expression for where 2017-08-31 02:11:49 +12:00
rob bygrave a695fb59b2 [maven-release-plugin] prepare for next development iteration 2017-08-31 01:50:20 +12:00
rob bygrave e197bb2900 [maven-release-plugin] prepare release ebean-10.4.3 2017-08-31 01:50:06 +12:00
rob bygrave b54b8815d9 Merge branch 'master' of github.com:ebean-orm/ebean 2017-08-30 21:59:08 +12:00
rob bygrave 1d1fde61be #1101 - findNative() on a bean with an associated @OneToOne (with the PK being derived from the parent) maps incorrectly 2017-08-30 21:58:33 +12:00
rob bygrave 683186cbe9 No effective change - pom whitespace only 2017-08-30 21:14:20 +12:00
Roy de BokxandRob Bygrave 5e6be40454 Only throwing warning for autocommit when server is not set to use autocommit-friendly TransactionManager. (#1085) 2017-08-30 20:57:49 +12:00
Roland PramlandRob Bygrave c00febd95b ENH: Abort all tests if connection to db could not be esablished (#1099) 2017-08-30 20:04:50 +12:00
Roland PramlandRob Bygrave 4c8e1113bf FIX: deadlock in TestCommitAndContiune for sqlserver & hsqldb (#1098) 2017-08-30 20:03:41 +12:00
Roland PramlandRob Bygrave 6f854da70b Fix/corrected error codes (#1097)
* FIX: db2plaform error codes and dbtypes

* FIX: H2: Add error code for locking

* FIX: Mysql: Add DataIntegrity-error code
2017-08-30 20:02:34 +12:00
Roland PramlandRob Bygrave 3f5a162a4e Fix/connection to different platforms (#1096)
* ADD: Profile for oracle and db2 to pom file. Downgraded mysql driver

* FIX: connection string for several platforms

* ADD: Documentation
2017-08-30 20:00:58 +12:00
Roland PramlandRob Bygrave d14529b602 FIX: optimized unique constraint handling for platforms that does support them on nullable columns (#1095) 2017-08-30 19:54:53 +12:00
Roland PramlandRob Bygrave f471961f06 FIX: extra-ddl modified to be compatible with different platfoms (#1094) 2017-08-30 17:51:41 +12:00
Roland PramlandRob Bygrave 457c1356a4 Fix/shortened tablenames (#1093)
* FIX: Shortened tablename (will fail on oracle)

* FIX: renamed table, otherwise, in db2 he_link_doc & hi_link_ are both generating the same index ix_h_lnk_dc_h_dc
2017-08-30 17:50:40 +12:00
Roland PramlandRob Bygrave 419ac2bf50 FIX: Possible NPE in StringHelper (#1092) 2017-08-30 17:49:34 +12:00
Roland PramlandRob Bygrave 727d088577 FIX: add MAX-DATE as default value for ROW END (#1091) 2017-08-30 17:48:36 +12:00
Roland PramlandRob Bygrave 972d2dcc6b FIX: nvarchar(max) length specified differently, otherwise this leads to a ddl like this "nvarchar(max)(700)" (#1090) 2017-08-30 17:47:21 +12:00
Roland PramlandRob Bygrave a5bcd49ecb FIX: Clean up before running tests - otherwise tests will fail if a previous test did not clean up well. (This fixes test failures in Eclipse where tests are executed in different order than in maven) (#1089) 2017-08-30 17:44:30 +12:00
Roland PramlandRob Bygrave ff496ad2d3 FIX: EnumToDbIntegerMap did not set 'integerType' - this leads to an incompatible quoting. Constraints like "check in ('1','2','3')" were not accepted by hsqldb on integer columns. (#1088) 2017-08-30 17:42:25 +12:00
Roland PramlandRob Bygrave 24d438ba23 FIX: H2HistoryTrigger honors schema (#1087) 2017-08-30 17:38:52 +12:00
Roland PramlandRob Bygrave 591067e39e FIX: Typo in classname (#1086) 2017-08-30 17:37:45 +12:00
rob bygrave 38a95dc5c4 No effective change - Reformat code - ScalarTypeEnumStandard 2017-08-24 22:37:41 +12:00
icodeandRob Bygrave 53426d8493 use jackson configuration [SD]eserialization enum (#1079)
* use jackson configuration [SD]eserialization enum

https://github.com/FasterXML/jackson-databind/wiki/Serialization-features 
https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features

keep jackson and ebean [SD]eserialization same
`objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_INDEX);`

* Update ScalarTypeEnumStandard.java

* Update ScalarTypeEnumStandard.java

* Update ScalarTypeEnumStandard.java

* Update ScalarTypeEnumStandard.java
2017-08-24 22:35:36 +12:00
rob bygrave a06238558e No effective change - Jackson WRITE_ENUMS_USING_INDEX test 2017-08-24 22:34:57 +12:00
Yuan XueguiandRob Bygrave b3a3b19d5d Fix: query table entity or sql entity has no id (#1082)
When query table entity or sql entity,include NamedQuery which modified via code and RawSqlQuery which modified via code.
2017-08-24 22:18:46 +12:00
rob bygrave 57231c80a8 #1084 - NPE When DB Migration generation run with @WhoCreated mapping 2017-08-24 22:15:01 +12:00
rob bygrave dc96fb9ad5 No effective change - update test, better asserts 2017-08-15 21:47:21 +12:00
rob bygrave 567847b47a #1081 - Update jackson-core dependency to 2.9.0 2017-08-05 00:11:53 +12:00
rob bygrave b74fed7073 Bump to 10.4.1 agent (@Transient properties included in mapped superclass enhancement detection) 2017-08-04 22:59:53 +12:00
rob bygrave cdaf8ee6d5 [maven-release-plugin] prepare for next development iteration 2017-08-04 21:33:33 +12:00
rob bygrave c6c5073e47 [maven-release-plugin] prepare release ebean-10.4.2 2017-08-04 21:33:19 +12:00
rob bygrave 2a58b954e9 #1062 - Multi-tenant - Update statements should include tenantId in where clause 2017-08-04 21:16:14 +12:00
Koen De GrooteandRob Bygrave 9772623880 Added the Serializable interface to classes that extend/implement a Comparator with an anonymous/typed class. (#1078) 2017-08-03 23:29:56 +12:00
rob bygrave 27b6183aad Merge branch 'master' of github.com:ebean-orm/ebean 2017-08-03 23:28:35 +12:00
David HofmannandRob Bygrave 55e19618f8 fix connection leak in JTA managed transactions (#1077) 2017-08-03 23:28:21 +12:00
rob bygrave 5c60ba4d7f Update test for WriteJson to just use PathProperties 2017-08-03 23:01:22 +12:00
rob bygrave b9cc584086 #1080 - NOT IN expression using entity beans / reference beans uses "in" 2017-08-03 20:31:18 +12:00
Koen De GrooteandRob Bygrave bf5a8c2fad Changed some boolean checks to use the recommended method of comparing floating point values in Java. (#1075) 2017-07-22 13:00:24 +12:00
rob bygrave 66138e5ea2 #1070 Fix for - Create test for bean cache not being cleared with delete or update queries 2017-07-21 23:31:23 +12:00
Rien Nentjes 75fb89ef76 Create test for bean cache not being cleared with delete or update queries 2017-07-14 11:04:43 +02:00
rob bygrave a2cb468222 No effective change - add test resource ebean.mf to hint to enhancement agent packages to enhance 2017-07-11 21:11:54 +12:00
rob bygrave 166bcd7c45 #1061 - findCount query is not cached in L2 query cache 2017-07-06 21:51:52 +12:00
rob bygrave 95aed6af54 #1060 - findSingleAttributeList doesn't store result in L2 query cache 2017-07-06 21:35:20 +12:00
rob bygrave 7a697c12af No effective change - moved mssql-jdbc down with other test scope dependencies 2017-07-06 21:11:40 +12:00
rob bygrave 23278ce30b #1058 - Refactor - PlatformPrefix isn't actually used in PlatformDddlWriter, removing. 2017-07-06 20:30:20 +12:00
Koen De GrooteandRob Bygrave 36a5610e25 Using Regular expression objects for splitting and replacing in strings. (#1059)
* Where possible, replace .split() and .replace() methods with compiled Regex patterns.
This prevents a regular expression from being compiled multiple times during runtime.

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

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