Compare commits

..
Author SHA1 Message Date
rob bygrave 903ee35d63 [maven-release-plugin] prepare release ebean-11.26.1 2018-11-14 09:33:50 +13:00
rob bygrave 658ee3d39f Bump to 11.26.1-SNAPSHOT 2018-11-14 09:32:25 +13:00
rob bygrave c074b7c457 #1547 - Revert for - FIX for the limit query #1540 2018-11-14 09:29:36 +13:00
rob bygrave 528ad7c7fb [maven-release-plugin] prepare for next development iteration 2018-11-13 23:58:37 +13:00
rob bygrave c3f41f4157 [maven-release-plugin] prepare release ebean-11.25.1 2018-11-13 23:58:22 +13:00
rob bygrave 33d104e6aa Bump version to 11.25.1-SNAPSHOT 2018-11-13 23:52:35 +13:00
rob bygrave 4bd3a0f293 #1546 - Refactor: Swap from entityBean._ebean_intercept() to entityBean._ebean_getIntercept() 2018-11-13 23:50:13 +13:00
rob bygrave 54a4fd75f1 #1539 - FIX: Pairs must not be modified by query
Remove the (|| multiValueSupported) part
2018-11-13 23:44:16 +13:00
Roland PramlandRob Bygrave 105f08cca1 FIX: Pairs must not be modified by query (#1539)
* FIX: Pairs must not be modified by query

* Modification can also occur on inValues.

* fixed tests

* removed obsolete line
2018-11-13 23:41:36 +13:00
rob bygrave 01d9dd3fe8 #1533 - Enhanced bean can not use final fields in Java 9+. 2018-11-13 23:37:45 +13:00
rob bygrave f09b38c58c #1545 - Bump ebean-datasource to 4.2.2 fix for leak with SQLWarning and Postgres JDBC driver 2018-11-13 21:33:44 +13:00
rob bygrave cb6d2e7ec0 Merge branch 'master' of github.com:ebean-orm/ebean 2018-11-13 21:27:13 +13:00
rob bygrave 89c0324e42 #1544 - No effective change - use https://ebean.io for urls 2018-11-13 21:26:56 +13:00
Roland PramlandRob Bygrave 7b4215f4f8 Fix test data (#1541)
* FIX: Put correct dataType for anniversary

* NEW: Add some deterministic ship dates to reference data
2018-11-13 21:09:53 +13:00
Roland PramlandRob Bygrave e4cb5f0443 FIX for the limit query (#1540) 2018-11-13 21:08:53 +13:00
Roland PramlandRob Bygrave ec24374d72 Add some sanity checks when creating sort comparators (#1538) 2018-11-13 20:55:09 +13:00
Roland PramlandRob Bygrave cfba4d2deb Add Java 8 Time API support for BasicTypeConverter (#1537) 2018-11-13 20:53:38 +13:00
rob bygrave 0ab019ca2c Merge branch 'FOCONIS-extrawhere-on-join' 2018-11-13 20:51:55 +13:00
rob bygrave 8c9dae0f88 Merge branch 'extrawhere-on-join' of https://github.com/FOCONIS/ebean into FOCONIS-extrawhere-on-join 2018-11-13 20:51:29 +13:00
Roland PramlandRob Bygrave 88a0c34d37 FIX: use outer join on "is null" check (#1535) 2018-11-13 20:46:18 +13:00
Roland PramlandRob Bygrave ce403e72a7 FIX: improved support for @DbForeignKey(noConstraint = true) (#1527)
* FIX: improved support for DbForeignKey(noConstraint = true)

* FIX: Tests for mariadb and sqlserver

* FIX: SqlServer was still incorrect

* FIX: not ommit property for formula

* Update BeanDescriptor.java (typo fix)

fix typo column (no code change!)
2018-11-13 20:44:45 +13:00
rob bygrave 3c6a52da31 #1531 - Transform extra-ddl.xml files when running in ddl mode (no migration)
Update to ebean-migration to with ScriptTransform as public and reuse here in Ebean
2018-11-13 20:39:15 +13:00
Manuel DanischandRob Bygrave 2806972032 Transform extra-ddl.xml files when running in ddl mode (no migration) (#1531) 2018-11-13 20:20:01 +13:00
rob bygrave acd6ba0a42 Merge branch 'master' of github.com:ebean-orm/ebean 2018-11-13 17:52:09 +13:00
rob bygrave b386c9a5a8 #1543 - ENH: Allow ebean.dbSchema to have multiple schemas 2018-11-13 17:51:57 +13:00
Roland Praml c5536aaeec FIX: append extraWhere to join 2018-11-10 23:42:02 +01:00
Roland Praml 1b2bfc5df8 FIX: use outer join on "is null" check 2018-11-10 23:32:24 +01:00
Roland PramlandRob Bygrave 808004165c Code cleanup (#1534)
* removed unused imports (no code change)

* Add missing @Override / @SupressWarnings (no code change)

* FIX: serialization warnings

* FIX: Access to static member
2018-11-10 20:07:01 +13:00
Roland PramlandRob Bygrave 61eedb4e9a Fixed tests that modify BasicData (#1530) 2018-11-03 16:38:26 +13:00
Roland PramlandRob Bygrave 662ffdd070 FIX: update extra ddl also, if no migration was written (#1529) 2018-11-03 16:26:21 +13:00
Roland PramlandRob Bygrave 13a5511318 FIX: disableLazyLoading was not checked correctly (#1528) 2018-11-03 16:23:27 +13:00
Roland PramlandRob Bygrave 0f9c98964e FIX Typos (DLL -> DDL) (#1526) 2018-11-02 09:21:31 +13:00
rob bygrave b87e6d90f6 [maven-release-plugin] prepare for next development iteration 2018-11-01 17:22:36 +13:00
rob bygrave 166906a814 [maven-release-plugin] prepare release ebean-11.24.1 2018-11-01 17:22:22 +13:00
rob bygrave 1733d2382c Bump agent to 11.24.1 2018-11-01 17:20:41 +13:00
rob bygrave a6f9120905 #1525 - Check for presence of JAXB in classpath in CurrentModel (to support Java 11 without xml) 2018-11-01 13:31:08 +13:00
rob bygrave 32b599512b #1524 - @History on @OneToMany with asOf() query and fetch(many property) gives PersistenceException: Error with property[6] dt[93]data[2018-11-01 14:56:18.015][java.sql.Timestamp] 2018-11-01 00:48:46 +13:00
rob bygrave 34fd24d620 No effective change - add back SQL logging 2018-11-01 00:48:02 +13:00
Rob BygraveandGitHub f928923b5b #1519 - Memory leak when using @Cache(enableQueryCache=true) (#1523)
* #1519 - Memory leak when using @Cache(enableQueryCache=true)

* #1519 - Memory leak when using @Cache(enableQueryCache=true)

Remove unused TransactionEventBeans
2018-10-31 23:53:16 +13:00
rob bygrave 1f928e65a4 #1521 - Add API for using explicit transaction for UpdateQuery - e.g. ebeanServer.update(Customer.class)...update(activeTransaction); 2018-10-30 13:39:54 +13:00
rob bygrave d920d8eb76 #1522 - Refactor - Change io.ebean.Model to not be a annotated with @MappedSuperclass 2018-10-30 13:38:57 +13:00
Michael BenzandRob Bygrave 23d2c64046 JavaDoc Fix closing h3 tag in Transaction. no code change! (#1520) 2018-10-30 12:55:58 +13:00
rob bygrave 19d29cd836 #1514 - Add support for JacksonAnnotations at bean-properties
Fix where Jackson is not present on the classpath
2018-10-26 14:50:17 +13:00
rob bygrave f14c3e8cb4 Merge branch 'master' of github.com:ebean-orm/ebean 2018-10-26 01:34:36 +13:00
rob bygrave b9ca8be95a #1517 - Update for ebean-datasource maven groupId change to io.ebean 2018-10-26 01:34:16 +13:00
rob bygrave d627bb47e2 #1516 - More user friendly exceptions - BeanNotEnhancedException, BeanNotRegisteredException 2018-10-26 01:33:24 +13:00
rob bygrave d395c666fc #1516 - More user friendly exceptions - BeanNotEnhancedException, BeanNotRegisteredException 2018-10-26 01:32:22 +13:00
Roland PramlandRob Bygrave 939397acfc Add support for JacksonAnnotations at bean-properties (#1514) 2018-10-25 22:11:36 +13:00
Roland PramlandRob Bygrave fecb5fa731 There should be only one "Rob" in o_customer (#1515)
* FIX: CsvReader-Test

* Fixes for tests

* reversed changes

* tidy TestCsvReaderWithCallback
2018-10-25 10:35:02 +13:00
rob bygrave bdc9917811 [maven-release-plugin] prepare for next development iteration 2018-10-24 23:51:00 +13:00
rob bygrave afcd6859e0 [maven-release-plugin] prepare release ebean-11.23.1 2018-10-24 23:50:44 +13:00
rob bygrave fef87323b3 Bump to 11.23.1-SNAPSHOT 2018-10-24 23:49:26 +13:00
Rob BygraveandGitHub 6909b20a9e #1510 - Tidy DbMigrationConfig removing the properties that should only be with DbMigration generation (#1513) 2018-10-24 23:31:07 +13:00
rob bygrave c1a479e1f4 #1512 - Invalid SQL with Postgres + order by nulls first/last + where() expression on ManyToMany 2018-10-24 23:26:19 +13:00
rob bygrave e06967ff4a #1512 - Invalid SQL with Postgres + order by nulls first/last + where() expression on ManyToMany 2018-10-24 23:25:48 +13:00
rob bygrave 156469ae0f Update tests for running MySql 2018-10-24 22:59:48 +13:00
rob bygrave 1d1d479dda Fix concat_expectString test with explicit cast to string 2018-10-24 22:17:44 +13:00
rob bygrave d185ea5962 Updated db migration test for mysql 2018-10-24 22:12:42 +13:00
rob bygrave 2dbb5ea80d #1511 - Add SAP HANA support - format only changes 2018-10-24 21:49:48 +13:00
Jonathan BreglerandRob Bygrave 158ee9a081 Add SAP HANA support (#1511) 2018-10-24 21:38:17 +13:00
rob bygrave b1410cc660 [maven-release-plugin] prepare for next development iteration 2018-10-19 20:07:32 +13:00
rob bygrave 4c2e9ccacd [maven-release-plugin] prepare release ebean-11.22.10 2018-10-19 20:07:16 +13:00
rob bygrave 29aa395cd4 #1509 - DBMigration add ... List<String> getPendingDrops(); 2018-10-19 16:24:06 +13:00
rob bygrave 8160d1f01c #1508 - Invalid SQL used to fetch identity value for @Draftable with database platform that uses Identity but not getGeneratedKeys 2018-10-19 15:56:39 +13:00
rob bygrave 6fd76ff697 [maven-release-plugin] prepare for next development iteration 2018-10-15 22:35:28 +13:00
rob bygrave 249cc5df49 [maven-release-plugin] prepare release ebean-11.22.9 2018-10-15 22:35:12 +13:00
rob bygrave 2646e8c27f #1507 - SqlUpdate.execute PersistenceException is not translated into more specific exception (like DuplicateKeyException)
Support for sqlUpdate.executeBatch()
2018-10-15 22:30:42 +13:00
rob bygrave 3eaf47fd70 No effective change - add additional tests 2018-10-15 22:25:38 +13:00
rob bygrave 9d64352771 No effective change - add additional tests 2018-10-15 22:13:18 +13:00
rob bygrave aa7325b249 #1507 - SqlUpdate.execute PersistenceException is not translated into more specific exception (like DuplicateKeyException) 2018-10-15 22:12:39 +13:00
rob bygrave 2839434773 #1506 - @ElementCollection DDL generates 2 indexes on the same column 2018-10-13 00:23:44 +13:00
rob bygrave de9823b69b #1505 - Unidirectional @OneToOne orphanRemoval=true ... does not delete orphan that is replaced 2018-10-12 22:30:06 +13:00
rob bygrave 15cab24848 #1504 - ENH: Add SqlUpdate executeNow() method ... that executes now regardless of transaction batch mode
Add test for UpdateQuery (which executes immediately regardless)
2018-10-12 20:33:59 +13:00
rob bygrave b87e360ac7 #1504 - ENH: Add SqlUpdate executeNow() method ... that executes now regardless of transaction batch mode 2018-10-12 18:49:20 +13:00
rob bygrave 82e68d7756 [maven-release-plugin] prepare for next development iteration 2018-10-05 13:04:31 +13:00
rob bygrave 0371043914 [maven-release-plugin] prepare release ebean-11.22.8 2018-10-05 13:04:15 +13:00
rob bygrave 54b5684628 #1502 - findNative build query to findPagedList, the query count is incorrect. 2018-10-05 12:22:25 +13:00
rob bygrave da95ec0c05 #1501 - Change logging for io.ebeaninternal.dbmigration.DefaultDbMigration to "io.ebean.GenerateMigration" 2018-10-05 11:26:42 +13:00
rob bygrave b5eec93995 #1500 - Improve Postgres 10 partitioning support, easier to create multiple indexes on each partition 2018-10-05 10:41:40 +13:00
rob bygrave fc26765a7e #1499 - findNative with findIds (or findSingleAttributeList with id property) throws ArrayIndexOutOfBoundsException 2018-10-04 22:51:51 +13:00
rob bygrave 9a6d339449 [maven-release-plugin] prepare for next development iteration 2018-10-04 22:10:24 +13:00
rob bygrave 5b09443cdf [maven-release-plugin] prepare release ebean-11.22.7 2018-10-04 22:10:06 +13:00
rob bygrave 31093babe3 #1498 - findIds() with @SoftDelete bean returns all Ids (when soft deleted ones should not return)
And fix delete permanent that cascades
2018-10-04 20:49:30 +13:00
rob bygrave f8b9ca2034 #1498 - findIds() with @SoftDelete bean returns all Ids (when soft deleted ones should not return) 2018-10-04 20:26:00 +13:00
rob bygrave c27749ae2d #1497 - SQL Error with @History + @SoftDelete + @OneToOne fetch=FetchType.EAGER 2018-10-04 19:59:36 +13:00
rob bygrave 2c04430185 No effective change - add testSoftDelete_includeSoftDeletes_findOne 2018-10-04 17:14:53 +13:00
rob bygrave dcde47daf6 Merge branch 'master' of github.com:ebean-orm/ebean 2018-09-28 16:34:08 +12:00
rob bygrave 57056d7abd #1496 - Query label lost for findId() 2018-09-28 16:33:54 +12:00
Rob Bygrave 3409f264ec [maven-release-plugin] prepare for next development iteration 2018-09-26 23:38:57 +12:00
Rob Bygrave 6a61614430 [maven-release-plugin] prepare release ebean-11.22.6 2018-09-26 23:38:42 +12:00
Rob Bygrave 9734ae5787 #1490 - FIX: withQuote_when_match for different platforms 2018-09-26 21:33:27 +12:00
Roland PramlandRob Bygrave b817d5ee4d FIX: withQuote_when:_match for different platforms (#1490) 2018-09-26 21:26:29 +12:00
Rob Bygrave 0036473971 #1489 - removed the concat-operator "||" and replaced with "concat()" 2018-09-26 21:23:14 +12:00
Roland PramlandRob Bygrave a020bb15a1 removed the concat-operator "||" and replaced with "concat()" (#1489)
* removed the concat-operator "||" and replaced with "concat()"

* using || in postgres

* FIX: Assert-statement
2018-09-26 21:19:28 +12:00
Rob Bygrave efa86043bf #1492 - Update for NEW: collation support for orderBy 2018-09-26 21:15:41 +12:00
Roland PramlandRob Bygrave eb1ea5ddd2 NEW: collation support for orderBy (#1492) 2018-09-26 20:57:33 +12:00
Roland PramlandRob Bygrave a83a4fa1c4 FIX: check platform on multiple annotations like '@Formula(platform=...)' (#1495) 2018-09-26 20:55:39 +12:00
Roland PramlandRob Bygrave fe71c7cc4e FIX: tableName detection for native statements on SQLServer (#1494) 2018-09-26 20:54:22 +12:00
Roland PramlandRob Bygrave 5522d56e8d ignored tests for sqlserver, because there is no nested transaction support, yet (#1493) 2018-09-26 20:48:49 +12:00
Roland PramlandRob Bygrave 7e995efb62 Add methods th the plugin API to access type and inheritance information (#1491) 2018-09-26 20:48:19 +12:00
Roland PramlandRob Bygrave ef46bc86cc Original Value and bean change was not computed correctly in changelog (#1488)
* Bug in Changelog: It takes the first non null value as oldValue and does not detect if oldValue == newValue

* CHANGE: EntityBeanIntercept uses bitflags instead of separate boolean arrays - new flag 'origValueSet' introduced - getDirtyValues checks, if values are really different.
2018-09-26 20:40:56 +12:00
rob bygrave 0e75c50be5 #1487 - @OneToOne Postgres with different Id types - error PSQLException: ERROR: operator does not exist: uuid = character varying
Fix
2018-09-24 22:30:45 +12:00
rob bygrave cd1cdb027f #1487 - @OneToOne Postgres with different Id types - error PSQLException: ERROR: operator does not exist: uuid = character varying
Test
2018-09-24 22:30:28 +12:00
Roland PramlandRob Bygrave 66703a3a29 FIX: recursive BatchedPstmtHolder.flush, when BeanPersistController or ChangeLogFilter hit the database (#1486) 2018-09-21 15:38:21 +12:00
Roland PramlandRob Bygrave 56e65d7463 Fix issue in UUID-v1 generator on AWS Beanstalk (#1485) 2018-09-21 15:35:53 +12:00
Roland PramlandRob Bygrave eb7277aa13 Wrong behaviour in BeanMap.put/putAll (#1484) 2018-09-21 15:35:01 +12:00
Rob Bygrave 42f9e74d91 #1483 - Invalid SQL with allQuotedIdentifiers true - where t0.[t0.[COND]] in (?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) 2018-09-20 23:12:26 +12:00
Rob Bygrave be24e11b91 [maven-release-plugin] prepare for next development iteration 2018-09-20 21:35:24 +12:00
Rob Bygrave 42d3e9eb91 [maven-release-plugin] prepare release ebean-11.22.5 2018-09-20 21:35:08 +12:00
Rob Bygrave bb164a0763 OneToOne inserts with idType=SEQUENCE will fail #1480 2018-09-20 21:32:48 +12:00
Rob Bygrave 3a50c264b1 No effective change - add test with softDelete on bean with history 2018-09-20 21:10:41 +12:00
Rob Bygrave 0e7dc4209c #1482 - Improve error message for incorrect mapping - @JoinColumn without @ManyToOne etc 2018-09-20 21:09:01 +12:00
Roland PramlandRob Bygrave 72845acbad FIX: JunctionExpression.notExists (#1481) 2018-09-20 20:24:33 +12:00
Roland PramlandRob Bygrave 29d88d7de8 FIX: NPE in StalarTypeLocalTime and StalarTypeMonthDay (#1479) 2018-09-20 20:23:46 +12:00
rob bygrave c81d329100 [maven-release-plugin] prepare for next development iteration 2018-08-31 21:56:34 +12:00
rob bygrave 7c1c144af5 [maven-release-plugin] prepare release ebean-11.22.4 2018-08-31 21:56:19 +12:00
rob bygrave 7809858634 #1478 - MySql DDL, invalid comment included resulting in PersistenceException: Failed to run script 2018-08-31 21:54:48 +12:00
rob bygrave bd3cb3c547 [maven-release-plugin] prepare for next development iteration 2018-08-27 21:44:54 +12:00
rob bygrave db25cb7bfb [maven-release-plugin] prepare release ebean-11.22.3 2018-08-27 21:44:38 +12:00
rob bygrave 55a9ba7170 #1475 - @OneToOne child with @SoftDelete that is deleted is still loaded by parent 2018-08-27 21:35:00 +12:00
rob bygrave 2df2224286 No effective change - add a new test for OneToOne with fetchType LAZY 2018-08-27 20:00:44 +12:00
rob bygrave e1e8092144 [maven-release-plugin] prepare for next development iteration 2018-08-21 21:15:23 +12:00
rob bygrave 4d4886d808 [maven-release-plugin] prepare release ebean-11.22.2 2018-08-21 21:15:07 +12:00
rob bygrave d11bb2c4e5 No effective change - add a new test for OneToMany orphanRemoval=true 2018-08-21 21:08:17 +12:00
rob bygrave 663b1bb4aa #1474 - IllegalStateException: Table [some_table] does not exists in model? ... when applying drop table migration and table is no longer in the model 2018-08-21 20:45:31 +12:00
SnōwballandRob Bygrave f3d26e32ec Possible fix error with multiple uses of enum (#1473)
When an enum is exists in the type cache and isn't String, @DbArray
fails. This includes a test that should fail everywhere (order of
reading fields isn't guaranteed afaik and the column needs to be read
before the @DbArray for the test to fail).
2018-08-21 20:26:08 +12:00
rob bygrave 5f83584315 [maven-release-plugin] prepare for next development iteration 2018-08-16 00:05:31 +12:00
rob bygrave d28e3ee456 [maven-release-plugin] prepare release ebean-11.22.1 2018-08-16 00:05:16 +12:00
rob bygrave e1b7161b22 Bump version to 11.22.1-SNAPSHOT 2018-08-16 00:03:15 +12:00
rob bygrave f63a3cb926 Bump ebean-migration to 11.9.1 2018-08-16 00:02:10 +12:00
rob bygrave 29c5a17202 #1472 - Cascade insert with managed id ... results in update rather than insert statement 2018-08-15 23:12:06 +12:00
rob bygrave 340b100a1a No effective change - Add test for OneToOne orphanRemoval=true and String Ids (non generated ids) 2018-08-14 22:09:01 +12:00
rob bygrave 80975acfbf #1471 - Change stateless update on bean with no @Version property that updates 0 rows to throw EntityNotFoundException 2018-08-14 14:23:28 +12:00
rob bygrave 95dab97b1b No effective change - adjust test adding @Draft property to @Draftable Document 2018-08-14 14:16:08 +12:00
rob bygrave ce10dd79f9 [maven-release-plugin] prepare for next development iteration 2018-08-13 22:06:41 +12:00
rob bygrave 3f12739e71 [maven-release-plugin] prepare release ebean-11.21.1 2018-08-13 22:06:26 +12:00
rob bygrave cd1ae20015 Bump to 11.21.1-SNAPSHOT 2018-08-13 22:00:15 +12:00
rob bygrave 6d1e3a08e1 #1470 - Change OnQueryOnly to default to OnQueryOnly.COMMIT 2018-08-13 21:59:33 +12:00
rob bygrave 94e6fe90e5 No effective change - white space format 2018-08-13 21:54:19 +12:00
SnōwballandRob Bygrave 9bbe99657a Let enum arrays be saved as integers or strings (#1467)
* Let enums be saved as integers or strings as needed

* Add tests for int and varchar @DbArray enums
2018-08-13 21:26:14 +12:00
rob bygrave 0faf9f54ea [maven-release-plugin] prepare for next development iteration 2018-08-10 15:54:42 +12:00
320 changed files with 8089 additions and 1069 deletions
+15
View File
@@ -151,3 +151,18 @@ In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
TODO: not yet tested
'hana' platform
--------------------
Requires an installed SAP HANA server - e.g. https://store.docker.com/images/sap-hana-express-edition
- Create a user `EBEAN_TEST` with password `Eb3an_test` in the tenant database `HXE`
- Adjust the connection string and/or username in `ebean.properties`
run: `mvn clean test -Ddatasource.default=hana`
Current status: PASS
Tests run: 2486, Failures: 0, Errors: 0, Skipped: 84
+16 -9
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.20.2</version>
<version>11.26.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -22,7 +22,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.20.2</tag>
<tag>ebean-11.26.1</tag>
</scm>
<profiles>
@@ -117,13 +117,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>4.2</version>
<version>4.3</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>3.2.2</version>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
@@ -135,7 +135,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.8.2</version>
<version>11.10.1</version>
</dependency>
<dependency>
@@ -229,7 +229,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.11.1</version>
<version>11.25.1</version>
<scope>test</scope>
</dependency>
@@ -271,6 +271,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.3.48</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje.composite</groupId>
<artifactId>avaje-composite-testing</artifactId>
@@ -302,7 +309,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.11.1</version>
<version>11.25.1</version>
<executions>
<execution>
<id>test</id>
+11
View File
@@ -2,10 +2,12 @@ package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.BeanNotEnhancedException;
import io.ebean.config.ServerConfig;
import io.ebean.plugin.Property;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import io.ebean.datasource.DataSourceConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -160,6 +162,15 @@ public final class Ebean {
defaultServer = getWithCreate(defaultName.trim());
}
}
} catch (BeanNotEnhancedException e) {
throw e;
} catch (DataSourceConfigurationException e) {
String msg = "Configuration error creating DataSource for the default EbeanServer." +
" This typically means a missing application-test.yml or missing ebean-test-config dependency." +
" See https://ebean.io/docs/trouble-shooting#datasource";
throw new DataSourceConfigurationException(msg, e);
} catch (Throwable e) {
logger.error("Error trying to create the default EbeanServer", e);
throw new RuntimeException(e);
-4
View File
@@ -11,17 +11,13 @@ import io.ebean.text.json.JsonContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Provides the API for fetching and saving beans to a particular DataSource.
@@ -166,6 +166,18 @@ public interface ExpressionList<T> {
*/
int delete();
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of rows that were deleted.
*/
int delete(Transaction transaction);
/**
* Execute as a update query.
*
@@ -174,6 +186,14 @@ public interface ExpressionList<T> {
*/
int update();
/**
* Execute as a update query with the given transaction.
*
* @return the number of rows that were updated.
* @see UpdateQuery
*/
int update(Transaction transaction);
/**
* Execute the query iterating over the results.
*
@@ -78,6 +78,7 @@ public class MergeOptionsBuilder {
this.deletePermanent = deletePermanent;
}
@Override
public Set<String> paths() {
return paths;
}
-2
View File
@@ -2,7 +2,6 @@ package io.ebean;
import io.ebean.bean.EntityBean;
import javax.persistence.MappedSuperclass;
/**
* A MappedSuperclass base class that provides convenience methods for inserting, updating and
@@ -74,7 +73,6 @@ import javax.persistence.MappedSuperclass;
*
* }</pre>
*/
@MappedSuperclass
public abstract class Model {
/**
+59 -7
View File
@@ -1,9 +1,12 @@
package io.ebean;
import io.ebean.util.StringHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Represents an Order By for a Query.
@@ -74,6 +77,15 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Add a property with ascending order to this OrderBy.
*/
public Query<T> asc(String propertyName, String collation) {
list.add(new Property(propertyName, true, collation));
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
@@ -83,6 +95,16 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
public Query<T> desc(String propertyName, String collation) {
list.add(new Property(propertyName, false, collation));
return query;
}
/**
* Return true if the property is known to be contained in the order by clause.
*/
@@ -231,6 +253,8 @@ public final class OrderBy<T> implements Serializable {
private boolean ascending;
private String collation;
private String nulls;
private String highLow;
@@ -247,17 +271,32 @@ public final class OrderBy<T> implements Serializable {
this.highLow = highLow;
}
public Property(String property, boolean ascending, String collation) {
this.property = property;
this.ascending = ascending;
this.collation = collation;
}
public Property(String property, boolean ascending, String collation, String nulls, String highLow) {
this.property = property;
this.ascending = ascending;
this.collation = collation;
this.nulls = nulls;
this.highLow = highLow;
}
/**
* Return a copy of this Property with the path trimmed.
*/
public Property copyWithTrim(String path) {
return new Property(property.substring(path.length() + 1), ascending, nulls, highLow);
return new Property(property.substring(path.length() + 1), ascending, collation, nulls, highLow);
}
@Override
public int hashCode() {
int hc = property.hashCode();
hc = hc * 92821 + (ascending ? 0 : 1);
hc = hc * 92821 + (collation == null ? 0 : collation.hashCode());
hc = hc * 92821 + (nulls == null ? 0 : nulls.hashCode());
hc = hc * 92821 + (highLow == null ? 0 : highLow.hashCode());
return hc;
@@ -274,8 +313,9 @@ public final class OrderBy<T> implements Serializable {
Property e = (Property) obj;
if (ascending != e.ascending) return false;
if (!property.equals(e.property)) return false;
if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false;
return highLow != null ? highLow.equals(e.highLow) : e.highLow == null;
if (!Objects.equals(collation, e.collation)) return false;
if (!Objects.equals(nulls, e.nulls)) return false;
return Objects.equals(highLow, e.highLow);
}
@Override
@@ -284,7 +324,7 @@ public final class OrderBy<T> implements Serializable {
}
public String toStringFormat() {
if (nulls == null) {
if (nulls == null && collation == null) {
if (ascending) {
return property;
} else {
@@ -292,11 +332,23 @@ public final class OrderBy<T> implements Serializable {
}
} else {
StringBuilder sb = new StringBuilder();
sb.append(property);
if (collation != null) {
if (collation.contains("${}")) {
// this is a complex collation, e.g. DB2 - we must replace the property
sb.append(StringHelper.replaceString(collation, "${}", property));
} else {
sb.append(property);
sb.append(" collate ").append(collation);
}
} else {
sb.append(property);
}
if (!ascending) {
sb.append(" ").append("desc");
}
sb.append(" ").append(nulls).append(" ").append(highLow);
if (nulls != null) {
sb.append(" ").append(nulls).append(" ").append(highLow);
}
return sb.toString();
}
}
@@ -319,7 +371,7 @@ public final class OrderBy<T> implements Serializable {
* Return a copy of this property.
*/
public Property copy() {
return new Property(property, ascending, nulls, highLow);
return new Property(property, ascending, collation, nulls, highLow);
}
/**
+2 -1
View File
@@ -1,6 +1,7 @@
package io.ebean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -125,7 +126,7 @@ public class Pairs {
* Return all the value pairs.
*/
public List<Entry> getEntries() {
return entries;
return Collections.unmodifiableList(entries);
}
/**
+20
View File
@@ -924,11 +924,31 @@ public interface Query<T> {
*/
int delete();
/**
* Execute as a delete query returning the number of rows deleted using the given transaction.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
*/
int delete(Transaction transaction);
/**
* Execute the UpdateQuery returning the number of rows updated.
*
* @return the number of beans/rows updated.
*/
int update();
/**
* Execute the UpdateQuery returning the number of rows updated using the given transaction.
*
* @return the number of beans/rows updated.
*/
int update(Transaction transaction);
/**
* Return the count of entities this query should return.
* <p>
+10
View File
@@ -98,6 +98,11 @@ public interface SqlUpdate {
/**
* Execute the update returning the number of rows modified.
* <p>
* Note that if the transaction has batch mode on then this update will use JDBC batch and may not execute until
* later - at commit time or a transaction flush. In this case this method returns -1 indicating that the
* update has been batched for later execution.
* </p>
* <p>
* After you have executed the SqlUpdate you can bind new variables using
* {@link #setParameter(String, Object)} etc and then execute the SqlUpdate
* again.
@@ -112,6 +117,11 @@ public interface SqlUpdate {
*/
int execute();
/**
* Execute the statement now regardless of the JDBC batch mode of the transaction.
*/
int executeNow();
/**
* Execute when addBatch() has been used to batch multiple bind executions.
*
+3 -3
View File
@@ -84,7 +84,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* This is similar to commit() but leaves the transaction "Active".
* </p>
* <h3>Functions/h3>
* <h3>Functions</h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
@@ -100,7 +100,7 @@ public interface Transaction extends AutoCloseable {
* This performs commit and completes the transaction closing underlying resources and
* marking the transaction as "In active".
* </p>
* <h3>Functions/h3>
* <h3>Functions</h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
@@ -117,7 +117,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* This performs rollback, closes underlying resources and marks the transaction as "In active".
* </p>
* <h3>Functions/h3>
* <h3>Functions</h3>
* <ul>
* <li>Call rollback on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
@@ -71,21 +71,30 @@ public final class EntityBeanIntercept implements Serializable {
/**
* Used when a bean is partially filled.
*/
private final boolean[] loadedProps;
private boolean fullyLoadedBean;
private static final byte FLAG_LOADED_PROP = 1;
/**
* Set of changed properties.
*/
private boolean[] changedProps;
private static final byte FLAG_CHANGED_PROP = 2;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private boolean[] embeddedDirty;
private static final byte FLAG_EMBEDDED_DIRTY = 4;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
private final byte[] flags;
private boolean fullyLoadedBean;
private Object[] origValues;
@@ -102,7 +111,7 @@ public final class EntityBeanIntercept implements Serializable {
*/
public EntityBeanIntercept(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
this.loadedProps = new boolean[owner._ebean_getPropertyNames().length];
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
/**
@@ -213,8 +222,8 @@ public final class EntityBeanIntercept implements Serializable {
* Check each property to see if the bean is partially loaded.
*/
public boolean isPartial() {
for (boolean loadedProp : loadedProps) {
if (!loadedProp) {
for (byte flag : flags) {
if ((flag & FLAG_LOADED_PROP) == 0) {
return true;
}
}
@@ -259,10 +268,10 @@ public final class EntityBeanIntercept implements Serializable {
* Return true if only the Id property has been loaded.
*/
public boolean hasIdOnly(int idIndex) {
for (int i = 0; i < loadedProps.length; i++) {
for (int i = 0; i < flags.length; i++) {
if (i == idIndex) {
if (!loadedProps[i]) return false;
} else if (loadedProps[i]) {
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
return false;
}
}
@@ -284,9 +293,9 @@ public final class EntityBeanIntercept implements Serializable {
if (idPos > -1) {
// For cases where properties are set on constructor
// set every non Id property to unloaded (for lazy loading)
for (int i = 0; i < loadedProps.length; i++) {
for (int i = 0; i < flags.length; i++) {
if (i != idPos) {
loadedProps[i] = false;
flags[i] &= ~FLAG_LOADED_PROP;
}
}
}
@@ -352,7 +361,9 @@ public final class EntityBeanIntercept implements Serializable {
this.owner._ebean_setEmbeddedLoaded();
this.lazyLoadProperty = -1;
this.origValues = null;
this.changedProps = null;
for (int i = 0; i < flags.length; i++) {
flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
}
this.dirty = false;
}
@@ -475,7 +486,11 @@ public final class EntityBeanIntercept implements Serializable {
if (position == -1) {
throw new IllegalArgumentException("Property " + propertyName + " not found");
}
loadedProps[position] = loaded;
if (loaded) {
flags[position] |= FLAG_LOADED_PROP;
} else {
flags[position] &= ~FLAG_LOADED_PROP;
}
}
/**
@@ -483,22 +498,22 @@ public final class EntityBeanIntercept implements Serializable {
* constructor.
*/
public void setPropertyUnloaded(int propertyIndex) {
loadedProps[propertyIndex] = false;
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
}
/**
* Set the property to be loaded.
*/
public void setLoadedProperty(int propertyIndex) {
loadedProps[propertyIndex] = true;
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
/**
* Set all properties to be loaded (post insert).
*/
public void setLoadedPropertyAll() {
for (int i = 0; i < loadedProps.length; i++) {
loadedProps[i] = true;
for (int i = 0; i < flags.length; i++) {
flags[i] |= FLAG_LOADED_PROP;
}
}
@@ -506,14 +521,14 @@ public final class EntityBeanIntercept implements Serializable {
* Return true if the property is loaded.
*/
public boolean isLoadedProperty(int propertyIndex) {
return loadedProps[propertyIndex];
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
}
/**
* Return true if the property is considered changed.
*/
public boolean isChangedProperty(int propertyIndex) {
return (changedProps != null && changedProps[propertyIndex]);
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
}
/**
@@ -521,8 +536,7 @@ public final class EntityBeanIntercept implements Serializable {
* embedded properties is dirty.
*/
public boolean isDirtyProperty(int propertyIndex) {
return (changedProps != null && changedProps[propertyIndex]
|| embeddedDirty != null && embeddedDirty[propertyIndex]);
return (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
}
/**
@@ -534,27 +548,22 @@ public final class EntityBeanIntercept implements Serializable {
}
public void setChangedProperty(int propertyIndex) {
if (changedProps == null) {
changedProps = new boolean[owner._ebean_getPropertyNames().length];
}
changedProps[propertyIndex] = true;
flags[propertyIndex] |= FLAG_CHANGED_PROP;
}
/**
* Set that an embedded bean has had one of its properties changed.
*/
private void setEmbeddedPropertyDirty(int propertyIndex) {
if (embeddedDirty == null) {
embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
}
embeddedDirty[propertyIndex] = true;
flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
}
private void setOriginalValue(int propertyIndex, Object value) {
if (origValues == null) {
origValues = new Object[owner._ebean_getPropertyNames().length];
}
if (origValues[propertyIndex] == null) {
if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
origValues[propertyIndex] = value;
}
}
@@ -574,13 +583,9 @@ public final class EntityBeanIntercept implements Serializable {
*/
public void setNewBeanForUpdate() {
if (changedProps == null) {
changedProps = new boolean[owner._ebean_getPropertyNames().length];
}
for (int i = 0; i < loadedProps.length; i++) {
if (loadedProps[i]) {
changedProps[i] = true;
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
flags[i] |= FLAG_CHANGED_PROP;
}
}
setDirty(true);
@@ -594,8 +599,8 @@ public final class EntityBeanIntercept implements Serializable {
return null;
}
Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < loadedProps.length; i++) {
if (loadedProps[i]) {
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
props.add(getProperty(i));
}
}
@@ -609,12 +614,8 @@ public final class EntityBeanIntercept implements Serializable {
int len = getPropertyLength();
boolean[] dirties = new boolean[len];
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
dirties[i] = true;
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
dirties[i] = true;
}
// this, or an embedded property has been changed - recurse
dirties[i] = (flags[i] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
}
return dirties;
}
@@ -634,11 +635,11 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyNames(Set<String> props, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
props.add(propName);
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i) + ".");
@@ -654,12 +655,12 @@ public final class EntityBeanIntercept implements Serializable {
String[] names = owner._ebean_getPropertyNames();
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
if (propertyNames.contains(names[i])) {
return true;
}
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
if (propertyNames.contains(names[i])) {
return true;
}
@@ -683,15 +684,16 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(Map<String, ValuePair> dirtyValues, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
if (!areEqual(oldVal, newVal)) {
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
}
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
@@ -705,13 +707,15 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
visitor.visit(i, newVal, oldVal);
if (!areEqual(oldVal, newVal)) {
visitor.visit(i, newVal, oldVal);
}
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
visitor.visitPush(i);
@@ -739,9 +743,9 @@ public final class EntityBeanIntercept implements Serializable {
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
sb.append(i).append(',');
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
sb.append(i).append('[');
@@ -765,15 +769,12 @@ public final class EntityBeanIntercept implements Serializable {
return sb;
}
/**
* Return the set of property names for changed properties.
*/
public boolean[] getChanged() {
return changedProps;
}
public boolean[] getLoaded() {
return loadedProps;
boolean[] ret= new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
return ret;
}
/**
@@ -821,14 +822,14 @@ public final class EntityBeanIntercept implements Serializable {
*/
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
if (loadedProps == null || loadedProps[loadProperty]) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
}
if (lazyLoadProperty == -1) {
@@ -887,7 +888,7 @@ public final class EntityBeanIntercept implements Serializable {
* Called when a BeanCollection is initialised automatically.
*/
public void initialisedMany(int propertyIndex) {
loadedProps[propertyIndex] = true;
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
private void preGetterCallback(int propertyIndex) {
+6 -3
View File
@@ -273,14 +273,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
checkReadOnly();
init();
if (modifyListening) {
Object oldBean = map.put(key, value);
E oldBean = map.put(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.put(key, value);
}
return map.put(key, value);
}
@Override
@@ -295,8 +297,9 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
modifyRemoval(oldBean);
}
}
} else {
map.putAll(puts);
}
map.putAll(puts);
}
@Override
@@ -0,0 +1,15 @@
package io.ebean.config;
/**
* Throw when an processing an entity bean that is not bytecode enhanced.
*
* Refer: https://ebean.io/docs/trouble-shooting#not-enhanced
*/
public class BeanNotEnhancedException extends IllegalStateException {
private static final long serialVersionUID = 3008101919425876799L;
public BeanNotEnhancedException(String msg) {
super(msg);
}
}
@@ -0,0 +1,15 @@
package io.ebean.config;
/**
* Throw when an processing thinks a bean is not registered.
*
* Refer: https://ebean.io/docs/trouble-shooting#not-registered
*/
public class BeanNotRegisteredException extends IllegalStateException {
private static final long serialVersionUID = -1411298126011136552L;
public BeanNotRegisteredException(String msg) {
super(msg);
}
}
@@ -28,34 +28,6 @@ public class DbMigrationConfig {
*/
protected Platform platform;
/**
* Set to true if the DB migration should be generated on server start.
*/
protected boolean generate;
/**
* The migration version name (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
protected String version;
/**
* Description text that can be appended to the version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
*/
protected String name;
/**
* Resource path for the migration xml and sql.
*/
@@ -75,13 +47,6 @@ public class DbMigrationConfig {
protected String modelSuffix = ".model.xml";
protected boolean includeGeneratedFileComment;
/**
* The version of a pending drop that should be generated as the next migration.
*/
protected String generatePendingDrop;
/**
* For running migration the DB table that holds migration execution status.
*/
@@ -228,52 +193,6 @@ public class DbMigrationConfig {
this.applyPrefix = applyPrefix;
}
/**
* Return true if the generated file comment should be included.
*/
public boolean isIncludeGeneratedFileComment() {
return includeGeneratedFileComment;
}
/**
* Set to true if the generated file comment should be included.
*/
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
/**
* Return the migration version (or "next") to generate pending drops for.
*/
public String getGeneratePendingDrop() {
return generatePendingDrop;
}
/**
* Set the migration version (or "next") to generate pending drops for.
*/
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
/**
* Set the migration version.
* <p>
* Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Set the migration name.
* <p>
* Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
*/
public void setName(String name) {
this.name = name;
}
/**
* Return the table name that holds the migration run details
* (used by DB Migration runner only).
@@ -504,14 +423,8 @@ public class DbMigrationConfig {
applyPrefix = properties.get("migration.applyPrefix", applyPrefix);
applySuffix = properties.get("migration.applySuffix", applySuffix);
modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop);
platform = properties.getEnum(Platform.class, "migration.platform", platform);
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
name = properties.get("migration.name", name);
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
@@ -532,44 +445,6 @@ public class DbMigrationConfig {
ddlHeader = properties.get("ddl.header", ddlHeader);
}
/**
* Return the migration version (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
public String getVersion() {
String envVersion = readEnvironment("ddl.migration.version");
if (!isEmpty(envVersion)) {
return envVersion.trim();
}
return version;
}
/**
* Return the migration name which is short description text that can be appended to
* the migration version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* </p>
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
* </p>
*/
public String getName() {
String envName = readEnvironment("ddl.migration.name");
if (!isEmpty(envName)) {
return envName.trim();
}
return name;
}
/**
* Return the system or environment property.
*/
@@ -30,7 +30,7 @@ import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.time.Clock;
@@ -1965,6 +1965,15 @@ public class ServerConfig {
* Return the UUID state file.
*/
public String getUuidStateFile() {
if (uuidStateFile == null || uuidStateFile.isEmpty()) {
// by default, add servername...
uuidStateFile = name + "-uuid.state";
// and store it in the user's home directory
String homeDir = System.getProperty("user.home");
if (homeDir != null && homeDir.isEmpty()) {
uuidStateFile = homeDir + "/.ebean/" + uuidStateFile;
}
}
return uuidStateFile;
}
@@ -52,7 +52,7 @@ public class DatabasePlatform {
/**
* The behaviour used when ending a read only transaction at read committed isolation level.
*/
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
protected OnQueryOnly onQueryOnly = OnQueryOnly.COMMIT;
/**
* The open quote used by quoted identifiers.
@@ -64,8 +64,6 @@ public class DatabasePlatform {
*/
protected String closeQuote = "\"";
protected String concatOperator = "||";
/**
* When set to true all db column names and table names use quoted identifiers.
*/
@@ -178,9 +176,17 @@ public class DatabasePlatform {
* findIterate() and findVisit().
*/
protected boolean forwardOnlyHintOnFindIterate;
/**
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
*
* This is {@code false} for HANA
*/
protected boolean supportsResultSetConcurrencyModeUpdatable = true;
/**
* By default we use JDBC batch when cascading (except for SQL Server).
* By default we use JDBC batch when cascading (except for SQL Server and HANA).
*/
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
@@ -455,13 +461,6 @@ public class DatabasePlatform {
return openQuote;
}
/**
* Return the DB concat operator.
*/
public String getConcatOperator() {
return concatOperator;
}
/**
* Return the JDBC type used to store booleans.
*/
@@ -525,6 +524,24 @@ public class DatabasePlatform {
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
}
/**
* Return true if the ResultSet CONCUR_UPDATABLE Hint should be used on
* createNativeSqlTree() PreparedStatements.
* <p>
* This specifically is required for Hana which doesn't support CONCUR_UPDATABLE
* </p>
*/
public boolean isSupportsResultSetConcurrencyModeUpdatable() {
return supportsResultSetConcurrencyModeUpdatable;
}
/**
* Set to true if the ResultSet CONCUR_UPDATABLE Hint should be used by default on createNativeSqlTree() PreparedStatements.
*/
public void setSupportsResultSetConcurrencyModeUpdatable(boolean supportsResultSetConcurrencyModeUpdatable) {
this.supportsResultSetConcurrencyModeUpdatable = supportsResultSetConcurrencyModeUpdatable;
}
/**
* Normally not needed - overridden in CockroachPlatform.
@@ -8,7 +8,9 @@ import java.util.regex.Pattern;
*/
public class DbIdentity {
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
private static final String TABLE_PLACEHOLDER = "{table}";
private static final Pattern TABLE_REPLACE = Pattern.compile(TABLE_PLACEHOLDER, Pattern.LITERAL);
/**
* Set if this DB supports sequences. Note some DB's support both Sequences
@@ -53,7 +55,9 @@ public class DbIdentity {
if (selectLastInsertedIdTemplate == null) {
return null;
}
if (!selectLastInsertedIdTemplate.contains(TABLE_PLACEHOLDER)) {
return selectLastInsertedIdTemplate;
}
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
}
@@ -0,0 +1,24 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.config.dbplatform.BasicSqlLimiter;
public class HanaBasicSqlLimiter implements BasicSqlLimiter {
@Override
public String limit(String dbSql, int firstRow, int maxRows) {
StringBuilder sb = new StringBuilder(50 + dbSql.length());
sb.append(dbSql);
if (maxRows > 0) {
sb.append(" ").append("limit");
sb.append(" ").append(maxRows);
if (firstRow > 0) {
sb.append(" ").append("offset").append(" ");
sb.append(firstRow);
}
}
return sb.toString();
}
}
@@ -0,0 +1,27 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.config.dbplatform.DbStandardHistorySupport;
public class HanaHistorySupport extends DbStandardHistorySupport {
@Override
public String getAsOfViewSuffix(String asOfViewSuffix) {
return " for system_time as of ?";
}
@Override
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
return " for system_time between ? and ?";
}
@Override
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
return tableAlias + "." + sysPeriod + "_start";
}
@Override
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
return tableAlias + "." + sysPeriod + "_end";
}
}
@@ -0,0 +1,96 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.Query.ForUpdate;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.PlatformConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.SqlErrorCodes;
public class HanaPlatform extends DatabasePlatform {
public HanaPlatform() {
this.platform = Platform.HANA;
this.sqlLimiter = new HanaSqlLimiter();
this.persistBatchOnCascade = PersistBatch.NONE;
this.supportsResultSetConcurrencyModeUpdatable = false;
this.columnAliasPrefix = null;
this.historySupport = new HanaHistorySupport();
this.basicSqlLimiter = new HanaBasicSqlLimiter();
this.likeClauseRaw = "like ?";
this.maxConstraintNameLength = 127;
this.maxTableNameLength = 127;
this.dbDefaultValue.setNow("current_timestamp");
this.exceptionTranslator = new SqlErrorCodes().addAcquireLock("131", "133", "146")
.addDataIntegrity("130", "429", "461", "462").addDuplicateKey("144", "301", "349").build();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSelectLastInsertedIdTemplate("select current_identity_value() from sys.dummy");
this.dbIdentity.setSupportsGetGeneratedKeys(false);
this.dbIdentity.setSupportsIdentity(true);
this.dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
this.dbTypeMap.put(DbType.BINARY, new DbPlatformType("varbinary", 255));
this.dbTypeMap.put(DbType.BIT, new DbPlatformType("smallint", false));
this.dbTypeMap.put(DbType.BLOB, new DbPlatformType("blob", false));
this.dbTypeMap.put(DbType.CHAR, new DbPlatformType("nvarchar", 255));
this.dbTypeMap.put(DbType.CLOB, new DbPlatformType("nclob", false));
this.dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
this.dbTypeMap.put(DbType.JSONVARCHAR, new DbPlatformType("nvarchar", 255));
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("blob", false));
this.dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nclob", false));
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point"));
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("smallint", false));
this.dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint", false));
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
this.dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
this.dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
}
@Override
protected void addGeoTypes(int srid) {
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point(" + srid + ")", false));
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
}
@Override
protected String withForUpdate(String sql, ForUpdate forUpdateMode) {
switch (forUpdateMode) {
case BASE:
return sql + " for update";
case NOWAIT:
return sql + " for update nowait";
case SKIPLOCKED:
return sql + " for update ignore locked";
default:
throw new IllegalArgumentException("Unknown update mode: " + forUpdateMode.name());
}
}
@Override
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
super.configure(config, allQuotedIdentifiers);
if (config.getDbUuid().useBinary()) {
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varbinary", 16));
} else {
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
}
}
}
@@ -0,0 +1,35 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.config.dbplatform.SqlLimitRequest;
import io.ebean.config.dbplatform.SqlLimitResponse;
import io.ebean.config.dbplatform.SqlLimiter;
public class HanaSqlLimiter implements SqlLimiter {
@Override
public SqlLimitResponse limit(SqlLimitRequest request) {
String dbSql = request.getDbSql();
StringBuilder sb = new StringBuilder(50 + dbSql.length());
sb.append("select ");
if (request.isDistinct()) {
sb.append("distinct ");
}
sb.append(dbSql);
int firstRow = request.getFirstRow();
int maxRows = request.getMaxRows();
if (maxRows > 0) {
sb.append(" ").append("limit ").append(maxRows);
if (firstRow > 0) {
sb.append(" ").append("offset ");
sb.append(firstRow);
}
}
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
return new SqlLimitResponse(sql, false);
}
}
@@ -0,0 +1,4 @@
/**
* SAP HANA specific support.
*/
package io.ebean.config.dbplatform.hana;
@@ -136,8 +136,9 @@ public class PostgresPlatform extends DatabasePlatform {
/**
* Return SQL using built in partition helper functions to create some initial partitions.
*
* Only use this if extra-dll doesn't have some initial partitions defined (which it should).
* Only use this if extra-ddl doesn't have some initial partitions defined (which it should).
*/
@Override
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String pkey) {
if (property == null) {
property = "";
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
/**
@@ -147,6 +148,11 @@ public interface DbMigration {
*/
void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix);
/**
* Return the list of versions that contain pending drops.
*/
List<String> getPendingDrops();
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
* <p>
@@ -9,6 +9,10 @@ import io.ebean.event.BeanQueryAdapter;
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
/**
* Information and methods on BeanDescriptors made available to plugins.
@@ -18,6 +22,7 @@ public interface BeanType<T> {
/**
* Return the short name of the bean type.
*/
@Nonnull
String getName();
/**
@@ -28,11 +33,13 @@ public interface BeanType<T> {
/**
* Return the full name of the bean type.
*/
@Nonnull
String getFullName();
/**
* Return the class type this BeanDescriptor describes.
*/
@Nonnull
Class<T> getBeanType();
/**
@@ -43,6 +50,7 @@ public interface BeanType<T> {
/**
* Return all the properties for this bean type.
*/
@Nonnull
Collection<? extends Property> allProperties();
/**
@@ -197,6 +205,28 @@ public interface BeanType<T> {
*/
boolean hasInheritance();
/**
* Return true if this object is the root level object in its entity
* inheritance.
*/
boolean isInheritanceRoot();
/**
* Returns all direct children of this beantype
*/
List<BeanType<?>> getInheritanceChildren();
/**
* Returns the parent in inheritance hiearchy
*/
BeanType<?> getInheritanceParent();
/**
* Visit all children recursively
* @param visitor
*/
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
/**
* Return the discriminator column.
*/
@@ -1,5 +1,7 @@
package io.ebean.plugin;
import javax.annotation.Nonnull;
/**
* Property of a entity bean that can be read.
*/
@@ -8,8 +10,15 @@ public interface Property {
/**
* Return the name of the property.
*/
@Nonnull
String getName();
/**
* Return the type of the property.
*/
@Nonnull
Class<?> getPropertyType();
/**
* Return the value of the property on the given bean.
*/
@@ -168,13 +168,8 @@ public class LoadBeanRequest extends LoadRequest {
// necessary but allow processing to continue until it is accessed by client code
Object id = desc.getId(ebi.getOwner());
if (!loadedIds.contains(id)) {
if (desc.isSoftDelete()) {
// assume this is logically deleted (hence not found)
desc.setSoftDeleteValue(ebi.getOwner());
} else {
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
ebi.setLazyLoadFailure(id);
}
// assume this is logically deleted (hence not found)
desc.markAsDeleted(ebi.getOwner());
}
}
}
@@ -25,7 +25,7 @@ public class NaturalKeyQueryData<T> {
private List<Pairs.Entry> inPairs;
// IN clause - only one allowed
private Collection<?> inValues;
private List<Object> inValues;
private String inProperty;
// normal EQ expressions
@@ -52,36 +52,36 @@ public class NaturalKeyQueryData<T> {
/**
* Match for In Pairs expression. We only allow one IN clause.
*/
public boolean matchInPairs(Pairs pairs) {
public List<Pairs.Entry> matchInPairs(String property0, String property1, List<Pairs.Entry> inPairs) {
if (hasIn) {
// only 1 IN allowed (to project naturalIds)
return false;
return null;
}
if (matchProperty(pairs.getProperty0()) && matchProperty(pairs.getProperty1())) {
if (matchProperty(property0) && matchProperty(property1)) {
this.hasIn = true;
this.inProperty0 = pairs.getProperty0();
this.inProperty1 = pairs.getProperty1();
this.inPairs = pairs.getEntries();
return true;
this.inProperty0 = property0;
this.inProperty1 = property1;
this.inPairs = new ArrayList<>(inPairs); // will be modified
return this.inPairs;
}
return false;
return null;
}
/**
* Match for IN expression. We only allow one IN clause.
*/
public boolean matchIn(String propName, Collection<?> sourceValues) {
public List<Object> matchIn(String propName, List<Object> sourceValues) {
if (hasIn) {
// only 1 IN allowed (to project naturalIds)
return false;
return null;
}
if (matchProperty(propName)) {
this.hasIn = true;
this.inProperty = propName;
this.inValues = sourceValues;
return true;
this.inValues = new ArrayList<>(sourceValues);
return this.inValues;
}
return false;
return null;
}
/**
@@ -37,6 +37,7 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
/**
* Return the label for the query.
*/
@Override
String getLabel();
/**
@@ -312,4 +312,8 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader,
*/
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
/**
* Execute the sql update regardless of transaction batch mode.
*/
int executeNow(SpiSqlUpdate sqlUpdate);
}
@@ -134,27 +134,40 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Includes soft deletes rows in the result.
*/
SOFT_DELETED,
SOFT_DELETED(false),
/**
* Query runs against draft tables.
*/
DRAFT,
DRAFT(false),
/**
* Query runs against current data (normal).
*/
CURRENT,
CURRENT(false),
/**
* Query runs potentially returning many versions of the same bean.
*/
VERSIONS,
VERSIONS(true),
/**
* Query runs 'As Of' a given date time.
*/
AS_OF;
AS_OF(true);
private final boolean history;
TemporalMode(boolean history) {
this.history = history;
}
/**
* Return true if this is a history query.
*/
public boolean isHistory() {
return history;
}
/**
* Return the mode of the query of if null return CURRENT mode.
@@ -7,7 +7,6 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
@@ -8,7 +8,6 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
@@ -29,16 +30,21 @@ public class TransactionEvent implements Serializable {
*/
private final transient boolean local;
private final long startMillis;
private TransactionEventTable eventTables;
private transient TransactionEventBeans eventBeans;
private transient List<PersistRequestBean<?>> listenerNotify;
private transient DeleteByIdMap deleteByIdMap;
private transient CacheChangeSet changeSet;
/**
* Create the TransactionEvent, one per Transaction.
*/
public TransactionEvent() {
public TransactionEvent(long startMillis) {
this.startMillis = startMillis;
this.local = true;
}
@@ -71,8 +77,8 @@ public class TransactionEvent implements Serializable {
/**
* Return the list of PersistRequestBean's for this transaction.
*/
public List<PersistRequestBean<?>> getPersistRequestBeans() {
return (eventBeans == null) ? null : eventBeans.getRequests();
public List<PersistRequestBean<?>> getListenerNotify() {
return (listenerNotify == null) ? null : listenerNotify;
}
public TransactionEventTable getEventTables() {
@@ -94,17 +100,13 @@ public class TransactionEvent implements Serializable {
}
/**
* Add a inserted updated or deleted bean to the event.
* Add post commit listeners. Watch this for large transactions.
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify()) {
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
}
eventBeans.add(request);
public void addListenerNotify(PersistRequestBean<?> request) {
if (listenerNotify == null) {
listenerNotify = new ArrayList<>();
}
listenerNotify.add(request);
}
/**
@@ -112,11 +114,13 @@ public class TransactionEvent implements Serializable {
*/
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
if (eventBeans == null && deleteByIdMap == null && eventTables == null) {
if (changeSet == null && deleteByIdMap == null && eventTables == null) {
return null;
}
CacheChangeSet changeSet = new CacheChangeSet(manager.clockNowMillis());
if (changeSet == null) {
changeSet = new CacheChangeSet(manager.clockNowMillis());
}
if (eventTables != null && !eventTables.isEmpty()) {
// notify cache with table based changes
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
@@ -124,9 +128,6 @@ public class TransactionEvent implements Serializable {
dm.cacheNotify(tableIUD, changeSet);
}
}
if (eventBeans != null) {
eventBeans.notifyCache(changeSet);
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache(changeSet);
}
@@ -137,12 +138,25 @@ public class TransactionEvent implements Serializable {
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
if (persistRequestBeans != null) {
for (PersistRequestBean<?> persistRequestBean : persistRequestBeans) {
List<PersistRequestBean<?>> requests = getListenerNotify();
if (requests != null) {
for (PersistRequestBean<?> persistRequestBean : requests) {
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
}
}
}
/**
* Return the CacheChangeSet that we add cache notification messages to.
*
* We want to add to this change set as we process requests allowing the
* PersistRequestBean to be garbage collected for large transactions.
*/
public CacheChangeSet obtainCacheChangeSet() {
if (changeSet == null) {
changeSet = new CacheChangeSet(startMillis);
}
return changeSet;
}
}
@@ -1,44 +0,0 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.core.PersistRequestBean;
import java.util.ArrayList;
import java.util.List;
/**
* Lists of inserted updated and deleted beans that have a BeanPersistListener.
* <p>
* These beans will be sent to the appropriate BeanListeners after a successful
* commit of the transaction.
* </p>
*/
public class TransactionEventBeans {
final ArrayList<PersistRequestBean<?>> requests = new ArrayList<>();
/**
* Return the list of PersistRequests that BeanListeners are interested in.
*/
public List<PersistRequestBean<?>> getRequests() {
return requests;
}
/**
* Add a bean for BeanListener notification.
*/
public void add(PersistRequestBean<?> request) {
requests.add(request);
}
/**
* Collect the cache changes.
*/
public void notifyCache(CacheChangeSet changeSet) {
for (PersistRequestBean<?> request : requests) {
request.notifyCache(changeSet);
}
}
}
@@ -1,8 +1,10 @@
package io.ebeaninternal.dbmigration;
import io.ebean.config.DbMigrationConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.migration.ddl.DdlRunner;
import io.ebean.migration.runner.ScriptTransform;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.model.CurrentModel;
@@ -42,6 +44,7 @@ public class DdlGenerator {
private final boolean jaxbPresent;
private final boolean ddlCommitOnCreateIndex;
private final String dbSchema;
private final ScriptTransform scriptTransform;
private CurrentModel currentModel;
private String dropAllContent;
@@ -61,6 +64,7 @@ public class DdlGenerator {
this.runDdl = serverConfig.isDdlRun();
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
}
this.scriptTransform = createScriptTransform(serverConfig.getMigrationConfig());
}
/**
@@ -126,7 +130,9 @@ public class DdlGenerator {
private void createSchemaIfRequired(Connection connection) {
try {
server.getDatabasePlatform().createSchemaIfNotExists(dbSchema, connection);
for (String schema : dbSchema.split(",")) {
server.getDatabasePlatform().createSchemaIfNotExists(schema, connection);
}
} catch (SQLException e) {
throw new PersistenceException("Failed to create DB Schema", e);
}
@@ -144,7 +150,7 @@ public class DdlGenerator {
} else if (ddlCommitOnCreateIndex) {
runner.setCommitOnCreateIndex();
}
int count = runner.runAll(content, connection);
int count = runner.runAll(scriptTransform.transform(content), connection);
if (expectErrors) {
connection.setAutoCommit(false);
}
@@ -164,7 +170,7 @@ public class DdlGenerator {
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
if (extraApply != null) {
runScript(connection, false, extraApply, "extra-dll");
runScript(connection, false, extraApply, "extra-ddl");
}
}
@@ -186,13 +192,13 @@ public class DdlGenerator {
if (currentModel.isTablePartitioning()) {
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
runScript(connection, false, extraPartitioning, "builtin-partitioning-dll");
runScript(connection, false, extraPartitioning, "builtin-partitioning-ddl");
}
}
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
if (extraApply != null) {
runScript(connection, false, extraApply, "extra-dll");
runScript(connection, false, extraApply, "extra-ddl");
}
if (currentModel.isTablePartitioning()) {
@@ -203,7 +209,7 @@ public class DdlGenerator {
/**
* Check if table partitions exist and if not create some. The expectation is that
* extra-dll.xml should have some partition initialisation but this helps people get going.
* extra-ddl.xml should have some partition initialisation but this helps people get going.
*/
private void checkInitialTablePartitions(Connection connection) {
@@ -346,4 +352,12 @@ public class DdlGenerator {
}
}
/**
* Create the ScriptTransform for placeholder key/value replacement.
*/
private ScriptTransform createScriptTransform(DbMigrationConfig config) {
return ScriptTransform.build(config.getRunPlaceholders(), config.getRunPlaceholderMap());
}
}
@@ -10,6 +10,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
@@ -57,7 +58,7 @@ import java.util.List;
*
* DbMigration migration = DbMigration.create();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* migration.setPlatform(Platform.POSTGRES);
*
* migration.generateMigration();
*
@@ -65,7 +66,7 @@ import java.util.List;
*/
public class DefaultDbMigration implements DbMigration {
protected static final Logger logger = LoggerFactory.getLogger(DefaultDbMigration.class);
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration");
private static final String initialVersion = "1.0";
@@ -265,6 +266,7 @@ public class DefaultDbMigration implements DbMigration {
* migration.generateMigration();
*
* }</pre>
*
* @return the generated migration or null
*/
@Override
@@ -287,6 +289,12 @@ public class DefaultDbMigration implements DbMigration {
Request request = createRequest();
if (platforms.isEmpty()) {
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
} else {
for (Pair pair : platforms) {
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
File subPath = platformWriter.subPath(request.migrationDir, pair.prefix);
generateExtraDdl(subPath, pair.platform, request.isTablePartitioning());
}
}
String pendingVersion = generatePendingDrop();
@@ -303,6 +311,24 @@ public class DefaultDbMigration implements DbMigration {
}
}
/**
* Return the versions containing pending drops.
*/
@Override
public List<String> getPendingDrops() {
if (!online) {
DbOffline.setGenerateMigration();
}
setDefaults();
try {
return createRequest().getPendingDrops();
} finally {
if (!online) {
DbOffline.reset();
}
}
}
/**
* Load the configuration for each of the target platforms.
*/
@@ -316,7 +342,7 @@ public class DefaultDbMigration implements DbMigration {
/**
* Generate "repeatable" migration scripts.
* <p>
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
* These take scrips from extra-ddl.xml (typically views) and outputs "repeatable"
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
* migration runner.
* </p>
@@ -494,7 +520,7 @@ public class DefaultDbMigration implements DbMigration {
if (nextDrop != null) {
return nextDrop;
}
return migrationConfig.getGeneratePendingDrop();
return generatePendingDrop;
}
/**
@@ -504,14 +530,15 @@ public class DefaultDbMigration implements DbMigration {
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
String version = migrationConfig.getVersion();
String version = getVersion();
if (version == null) {
version = migrationModel.getNextVersion(initialVersion);
}
String fullVersion = migrationConfig.getApplyPrefix() + version;
if (migrationConfig.getName() != null) {
fullVersion += "__" + toUnderScore(migrationConfig.getName());
String name = getName();
if (name != null) {
fullVersion += "__" + toUnderScore(name);
} else if (dropsFor != null) {
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
@@ -539,8 +566,6 @@ public class DefaultDbMigration implements DbMigration {
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
File subPath = platformWriter.subPath(writePath, pair.prefix);
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
generateExtraDdl(subPath, pair.platform, currentModel.isTablePartitioning());
}
}
@@ -558,7 +583,7 @@ public class DefaultDbMigration implements DbMigration {
if (file.exists()) {
return false;
}
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null;
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
xmlWriter.write(dbMigration, file);
return true;
@@ -586,21 +611,66 @@ public class DefaultDbMigration implements DbMigration {
if (header != null) {
migrationConfig.setDdlHeader(header);
}
if (includeGeneratedFileComment != null) {
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
}
if (version != null) {
migrationConfig.setVersion(version);
}
if (name != null) {
migrationConfig.setName(name);
}
if (generatePendingDrop != null) {
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
}
}
}
/**
* Return the migration version (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
private String getVersion() {
String envVersion = readEnvironment("ddl.migration.version");
if (!isEmpty(envVersion)) {
return envVersion.trim();
}
return version;
}
/**
* Return the migration name which is short description text that can be appended to
* the migration version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* </p>
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
* </p>
*/
private String getName() {
String envName = readEnvironment("ddl.migration.name");
if (!isEmpty(envName)) {
return envName.trim();
}
return name;
}
/**
* Return true if the string is null or empty.
*/
private boolean isEmpty(String val) {
return val == null || val.trim().isEmpty();
}
/**
* Return the system or environment property.
*/
private String readEnvironment(String key) {
String val = System.getProperty(key);
if (val == null) {
val = System.getenv(key);
}
return val;
}
/**
* Return the file path to write the xml and sql to.
*/
@@ -662,6 +732,8 @@ public class DefaultDbMigration implements DbMigration {
return new DB2Platform();
case SQLITE:
return new SQLitePlatform();
case HANA:
return new HanaPlatform();
case GENERIC:
return new DatabasePlatform();
@@ -28,7 +28,11 @@ public class BaseDdlHandler implements DdlHandler {
protected final TableDdl tableDdl;
public BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.tableDdl = new BaseTableDdl(serverConfig, platformDdl);
this(serverConfig, platformDdl, new BaseTableDdl(serverConfig, platformDdl));
}
protected BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl, TableDdl tableDdl) {
this.tableDdl = tableDdl;
}
@Override
@@ -0,0 +1,225 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import java.io.IOException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractHanaDdl extends PlatformDdl {
private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\w+)\\s*\\[\\s*\\]\\s*(\\(\\d+\\))?", Pattern.CASE_INSENSITIVE);
public AbstractHanaDdl(DatabasePlatform platform) {
super(platform);
this.addColumn = "add (";
this.addColumnSuffix = ")";
this.alterColumn = "alter (";
this.alterColumnSuffix = ")";
this.columnDropDefault = " default null";
this.columnSetDefault = " default";
this.columnSetNotnull = " not null";
this.columnSetNull = " null";
this.dropColumn = "drop (";
this.dropColumnSuffix = ")";
this.dropConstraintIfExists = "drop constraint ";
this.dropIndexIfExists = "drop index ";
this.dropSequenceIfExists = "drop sequence ";
this.dropTableCascade = " cascade";
this.dropTableIfExists = "drop table ";
this.fallbackArrayType = "nvarchar(1000)";
this.historyDdl = new HanaHistoryDdl();
this.identitySuffix = " generated by default as identity";
}
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String currentType = alter.getCurrentType();
String type = alter.getType() != null ? alter.getType() : currentType;
type = convert(type, false);
currentType = convert(currentType, false);
boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
String notnullClause = notnull ? " not null" : "";
String defaultValue = DdlHelp.isDropDefault(alter.getDefaultValue()) ? "null"
: (alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue());
String defaultValueClause = (defaultValue == null || defaultValue.isEmpty()) ? "" : " default " + defaultValue;
try {
DdlBuffer buffer = new BaseDdlBuffer(null);
if (!isConvertible(currentType, type)) {
// add an intermediate conversion if possible
if (isNumberType(currentType)) {
// numbers can always be converted to decimal
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
.append(" decimal ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
.endOfStatement();
} else if (isStringType(currentType)) {
// strings can always be converted to nclob
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
.append(" nclob ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
.endOfStatement();
}
}
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
.append(" ").append(type).append(defaultValueClause).append(notnullClause).append(alterColumnSuffix);
return buffer.getBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
throw new UnsupportedOperationException();
}
@Override
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
return null;
}
@Override
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return new HanaDdlHandler(serverConfig, this);
}
@Override
public String alterColumnType(String tableName, String columnName, String type) {
return null;
}
@Override
protected String convertArrayType(String logicalArrayType) {
Matcher matcher = ARRAY_PATTERN.matcher(logicalArrayType);
if (matcher.matches()) {
return convert(matcher.group(1), false) + " array" + (matcher.group(2) == null ? "" : matcher.group(2));
} else {
return fallbackArrayType;
}
}
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
if (nullableColumns == null || nullableColumns.length == 0) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
} else {
return "-- cannot create unique index \"" + uqName + "\" on table \"" + tableName + "\" with nullable columns";
}
}
@Override
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
DdlBuffer buffer = new BaseDdlBuffer(null);
try {
buffer.append("delimiter $$").newLine();
buffer.append("do").newLine();
buffer.append("begin").newLine();
buffer.append("declare exit handler for sql_error_code 397 begin end").endOfStatement();
buffer.append("exec 'alter table ").append(tableName).append(" ").append(dropUniqueConstraint).append(" ")
.append(maxConstraintName(uniqueConstraintName)).append("'").endOfStatement();
buffer.append("end").endOfStatement();
buffer.append("$$");
return buffer.getBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
return alterTableDropUniqueConstraint(tableName, constraintName);
}
/**
* It is rather complex to delete a column on HANA as there must not exist any
* foreign keys. That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')")
.endOfStatement();
}
/**
* Check if a data type can be converted to another data type. Data types can't
* be converted if the target type has a lower precision than the source type.
*
* @param sourceType The source data type
* @param targetType the target data type
* @return {@code true} if the type can be converted, {@code false} otherwise
*/
private boolean isConvertible(String sourceType, String targetType) {
if (Objects.equals(sourceType, targetType)) {
return true;
}
if (sourceType == null || targetType == null) {
return true;
}
if ("bigint".equals(sourceType)) {
if ("integer".equals(targetType) || "smallint".equals(targetType) || "tinyint".equals(targetType)) {
return false;
}
} else if ("integer".equals(sourceType)) {
if ("smallint".equals(targetType) || "tinyint".equals(targetType)) {
return false;
}
} else if ("smallint".equals(sourceType)) {
if ("tinyint".equals(targetType)) {
return false;
}
} else if ("double".equals(sourceType)) {
if ("real".equals(targetType)) {
return false;
}
}
DbPlatformType dbPlatformSourceType = DbPlatformType.parse(sourceType);
if ("float".equals(dbPlatformSourceType.getName())) {
if ("real".equals(targetType)) {
return false;
}
} else if ("varchar".equals(dbPlatformSourceType.getName()) || "nvarchar".equals(dbPlatformSourceType.getName())) {
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
if ("varchar".equals(dbPlatformTargetType.getName()) || "nvarchar".equals(dbPlatformTargetType.getName())) {
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()) {
return false;
}
}
} else if ("decimal".equals(dbPlatformSourceType.getName())) {
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
if ("decimal".equals(dbPlatformTargetType.getName())) {
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()
|| dbPlatformSourceType.getDefaultScale() > dbPlatformTargetType.getDefaultScale()) {
return false;
}
}
}
return true;
}
private boolean isNumberType(String type) {
return type != null
&& ("bigint".equals(type) || "integer".equals(type) || "smallint".equals(type) || "tinyint".equals(type)
|| type.startsWith("float") || "real".equals(type) || "double".equals(type) || type.startsWith("decimal"));
}
private boolean isStringType(String type) {
return type != null
&& (type.startsWith("varchar") || type.startsWith("nvarchar") || "clob".equals(type) || "nclob".equals(type));
}
}
@@ -260,7 +260,7 @@ public class BaseTableDdl implements TableDdl {
String partitionMode = createTable.getPartitionMode();
DdlBuffer apply = writer.apply();
apply.append("create table ").append(tableName).append(" (");
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(tableName).append(" (");
writeTableColumns(apply, columns, useIdentity);
writeCheckConstraints(apply, createTable);
writeUniqueConstraints(apply, createTable);
@@ -909,7 +909,7 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == historySupport.TRIGGER_BASED) {
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == HistorySupport.TRIGGER_BASED) {
// mysql and sql server column type change allowing nulls in the history table column
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
AlterColumn alterHistoryColumn = new AlterColumn();
@@ -175,7 +175,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(table.getName()).append(historySuffix).append("(").newLine();
Collection<MColumn> cols = table.allColumns();
for (MColumn column : cols) {
@@ -238,8 +238,8 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
}
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodStart).endOfStatement();
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodStart);
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodEnd);
}
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
@@ -0,0 +1,51 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import java.io.IOException;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
public class HanaColumnStoreDdl extends AbstractHanaDdl {
public HanaColumnStoreDdl(DatabasePlatform platform) {
super(platform);
this.createTable = "create column table";
}
@Override
public String createIndex(String indexName, String tableName, String[] columns) {
if (columns == null || columns.length == 0) {
return "-- cannot create index: no columns given";
}
if (columns.length == 1) {
return "-- explicit index \"" + indexName + "\" for single column \"" + columns[0] + "\" of table \"" + tableName
+ "\" is not necessary";
}
StringBuilder buffer = new StringBuilder();
buffer.append("create inverted hash index ").append(maxConstraintName(indexName)).append(" on ").append(tableName);
appendColumns(columns, buffer);
return buffer.toString();
}
@Override
public String dropIndex(String indexName, String tableName) {
DdlBuffer buffer = new BaseDdlBuffer(null);
try {
buffer.append("delimiter $$").newLine();
buffer.append("do").newLine();
buffer.append("begin").newLine();
buffer.append("declare exit handler for sql_error_code 261 begin end").endOfStatement();
buffer.append("exec '").append(dropIndexIfExists).append(maxConstraintName(indexName)).append("'")
.endOfStatement();
buffer.append("end").endOfStatement();
buffer.append("$$");
return buffer.getBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,11 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
public class HanaDdlHandler extends BaseDdlHandler {
public HanaDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
super(serverConfig, platformDdl, new HanaTableDdl(serverConfig, platformDdl));
}
}
@@ -0,0 +1,157 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MTable;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class HanaHistoryDdl implements PlatformHistoryDdl {
private String systemPeriodStart;
private String systemPeriodEnd;
private PlatformDdl platformDdl;
private String historySuffix;
private final AtomicInteger counter = new AtomicInteger(0);
private Map<String, String> createdHistoryTables = new ConcurrentHashMap<>();
@Override
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "_start";
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "_end";
this.platformDdl = platformDdl;
this.historySuffix = serverConfig.getHistoryTableSuffix();
}
@Override
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
String tableName = table.getName();
String historyTableName = tableName + historySuffix;
DdlBuffer apply = writer.applyHistoryView();
if (apply.isEmpty()) {
createdHistoryTables.clear();
}
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(historyTableName).append(" (").newLine();
// create history table
Collection<MColumn> cols = table.allColumns();
for (MColumn column : cols) {
if (!column.isDraftOnly()) {
writeColumnDefinition(apply, column.getName(), column.getType(), column.getDefaultValue(), column.isNotnull(),
column.isIdentity() ? platformDdl.identitySuffix : null);
apply.append(",").newLine();
}
}
writeColumnDefinition(apply, systemPeriodStart, "TIMESTAMP", null, false, null);
apply.append(",").newLine();
writeColumnDefinition(apply, systemPeriodEnd, "TIMESTAMP", null, false, null);
apply.newLine().append(")").endOfStatement();
// enable system versioning
apply.append("alter table ").append(tableName).append(" add (").newLine();
apply.append(" ").append(systemPeriodStart).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW START, ").newLine();
apply.append(" ").append(systemPeriodEnd).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW END").newLine();
apply.append(")").endOfStatement();
apply.append("alter table ").append(tableName).append(" add period for system_time(").append(systemPeriodStart)
.append(",").append(systemPeriodEnd).append(")").endOfStatement();
enableSystemVersioning(apply, tableName, historyTableName, true, false);
createdHistoryTables.put(tableName, historyTableName);
dropHistoryTable(writer.dropAll(), tableName, historyTableName);
}
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
dropHistoryTable(writer.applyDropDependencies(), dropHistoryTable.getBaseTable(),
dropHistoryTable.getBaseTable() + historySuffix);
}
protected void dropHistoryTable(DdlBuffer apply, String baseTable, String historyTable) throws IOException {
// disable system versioning
disableSystemVersioning(apply, baseTable);
apply.append("alter table ").append(baseTable).append(" drop period for system_time").endOfStatement();
// drop the period columns
apply.append("alter table ").append(baseTable).append(" drop (").append(systemPeriodStart).append(",")
.append(systemPeriodEnd).append(")").endOfStatement();
// drop the history table
apply.append("drop table ").append(historyTable).append(" cascade").endOfStatement();
}
@Override
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
MTable table = writer.getTable(addHistoryTable.getBaseTable());
if (table == null) {
throw new IllegalStateException("MTable " + addHistoryTable.getBaseTable() + " not found in writer? (required for history DDL)");
}
createWithHistory(writer, table);
}
@Override
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) {
// nothing to do
}
protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type, String defaultValue,
boolean isNotNull, String generated) throws IOException {
String platformType = platformDdl.convert(type, false);
buffer.append(" ").append(platformDdl.lowerColumnName(columnName));
buffer.append(" ").append(platformType);
if (defaultValue != null) {
buffer.append(" default ").append(defaultValue);
}
if (isNotNull) {
buffer.append(" not null");
}
if (generated != null) {
buffer.append(" ").append(generated);
}
}
public void disableSystemVersioning(DdlBuffer apply, String tableName) throws IOException {
disableSystemVersioning(apply, tableName, false);
}
public void disableSystemVersioning(DdlBuffer apply, String tableName, boolean uniqueStatement) throws IOException {
apply.append("alter table ").append(tableName).append(" drop system versioning");
if (uniqueStatement) {
// needed for the DB migration test to prevent the statement from being filtered
// out as a duplicate
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
}
apply.endOfStatement();
}
public void enableSystemVersioning(DdlBuffer apply, String tableName, String historyTableName, boolean validated,
boolean uniqueStatement) throws IOException {
apply.append("alter table ").append(tableName).append(" add system versioning history table ").append(historyTableName);
if (!validated) {
apply.append(" not validated");
}
if (uniqueStatement) {
// needed for the DB migration test to prevent the statement from being filtered
// out as a duplicate
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
}
apply.endOfStatement();
}
public boolean isSystemVersioningEnabled(String tableName) {
return !createdHistoryTables.containsKey(tableName);
}
}
@@ -0,0 +1,11 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
public class HanaRowStoreDdl extends AbstractHanaDdl {
public HanaRowStoreDdl(DatabasePlatform platform) {
super(platform);
this.createTable = "create row table";
}
}
@@ -0,0 +1,134 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import java.io.IOException;
import java.util.List;
import io.ebean.config.PropertiesWrapper;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.model.MTable;
public class HanaTableDdl extends BaseTableDdl {
private final HanaHistoryDdl historyDdl;
private final boolean generateUniqueDdl;
public HanaTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) {
super(serverConfig, platformDdl);
this.historyDdl = (HanaHistoryDdl) platformDdl.historyDdl;
if (serverConfig.getProperties() != null) {
PropertiesWrapper wrapper = new PropertiesWrapper("ebean", "hana", serverConfig.getProperties(), serverConfig.getClassLoadConfig());
this.generateUniqueDdl = wrapper.getBoolean("generateUniqueDdl", false);
} else {
this.generateUniqueDdl = false;
}
}
@Override
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnBaseAttributes(alter);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
}
@Override
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
String tableName = addColumn.getTableName();
MTable table = writer.getTable(tableName);
if (table == null) {
super.generate(writer, addColumn);
return;
}
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
if (manageSystemVersioning) {
historyDdl.disableSystemVersioning(writer.apply(), table.getName(), this.generateUniqueDdl);
}
super.generate(writer, addColumn);
if (manageSystemVersioning) {
// make same changes to the history table
String historyTable = historyTable(tableName);
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
}
historyDdl.enableSystemVersioning(writer.apply(), table.getName(), historyTable, false, this.generateUniqueDdl);
}
}
@Override
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
String tableName = alterColumn.getTableName();
MTable table = writer.getTable(tableName);
if (table == null) {
super.generate(writer, alterColumn);
return;
}
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
if (manageSystemVersioning) {
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
}
super.generate(writer, alterColumn);
if (manageSystemVersioning) {
// make same changes to the history table
String historyTable = historyTable(tableName);
if (hasValue(alterColumn.getType()) || hasValue(alterColumn.getDefaultValue()) || alterColumn.isNotnull() != null) {
AlterColumn alterHistoryColumn = new AlterColumn();
alterHistoryColumn.setTableName(historyTable);
alterHistoryColumn.setColumnName(alterColumn.getColumnName());
alterHistoryColumn.setType(alterColumn.getType());
alterHistoryColumn.setDefaultValue(alterColumn.getDefaultValue());
alterHistoryColumn.setNotnull(alterColumn.isNotnull());
alterHistoryColumn.setCurrentType(alterColumn.getCurrentType());
alterHistoryColumn.setCurrentDefaultValue(alterColumn.getCurrentDefaultValue());
alterHistoryColumn.setCurrentNotnull(alterColumn.isCurrentNotnull());
String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
// write the apply to history table
writer.apply().append(histColumnDdl).endOfStatement();
}
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
}
}
@Override
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
String tableName = dropColumn.getTableName();
MTable table = writer.getTable(tableName);
if (table == null) {
super.generate(writer, dropColumn);
return;
}
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
if (manageSystemVersioning) {
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
}
super.generate(writer, dropColumn);
if (manageSystemVersioning) {
// also drop from the history table
String historyTable = historyTable(tableName);
alterTableDropColumn(writer.apply(), historyTable, dropColumn.getColumnName());
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
}
}
}
@@ -78,12 +78,16 @@ public class PlatformDdl {
protected String dropIndexIfExists = "drop index if exists ";
protected String alterColumn = "alter column";
protected String alterColumnSuffix = "";
protected String dropUniqueConstraint = "drop constraint";
protected String addConstraint = "add constraint";
protected String addColumn = "add column";
protected String addColumnSuffix = "";
protected String columnSetType = "";
@@ -96,6 +100,12 @@ public class PlatformDdl {
protected String columnSetNull = "set null";
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
protected String createTable = "create table";
protected String dropColumn = "drop column";
protected String dropColumnSuffix = "";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
@@ -458,6 +468,7 @@ public class PlatformDdl {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
buffer.append(addColumnSuffix);
buffer.endOfStatement();
// check constraints cannot be added in one statement for h2
@@ -467,14 +478,15 @@ public class PlatformDdl {
buffer.append(ddl).endOfStatement();
}
} else {
buffer.append(addColumnSuffix);
buffer.endOfStatement();
}
}
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
.endOfStatement();
buffer.append("alter table ").append(tableName).append(" ").append(dropColumn).append(" ").append(columnName)
.append(dropColumnSuffix).endOfStatement();
}
/**
@@ -494,19 +506,19 @@ public class PlatformDdl {
*/
public String alterColumnType(String tableName, String columnName, String type) {
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false);
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false) + alterColumnSuffix;
}
/**
* Alter a column adding or removing the not null constraint.
* <p>
* Note that that MySql and SQL Server instead use alterColumnBaseAttributes()
* Note that that MySql, SQL Server, and HANA instead use alterColumnBaseAttributes()
* </p>
*/
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
String suffix = notnull ? columnSetNotnull : columnSetNull;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
}
/**
@@ -522,17 +534,17 @@ public class PlatformDdl {
*/
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
}
/**
* Alter column setting both the type and not null constraint.
* <p>
* Used by MySql and SQL Server as these require both column attributes to be set together.
* Used by MySql, SQL Server, and HANA as these require both column attributes to be set together.
* </p>
*/
public String alterColumnBaseAttributes(AlterColumn alter) {
// by default do nothing, only used by mysql and sql server as they can only
// by default do nothing, only used by mysql, sql server, and HANA as they can only
// modify the column with the full column definition
return null;
}
@@ -662,6 +674,17 @@ public class PlatformDdl {
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
/**
* Returns the database-specific "create table" command prefix. For HANA this is
* either "create column table" or "create row table", for all other databases
* it is "create table".
*
* @return The "create table" command prefix
*/
public String getCreateTableCommandPrefix() {
return createTable;
}
public boolean suppressPrimaryKeyOnPartition() {
return false;
@@ -29,6 +29,8 @@ public class CurrentModel {
private final boolean platformTypes;
private final boolean jaxbPresent;
private ModelContainer model;
private ChangeSet changeSet;
@@ -58,6 +60,7 @@ public class CurrentModel {
this.constraintNaming = constraintNaming;
this.maxLength = maxLength(server, constraintNaming);
this.platformTypes = platformTypes;
this.jaxbPresent = server.getServerConfig().getClassLoadConfig().isJavaxJAXBPresent();
}
/**
@@ -130,7 +133,9 @@ public class CurrentModel {
ddl.append(header).append('\n');
}
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
if (jaxbPresent) {
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
}
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
@@ -1,6 +1,5 @@
package io.ebeaninternal.dbmigration.model;
import io.ebean.annotation.PartitionMode;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.SplitColumns;
import io.ebeaninternal.dbmigration.migration.AddColumn;
@@ -184,13 +184,12 @@ public class ModelContainer {
/**
* Unset the withHistory flag on the associated base table.
*/
private void applyChange(DropHistoryTable change) {
protected void applyChange(DropHistoryTable change) {
MTable table = tables.get(change.getBaseTable());
if (table == null) {
throw new IllegalStateException("Table [" + change.getBaseTable() + "] does not exist in model?");
if (table != null) {
table.setWithHistory(false);
}
table.setWithHistory(false);
}
private void applyChange(AddUniqueConstraint change) {
@@ -241,19 +240,14 @@ public class ModelContainer {
if (tables.containsKey(tableName)) {
throw new IllegalStateException("Table [" + tableName + "] already exists in model?");
}
MTable table = new MTable(createTable);
tables.put(tableName, table);
tables.put(tableName, new MTable(createTable));
}
/**
* Apply a DropTable change to the model.
*/
protected void applyChange(DropTable dropTable) {
String tableName = dropTable.getName();
if (!tables.containsKey(tableName)) {
throw new IllegalStateException("Table [" + tableName + "] does not exists in model?");
}
tables.remove(tableName);
tables.remove(dropTable.getName());
}
/**
@@ -264,22 +258,16 @@ public class ModelContainer {
if (indexes.containsKey(indexName)) {
throw new IllegalStateException("Index [" + indexName + "] already exists in model?");
}
MIndex index = new MIndex(createIndex);
indexes.put(createIndex.getIndexName(), index);
indexes.put(createIndex.getIndexName(), new MIndex(createIndex));
}
/**
* Apply a DropTable change to the model.
*/
protected void applyChange(DropIndex dropIndex) {
String name = dropIndex.getIndexName();
if (!indexes.containsKey(name)) {
throw new IllegalStateException("Index [" + name + "] does not exist in model?");
}
indexes.remove(name);
indexes.remove(dropIndex.getIndexName());
}
/**
* Apply a AddColumn change to the model.
*/
@@ -24,9 +24,6 @@ public class ModelBuildElementTable {
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
ctx.fkeyBuilder(table)
.addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true);
ctx.addTable(table);
}
@@ -66,7 +66,7 @@ public class CachedBeanDataFromBean {
Object v = aPropertiesNonTransient.getValue(bean);
aPropertiesNonTransient.setValue(sharableBean, v);
}
EntityBeanIntercept intercept = sharableBean._ebean_intercept();
EntityBeanIntercept intercept = sharableBean._ebean_getIntercept();
intercept.setReadOnly(true);
intercept.setLoaded();
return sharableBean;
@@ -19,6 +19,7 @@ public class DefaultServerQueryCache extends DefaultServerCache {
this.queryCacheEntryValidate = config.getQueryCacheEntryValidate();
}
@Override
protected Object unwrapEntry(CacheEntry entry) {
return ((QueryCacheEntry) entry.getValue()).getValue();
}
@@ -59,6 +59,7 @@ public class ClusterManager implements ServerLookup {
}
}
@Override
public EbeanServer getServer(String name) {
synchronized (monitor) {
return serverMap.get(name);
@@ -11,7 +11,6 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.util.BindParamsParser;
import java.sql.Connection;
@@ -7,6 +7,10 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Calendar;
import java.util.UUID;
@@ -341,6 +345,9 @@ public final class BasicTypeConverter implements Serializable {
} else if (value instanceof String) {
return Timestamp.valueOf((String) value);
} else if (value instanceof LocalDateTime) {
return Timestamp.valueOf((LocalDateTime) value);
} else if (value instanceof Number) {
return new Timestamp(((Number) value).longValue());
@@ -361,6 +368,9 @@ public final class BasicTypeConverter implements Serializable {
} else if (value instanceof String) {
return java.sql.Time.valueOf((String) value);
} else if (value instanceof LocalTime) {
return java.sql.Time.valueOf((LocalTime) value);
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
@@ -390,6 +400,9 @@ public final class BasicTypeConverter implements Serializable {
} else if (value instanceof Number) {
return new java.sql.Date(((Number) value).longValue());
} else if (value instanceof LocalDate) {
return java.sql.Date.valueOf((LocalDate)value);
} else {
String m = "Unable to convert [" + value.getClass().getName() + "] into a java.sql.Date.";
throw new RuntimeException(m);
@@ -5,6 +5,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
@@ -106,6 +107,9 @@ public class DatabasePlatformFactory {
if (dbName.equals("sqlite")) {
return new SQLitePlatform();
}
if (dbName.equals("hana")) {
return new HanaPlatform();
}
throw new RuntimeException("database platform " + dbName + " is not known?");
}
@@ -156,6 +160,8 @@ public class DatabasePlatformFactory {
return new DB2Platform();
} else if (dbProductName.contains("sql anywhere")) {
return new SqlAnywherePlatform();
} else if (dbProductName.contains("hdb")) {
return new HanaPlatform();
}
// use the standard one
@@ -15,10 +15,10 @@ import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.bootup.BootupClassPathSearch;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.lib.ShutdownManager;
import org.avaje.datasource.DataSourceAlertFactory;
import org.avaje.datasource.DataSourceConfig;
import org.avaje.datasource.DataSourceFactory;
import org.avaje.datasource.DataSourcePoolListener;
import io.ebean.datasource.DataSourceAlertFactory;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceFactory;
import io.ebean.datasource.DataSourcePoolListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -2054,6 +2054,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return persister.executeSqlUpdate(updSql, t);
}
@Override
public int executeNow(SpiSqlUpdate sqlUpdate) {
return persister.executeSqlUpdateNow(sqlUpdate, null);
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
persister.addBatch(sqlUpdate, transaction);
@@ -2346,7 +2351,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_intercept().isNew() && id != null) {
if (entityBean._ebean_getIntercept().isNew() && id != null) {
// Primary Key is changeable only on new models - so skip check if we are not
// new.
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
@@ -2376,7 +2381,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
ExpressionList<?> exprList = query.where();
if (!entityBean._ebean_intercept().isNew()) {
if (!entityBean._ebean_getIntercept().isNew()) {
// if model is not new, exclude ourself.
exprList.ne(idProperty.getName(), idProperty.getVal(entityBean));
}
@@ -133,6 +133,15 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
}
}
@Override
public int executeNow() {
if (server != null) {
return server.executeNow(this);
} else {
throw new IllegalStateException("server is null?");
}
}
@Override
public int[] executeBatch() {
if (server == null) {
@@ -230,6 +239,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
return this;
}
@Override
public SqlUpdate setNextParameter(Object value) {
setParameter(++addPos, value);
return this;
@@ -87,7 +87,7 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.TenantCatalogProvider;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import javax.sql.DataSource;
import java.io.PrintWriter;
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.TenantSchemaProvider;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import javax.sql.DataSource;
import java.io.PrintWriter;
@@ -8,6 +8,9 @@ import io.ebeaninternal.server.persist.BatchPostExecute;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.persist.PersistExecute;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
/**
* Wraps all the objects used to persist a bean.
*/
@@ -95,6 +98,13 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
return transaction.isBatchThisRequest();
}
/**
* Translate the SQLException into a specific exception given the platform.
*/
public PersistenceException translateSqlException(SQLException e) {
return transaction.translate(e.getMessage(), e);
}
/**
* Execute the statement.
*/
@@ -19,6 +19,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.BatchControl;
@@ -31,6 +32,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdate;
import io.ebeanservice.docstore.api.DocStoreUpdateContext;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import javax.persistence.EntityNotFoundException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.io.IOException;
@@ -130,6 +132,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private boolean[] dirtyProperties;
/**
* Imported OneToOne orphan that needs to be deleted.
*/
private EntityBean orphanBean;
/**
* Flag set when request is added to JDBC batch.
*/
@@ -166,6 +173,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private int pendingPostUpdateNotify;
/**
* Set to true when post execute has occured (so includes batch flush).
*/
private boolean postExecute;
/**
* Set to true after many properties have been persisted (so includes element collections).
*/
private boolean complete;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
@@ -434,10 +451,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Return true if this change should notify cache, listener or doc store.
* Return true if this change should notify persist listener or doc store (and keep the request).
*/
public boolean isNotify() {
return notifyCache || isNotifyPersistListener() || isDocStoreNotify();
private boolean isNotifyListeners() {
return isNotifyPersistListener() || isDocStoreNotify();
}
/**
@@ -854,12 +871,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
@Override
public final void checkRowCount(int rowCount) {
if (ConcurrencyMode.VERSION == concurrencyMode && rowCount != 1) {
// fix for oracle.
// see: https://stackoverflow.com/questions/19022175/executebatch-method-return-array-of-value-2-in-java
if (rowCount != Statement.SUCCESS_NO_INFO) {
String m = Message.msg("persist.conc2", String.valueOf(rowCount));
throw new OptimisticLockException(m, null, bean);
if (rowCount != 1 && rowCount != Statement.SUCCESS_NO_INFO) {
if (ConcurrencyMode.VERSION == concurrencyMode) {
throw new OptimisticLockException(Message.msg("persist.conc2", String.valueOf(rowCount)), null, bean);
} else if (rowCount == 0 && type == Type.UPDATE) {
throw new EntityNotFoundException("No rows updated");
}
}
switch (type) {
@@ -912,7 +928,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
@Override
public void postExecute() {
postExecute = true;
if (controller != null) {
controllerPost();
}
@@ -933,7 +949,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
postInsert();
}
addEvent();
addPostCommitListeners();
notifyCacheOnPostExecute();
if (isLogSummary()) {
logSummary();
@@ -1011,14 +1028,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Add the bean to the TransactionEvent. This will be used by TransactionManager to sync Cache,
* Cluster and text indexes.
* Add the request to TransactionEvent if there are post commit listeners.
*/
private void addEvent() {
private void addPostCommitListeners() {
TransactionEvent event = transaction.getEvent();
if (event != null) {
event.add(this);
if (event != null && isNotifyListeners()) {
event.addListenerNotify(this);
}
}
@@ -1081,23 +1096,62 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Check if any of its many properties where cascade saved and hence we need to update related
* many property caches.
* Completed insert or delete request. Do cache notify in non-batched case.
*/
public void checkUpdatedManysOnly() {
public void complete() {
notifyCacheOnComplete();
}
/**
* Completed update request handling cases for element collection and where ONLY
* many properties were updated.
*/
public void completeUpdate() {
if (!dirty && updatedManys != null) {
// set the flag and register for post commit processing if there
// is caching or registered listeners
if (idValue == null) {
this.idValue = beanDescriptor.getId(entityBean);
}
// not dirty so postExecute() will never be called
// set true to trigger cache notify if needed
postExecute = true;
updatedManysOnly = true;
setNotifyCache();
addEvent();
addPostCommitListeners();
}
notifyCacheOnComplete();
postUpdateNotify();
}
/**
* Notify cache when using batched persist.
*/
private void notifyCacheOnPostExecute() {
postExecute = true;
if (notifyCache && complete) {
// add cache notification (on batch persist)
TransactionEvent event = transaction.getEvent();
if (event != null) {
notifyCache(event.obtainCacheChangeSet());
}
}
}
/**
* Notify cache when not use batched persist.
*/
private void notifyCacheOnComplete() {
complete = true;
if (notifyCache && postExecute) {
// add cache notification (on non-batch persist)
TransactionEvent event = transaction.getEvent();
if (event != null) {
notifyCache(event.obtainCacheChangeSet());
}
}
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
@@ -1307,14 +1361,22 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Set the request flags indicating this is an insert.
*/
public void flagInsert() {
flags = Flags.setInsert(flags);
if (intercept.isNew()) {
flags = Flags.setInsertNormal(flags);
} else {
flags = Flags.setInsert(flags);
}
}
/**
* Unset the request insert flag indicating this is an update.
*/
public void flagUpdate() {
flags = Flags.unsetInsert(flags);
if (intercept.isLoaded()) {
flags = Flags.setUpdateNormal(flags);
} else {
flags = Flags.setUpdate(flags);
}
}
/**
@@ -1369,4 +1431,25 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
}
}
/**
* Set an orphan bean that needs to be deleted AFTER the request has persisted.
*/
public void setImportedOrphanForRemoval(BeanPropertyAssocOne<?> prop) {
Object orphan = getOrigValue(prop);
if (orphan instanceof EntityBean) {
orphanBean = (EntityBean) orphan;
}
}
public EntityBean getImportedOrphanForRemoval() {
return orphanBean;
}
/**
* Return the SQL used to fetch the last inserted id value.
*/
public String getSelectLastInsertedId() {
return beanDescriptor.getSelectLastInsertedId(publish);
}
}
@@ -28,18 +28,23 @@ public final class PersistRequestUpdateSql extends PersistRequest {
private boolean addBatch;
/**
* Create.
*/
private boolean forceNoBatch;
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
SpiTransaction t, PersistExecute persistExecute) {
SpiTransaction t, PersistExecute persistExecute, boolean forceNoBatch) {
super(server, t, persistExecute, sqlUpdate.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = sqlUpdate;
this.forceNoBatch = forceNoBatch;
updateSql.reset();
}
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
SpiTransaction t, PersistExecute persistExecute) {
this(server, sqlUpdate, t, persistExecute, false);
}
@Override
public void profile(long offset, int flushCount) {
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
@@ -60,7 +65,7 @@ public final class PersistRequestUpdateSql extends PersistRequest {
@Override
public boolean isBatchThisRequest() {
return addBatch || super.isBatchThisRequest();
return !forceNoBatch && (addBatch || super.isBatchThisRequest());
}
@Override
@@ -74,10 +74,15 @@ public interface Persister {
int executeOrmUpdate(Update<?> update, Transaction t);
/**
* Execute the UpdateSql.
* Execute the SqlUpdate (taking into account transaction batch mode).
*/
int executeSqlUpdate(SqlUpdate update, Transaction t);
/**
* Execute the SqlUpdate now regardless of transaction batch mode.
*/
int executeSqlUpdateNow(SpiSqlUpdate update, Transaction t);
/**
* Execute the CallableSql.
*/
@@ -4,6 +4,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HanaColumnStoreDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MySqlDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.Oracle10Ddl;
@@ -47,6 +48,8 @@ public class PlatformDdlBuilder {
case SQLSERVER17:
case SQLSERVER:
return new SqlServerDdl(platform);
case HANA:
return new HanaColumnStoreDdl(platform);
default:
return new PlatformDdl(platform);
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -7,8 +7,14 @@ import io.ebeaninternal.server.query.SqlJoinType;
*/
class AssocOneHelpRefExported extends AssocOneHelp {
public AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
private final boolean softDelete;
private final String softDeletePredicate;
AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
super(property);
this.softDelete = property.targetDescriptor.isSoftDelete();
this.softDeletePredicate = (softDelete) ? property.targetDescriptor.getSoftDeletePredicate("") : null;
}
/**
@@ -28,6 +34,10 @@ class AssocOneHelpRefExported extends AssocOneHelp {
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
String relativePrefix = ctx.getRelativePrefix(property.getName());
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
if (softDelete && !ctx.isIncludeSoftDelete()) {
property.tableJoin.addJoin(joinType, relativePrefix, ctx, softDeletePredicate);
} else {
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
}
@@ -54,7 +54,7 @@ class AssocOneHelpRefInherit extends AssocOneHelp {
// for inheritance hierarchy create the correct type for this row...
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = desc.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (disableLazyLoading) {
if (!disableLazyLoading) {
ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
@@ -42,6 +42,7 @@ abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
}
}
@SuppressWarnings("rawtypes")
@Override
public Collection underlying(Object value) {
if (value instanceof BeanCollection) {
@@ -39,6 +39,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
/**
* Create an empty collection of the correct type without a parent bean.
*/
@Override
BeanCollection<T> createEmptyNoParent();
/**
@@ -49,6 +50,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
/**
* Add a bean to the List Set or Map.
*/
@Override
void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck);
/**
@@ -22,7 +22,7 @@ public class BeanCollectionUtil {
}
/**
* Return the details of the collection or map taking care to avoid
* Return the details (map entry set) of the collection or map taking care to avoid
* unnecessary fetching of the data.
*/
public static Collection<?> getActualEntries(Object o) {
@@ -48,4 +48,32 @@ public class BeanCollectionUtil {
}
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
}
/**
* Return the details (map values) of the collection or map taking care to avoid
* unnecessary fetching of the data.
*/
public static Collection<?> getActualDetails(Object o) {
if (o == null) {
return null;
}
if (o instanceof BeanCollection<?>) {
BeanCollection<?> bc = (BeanCollection<?>) o;
if (!bc.isPopulated()) {
return null;
}
// For maps this is a collection of Map.Entry, otherwise it
// returns a collection of beans
return bc.getActualDetails();
}
if (o instanceof Map<?, ?>) {
// yes, we want the entrySet (to set the keys)
return ((Map<?, ?>) o).values();
} else if (o instanceof Collection<?>) {
return ((Collection<?>) o);
}
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
}
}
@@ -7,7 +7,7 @@ import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PartitionMode;
import io.ebean.annotation.Formula;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
@@ -61,6 +61,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
import io.ebeaninternal.server.el.ElComparator;
import io.ebeaninternal.server.el.ElComparatorCompound;
import io.ebeaninternal.server.el.ElComparatorNoop;
import io.ebeaninternal.server.el.ElComparatorProperty;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyDeploy;
@@ -100,6 +101,7 @@ import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -107,6 +109,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Describes Beans including their deployment information.
@@ -179,6 +183,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* getGeneratedKeys is not supported.
*/
private final String selectLastInsertedId;
private final String selectLastInsertedIdDraft;
private final boolean autoTunable;
@@ -470,6 +475,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.sequenceInitialValue = deploy.getSequenceInitialValue();
this.sequenceAllocationSize = deploy.getSequenceAllocationSize();
this.selectLastInsertedId = deploy.getSelectLastInsertedId();
this.selectLastInsertedIdDraft = deploy.getSelectLastInsertedIdDraft();
this.concurrencyMode = deploy.getConcurrencyMode();
this.updateChangesOnly = deploy.isUpdateChangesOnly();
this.indexDefinitions = deploy.getIndexDefinitions();
@@ -492,7 +498,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
this.softDelete = (softDeleteProperty != null);
// if formula is set, the property is virtual only (there is no column in db) the formula must evaluate to true,
// if there is a join to a deleted bean. Example: '@Formula(select = "${ta}.user_id is null")'
// this is required to support markAsDelete on beans that may have no FK constraint.
this.softDelete = (softDeleteProperty != null && !softDeleteProperty.isFormula());
this.idProperty = listHelper.getId();
this.versionProperty = listHelper.getVersionProperty();
this.unmappedJson = listHelper.getUnmappedJson();
@@ -1133,6 +1142,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return true if this object is the root level object in its entity
* inheritance.
*/
@Override
public boolean isInheritanceRoot() {
return inheritInfo == null || inheritInfo.isRoot();
}
@@ -1829,6 +1839,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return the IdBinder which is helpful for handling the various types of Id.
*/
@Override
public IdBinder getIdBinder() {
return idBinder;
}
@@ -1937,6 +1948,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Creates a new entity bean without invoking {@link BeanPostConstructListener#postCreate(Object)}
*/
@Override
public EntityBean createEntityBean() {
return createEntityBean(false);
}
@@ -2207,6 +2219,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Put the bean into the persistence context if it is absent.
*/
@Override
public Object contextPutIfAbsent(PersistenceContext pc, Object id, EntityBean localBean) {
return pc.putIfAbsent(rootBeanType, id, localBean);
}
@@ -2431,7 +2444,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private ElComparator<T> createPropertyComparator(SortByClause.Property sortProp) {
ElPropertyValue elGetValue = getElGetValue(sortProp.getName());
if (elGetValue == null) {
logger.error("Sort property [" + sortProp + "] not found in " + beanType + ". Cannot sort.");
return new ElComparatorNoop<>();
}
if (elGetValue.isAssocMany()) {
logger.error("Sort property [" + sortProp + "] in " + beanType + " is a many-property. Cannot sort.");
return new ElComparatorNoop<>();
}
Boolean nullsHigh = sortProp.getNullsHigh();
if (nullsHigh == null) {
nullsHigh = Boolean.TRUE;
@@ -2633,6 +2654,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return autoTunable;
}
@Override
public boolean isElementType() {
return false;
}
@@ -2641,6 +2663,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Returns the Inheritance mapping information. This will be null if this type
* of bean is not involved in any ORM inheritance mapping.
*/
@Override
public InheritInfo getInheritInfo() {
return inheritInfo;
}
@@ -2809,6 +2832,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Returns true if this bean is based on RawSql.
*/
@Override
public boolean isRawSqlBased() {
return EntityType.SQL == entityType;
}
@@ -2863,6 +2887,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return the base table to use given the query temporal mode.
*/
@Override
public String getBaseTable(SpiQuery.TemporalMode mode) {
switch (mode) {
case DRAFT:
@@ -2890,6 +2915,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return readAuditing;
}
@Override
public boolean isSoftDelete() {
return softDelete;
}
@@ -2902,10 +2928,25 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return softDeleteProperty.getSoftDeleteDbSet();
}
@Override
public String getSoftDeletePredicate(String tableAlias) {
return softDeleteProperty.getSoftDeleteDbPredicate(tableAlias);
}
@Override
public void markAsDeleted(EntityBean bean) {
if (softDeleteProperty == null) {
Object id = getId(bean);
logger.info("(Lazy) loading unsuccessful for type:{} id:{} - expecting when bean has been deleted", getName(), id);
bean._ebean_getIntercept().setLazyLoadFailure(id);
} else {
setSoftDeleteValue(bean);
bean._ebean_getIntercept().setLoaded();
setAllLoaded(bean);
}
}
/**
* Return true if this entity type is draftable.
*/
@@ -2974,6 +3015,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Set the draft to true for this entity bean instance.
* This bean is being loaded via asDraft() query.
*/
@Override
public void setDraft(EntityBean entityBean) {
if (draft != null) {
draft.setValue(entityBean, true);
@@ -3027,6 +3069,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return true if this entity bean has history support.
*/
@Override
public boolean isHistorySupport() {
return historySupport;
}
@@ -3082,8 +3125,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* supported.
* </p>
*/
public String getSelectLastInsertedId() {
return selectLastInsertedId;
public String getSelectLastInsertedId(boolean publish) {
return publish ? selectLastInsertedId : selectLastInsertedIdDraft;
}
/**
* Return true if this bean uses a SQL select to fetch the last inserted id value.
*/
public boolean supportsSelectLastInsertedId() {
return selectLastInsertedId != null;
}
@Override
@@ -3475,4 +3525,29 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public List<BeanProperty[]> getUniqueProps() {
return propertiesUnique;
}
@Override
public List<BeanType<?>> getInheritanceChildren() {
if (hasInheritance()) {
return getInheritInfo().getChildren()
.stream()
.map(InheritInfo::desc)
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public BeanType<?> getInheritanceParent() {
return getInheritInfo() == null ? null : getInheritInfo().getParent().desc();
}
@Override
public void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor) {
if (hasInheritance()) {
getInheritInfo().visitChildren(info -> visitor.accept(info.desc()));
}
}
}
@@ -348,13 +348,12 @@ final class BeanDescriptorCacheHelp<T> {
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
Collection<?> actualDetails = BeanCollectionUtil.getActualDetails(details);
if (actualDetails == null) {
return null;
}
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
List<Object> idList = new ArrayList<>(actualDetails.size());
for (Object bean : actualDetails) {
idList.add(targetDescriptor.getId((EntityBean) bean));
@@ -794,7 +793,6 @@ final class BeanDescriptorCacheHelp<T> {
// query caching only
return;
}
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
for (BeanPropertyAssocMany<?> many : manyCollections) {
@@ -46,6 +46,7 @@ class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
return (EntityBean)prototype._ebean_newInstance();
}
@Override
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
@@ -24,6 +24,7 @@ class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
this.scalarType = firstBaseScalarType();
}
@Override
public void bindElementValue(SqlUpdate insert, Object value) {
insert.setNextParameter(value);
}
@@ -6,6 +6,7 @@ import io.ebean.RawSqlBuilder;
import io.ebean.annotation.ConstraintMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.config.BeanNotEnhancedException;
import io.ebean.config.EncryptKey;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.NamingConvention;
@@ -388,6 +389,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return asOfTableMap;
} catch (BeanNotEnhancedException e) {
throw e;
} catch (RuntimeException e) {
logger.error("Error in deployment", e);
throw e;
@@ -1010,7 +1014,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* into the order_id column on the order_lines table).
* </p>
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void makeUnidirectional(DeployBeanPropertyAssocMany<?> oneToMany) {
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
@@ -1387,9 +1390,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
if (IdType.IDENTITY == desc.getIdType()) {
// used when getGeneratedKeys is not supported (SQL Server 2000)
// used when getGeneratedKeys is not supported (SQL Server 2000, SAP Hana)
String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable());
desc.setSelectLastInsertedId(selectLastInsertedId);
String selectLastInsertedIdDraft = (!desc.isDraftable()) ? selectLastInsertedId : dbIdentity.getSelectLastInsertedId(desc.getDraftTable());
desc.setSelectLastInsertedId(selectLastInsertedId, selectLastInsertedIdDraft);
return;
}
@@ -1470,7 +1474,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (isPersistentField(prop)) {
throw new IllegalStateException(
"If you are running in an IDE with enhancement plugin try a Build -> Rebuild Project to recompile and enhance all entity beans. " +
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
}
} else {
@@ -1553,7 +1557,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Class<?> beanClass = desc.getBeanType();
if (!hasEntityBeanInterface(beanClass)) {
throw new IllegalStateException("Bean " + beanClass + " is not enhanced?");
String msg = "Bean " + beanClass + " is not enhanced? If you are running in IDEA or Eclipse check" +
" that the enhancement plugin is installed. See https://ebean.io/docs/trouble-shooting#not-enhanced";
throw new BeanNotEnhancedException(msg);
}
// the bean already implements EntityBean
@@ -1582,7 +1588,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// ok to stop and treat just the same as Object.class
return;
}
throw new IllegalStateException("Super type " + superclass + " is not enhanced?");
throw new BeanNotEnhancedException("Super type " + superclass + " is not enhanced? See https://ebean.io/docs/trouble-shooting#not-enhanced");
}
// recursively continue up the inheritance hierarchy
@@ -1665,11 +1671,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private ElementHelp elementHelper(ManyType manyType) {
switch (manyType) {
case LIST: return new ElementHelpList();
case SET: return new ElementHelpSet();
case MAP: return new ElementHelpMap();
case LIST:
return new ElementHelpList();
case SET:
return new ElementHelpSet();
case MAP:
return new ElementHelpMap();
default:
throw new IllegalStateException("manyType unexpected "+manyType);
throw new IllegalStateException("manyType unexpected " + manyType);
}
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.config.BeanNotRegisteredException;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import javax.persistence.PersistenceException;
import java.util.Map;
/**
@@ -22,8 +22,8 @@ public class BeanEmbeddedMetaFactory {
BeanDescriptor<?> targetDesc = owner.getBeanDescriptor(prop.getTargetType());
if (targetDesc == null) {
String msg = "Could not find BeanDescriptor for " + prop.getTargetType()
+ ". Perhaps the EmbeddedId class is not registered?";
throw new PersistenceException(msg);
+ ". Perhaps the EmbeddedId class is not registered? See https://ebean.io/docs/trouble-shooting#not-registered";
throw new BeanNotRegisteredException(msg);
}
// deployment override information (column names)
@@ -979,6 +979,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the full name of this property.
*/
@Override
public String getFullBeanName() {
return descriptor.getFullName() + "." + name;
}
@@ -994,6 +995,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the scalarType.
*/
@Override
@SuppressWarnings(value = "unchecked")
public ScalarType<Object> getScalarType() {
return scalarType;
@@ -1283,7 +1285,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return true if this is a ManyToMany with history support (on the intersection table).
*/
public boolean isManyToManyWithHistory() {
return !excludedFromHistory && descriptor.isHistorySupport();
return false;
}
/**
@@ -1369,6 +1371,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the property type.
*/
@Override
public Class<?> getPropertyType() {
return propertyType;
}
@@ -292,6 +292,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
* Return an extra clause to add to the query for loading or joining
* to this bean type.
*/
@Override
public String getExtraWhere() {
return extraWhere;
}
@@ -208,6 +208,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
}
@Override
public boolean isManyToManyWithHistory() {
return manyToMany && !excludedFromHistory && descriptor.isHistorySupport();
}
@Override
protected void docStoreIncludeByDefault(PathProperties pathProps) {
// by default not including "Many" properties in document store
@@ -250,6 +255,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
/**
* Add the bean to the appropriate collection on the parent bean.
*/
@Override
public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean, boolean withCheck) {
BeanCollection<?> bc = (BeanCollection<?>) super.getValue(parentBean);
if (bc == null) {
@@ -318,11 +324,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
/**
* Find the Id's of detail beans given a parent Id or list of parent Id's.
*/
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds, boolean hard) {
if (parentId != null) {
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds);
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds, hard);
} else {
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds);
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds, hard);
}
}
@@ -533,6 +539,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
/**
* Return true if this is many to many.
*/
@Override
public boolean hasJoinTable() {
return manyToMany || o2mJoinTable;
}
@@ -558,6 +565,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
/**
* ManyToMany only, join from local table to intersection table.
*/
@Override
public TableJoin getIntersectionTableJoin() {
return intersectionJoin;
}
@@ -601,6 +609,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return mapKey;
}
@Override
public BeanCollection<?> createReferenceIfNull(EntityBean parentBean) {
Object v = getValue(parentBean);
@@ -633,6 +642,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return descriptor.getId(parentBean);
}
@Override
public void addSelectExported(DbSqlContext ctx, String tableAlias) {
String alias = hasJoinTable() ? "int_" : tableAlias;
@@ -127,14 +127,16 @@ class BeanPropertyAssocManySqlHelp<T> {
many.bindParentIdsIn(expr, parentIds, query);
}
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds, boolean hard) {
String rawWhere = deriveWhereParentIdSql(false, "");
SpiEbeanServer server = descriptor.getEbeanServer();
SpiQuery<?> q = many.newQuery(server);
many.bindParentIdEq(rawWhere, parentId, q);
if (hard) {
q.setIncludeSoftDeletes();
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
@@ -142,7 +144,7 @@ class BeanPropertyAssocManySqlHelp<T> {
return server.findIds(q, t);
}
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds, boolean hard) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
@@ -153,7 +155,9 @@ class BeanPropertyAssocManySqlHelp<T> {
SpiQuery<?> q = many.newQuery(server);
//Query<?> q = server.find(propertyType);
many.bindParentIdsIn(expr, parentIds, q);
if (hard) {
q.setIncludeSoftDeletes();
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
@@ -261,7 +261,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t) {
String rawWhere = deriveWhereParentIdSql(true);
String inClause = targetIdBinder.getIdInValueExpr(false, parentIds.size());
String inClause = getIdBinder().getIdInValueExpr(false, parentIds.size());
String expr = rawWhere + inClause;
SpiEbeanServer server = server();
@@ -537,8 +537,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
BeanProperty[] emIds = targetDesc.propertiesBaseScalar();
try {
for (BeanProperty emId : emIds) {
ExportedProperty expProp = findMatch(true, emId);
list.add(expProp);
list.add(findMatch(true, emId));
}
} catch (PersistenceException e) {
// not found as individual scalar properties
@@ -547,8 +546,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
} else {
if (idProp != null) {
ExportedProperty expProp = findMatch(false, idProp);
list.add(expProp);
list.add(findMatch(false, idProp));
}
}
@@ -12,8 +12,6 @@ public interface DbSqlContext {
*/
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String inheritance);
void pushSecondaryTableAlias(String alias);
/**
* Push the current table alias onto the stack.
*/
@@ -91,11 +89,6 @@ public interface DbSqlContext {
*/
String getContent();
/**
* Return the current join node.
*/
String peekJoin();
/**
* Push a join node onto the stack.
*/
@@ -125,6 +118,11 @@ public interface DbSqlContext {
*/
void appendHistorySysPeriod();
/**
* Return true if the query includes soft deleted rows.
*/
boolean isIncludeSoftDelete();
/**
* Return true if the query is a 'asDraft' query.
*/
@@ -139,4 +137,5 @@ public interface DbSqlContext {
* Append 'for update' lock hints on FROM clause (sql server only).
*/
void appendFromForUpdate();
}
@@ -22,6 +22,11 @@ public abstract class DeployParser {
*/
protected static final char UNDERSCORE = '_';
protected static final char OPEN_SQUARE_BRACKET = '[';
protected static final char CLOSE_SQUARE_BRACKET = ']';
protected static final char DOUBLE_QUOTE = '\"';
protected static final char BACK_QUOTE = '`';
/**
* Used to determine when a column name terminates.
*/
@@ -183,10 +188,10 @@ public abstract class DeployParser {
wordBuffer.append(ch);
return false;
}
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD;
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD || ch == DOUBLE_QUOTE || ch == CLOSE_SQUARE_BRACKET || ch == BACK_QUOTE;
}
private boolean isWordStart(char ch) {
return Character.isLetter(ch) || ch == UNDERSCORE;
return Character.isLetter(ch) || ch == UNDERSCORE || ch == DOUBLE_QUOTE || ch == OPEN_SQUARE_BRACKET || ch == BACK_QUOTE;
}
}
@@ -5,6 +5,8 @@ import io.ebean.bean.EntityBeanIntercept;
class ElementEntityBean implements EntityBean {
private static final long serialVersionUID = 8742536671024715601L;
private final String[] properties;
private Object[] data;
@@ -126,6 +126,16 @@ public final class TableJoin {
return type;
}
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx, String predicate) {
String[] names = SplitName.split(prefix);
String a1 = ctx.getTableAlias(names[0]);
String a2 = ctx.getTableAlias(prefix);
SqlJoinType returnJoinType = addJoin(joinType, a1, a2, ctx);
ctx.append("and ").append(a2).append(predicate);
return returnJoinType;
}
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
String[] names = SplitName.split(prefix);
@@ -3,7 +3,6 @@ package io.ebeaninternal.server.deploy.meta;
import io.ebean.annotation.Cache;
import io.ebean.annotation.DocStore;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PartitionMode;
import io.ebean.config.ServerConfig;
import io.ebean.config.TableName;
import io.ebean.config.dbplatform.IdType;
@@ -126,6 +125,7 @@ public class DeployBeanDescriptor<T> {
* Used with Identity columns but no getGeneratedKeys support.
*/
private String selectLastInsertedId;
private String selectLastInsertedIdDraft;
/**
* The concurrency mode for beans of this type.
@@ -222,6 +222,8 @@ public class DeployBeanDescriptor<T> {
private short profileId;
private Object jacksonAnnotatedClass;
/**
* Construct the BeanDescriptor.
*/
@@ -839,11 +841,16 @@ public class DeployBeanDescriptor<T> {
return selectLastInsertedId;
}
public String getSelectLastInsertedIdDraft() {
return selectLastInsertedIdDraft;
}
/**
* Set the SQL used to return the last inserted Id.
*/
public void setSelectLastInsertedId(String selectLastInsertedId) {
public void setSelectLastInsertedId(String selectLastInsertedId, String selectLastInsertedIdDraft) {
this.selectLastInsertedId = selectLastInsertedId;
this.selectLastInsertedIdDraft = selectLastInsertedIdDraft;
}
/**
@@ -1232,6 +1239,7 @@ public class DeployBeanDescriptor<T> {
this.descriptor = descriptor;
}
@Override
public String getDeployWord(String expression) {
return descriptor.getDeployWord(expression);
}
@@ -1246,4 +1254,13 @@ public class DeployBeanDescriptor<T> {
return (property == null) ? null : "${ta}." + property.getDbColumn();
}
/**
* Returns the jackson annotated class, if jackson is present.
*/
public Object /*AnnotatedClass*/ getJacksonAnnotatedClass() {
if (jacksonAnnotatedClass == null) {
jacksonAnnotatedClass = new DeployBeanObtainJackson(serverConfig, beanType).obtain();
}
return jacksonAnnotatedClass;
}
}
@@ -0,0 +1,30 @@
package io.ebeaninternal.server.deploy.meta;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
import io.ebean.config.ServerConfig;
/**
* Used to obtain the Jackson AnnotatedClass for a given bean type utlimately to obtain field level Jackson annotations.
*/
class DeployBeanObtainJackson<T> {
private final ServerConfig serverConfig;
private final Class<T> beanType;
DeployBeanObtainJackson(ServerConfig serverConfig, Class<T> beanType) {
this.serverConfig = serverConfig;
this.beanType = beanType;
}
/**
* Return the Jackson AnnotatedClass for the given bean type.
*/
Object obtain() {
ObjectMapper objectMapper = (ObjectMapper) serverConfig.getObjectMapper();
JavaType javaType = objectMapper.getTypeFactory().constructType(beanType);
return AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig(), javaType, objectMapper.getDeserializationConfig());
}
}
@@ -1124,4 +1124,18 @@ public class DeployBeanProperty {
public void setElementProperty() {
this.elementProperty = true;
}
/**
* Returns the jackson annotated field, if jackson is present.
*/
public Object /*AnnotatedField*/ getJacksonField() {
com.fasterxml.jackson.databind.introspect.AnnotatedClass jac =
(com.fasterxml.jackson.databind.introspect.AnnotatedClass) getDesc().getJacksonAnnotatedClass();
for (com.fasterxml.jackson.databind.introspect.AnnotatedField candidate : jac.fields()) {
if (candidate.getName().equals(getName())) {
return candidate;
}
}
return null;
}
}
@@ -6,6 +6,7 @@ import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.PrivateOwned;
import io.ebean.annotation.Where;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebean.config.TableName;
import io.ebean.util.CamelCaseHelper;
@@ -430,7 +431,7 @@ class AnnotationAssocManys extends AnnotationParser {
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? See https://ebean.io/docs/trouble-shooting#not-registered";
}
private void readToMany(ManyToMany propAnn, DeployBeanPropertyAssocMany<?> manyProp) {
@@ -451,8 +452,7 @@ class AnnotationAssocManys extends AnnotationParser {
// find the other many table (not intersection)
BeanTable assoc = factory.getBeanTable(targetType);
if (assoc == null) {
String msg = errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName());
throw new RuntimeException(msg);
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName()));
}
manyProp.setManyToMany();
@@ -478,8 +478,7 @@ class AnnotationAssocManys extends AnnotationParser {
BeanTable assoc = factory.getBeanTable(targetType);
if (assoc == null) {
String msg = errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName());
throw new RuntimeException(msg);
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName()));
}
manyProp.setBeanTable(assoc);
@@ -4,6 +4,7 @@ import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.TenantId;
import io.ebean.annotation.Where;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanTable;
@@ -132,6 +133,9 @@ public class AnnotationAssocOnes extends AnnotationParser {
// check for manually defined joins
BeanTable beanTable = prop.getBeanTable();
for (JoinColumn joinColumn : getAll(prop, JoinColumn.class)) {
if (beanTable == null) {
throw new IllegalStateException("Looks like a missing @ManyToOne or @OneToOne on property " + prop.getFullBeanName()+" - no related 'BeanTable'");
}
prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable);
if (!joinColumn.updatable()) {
prop.setDbUpdateable(false);
@@ -146,6 +150,9 @@ public class AnnotationAssocOnes extends AnnotationParser {
JoinTable joinTable = get(prop, JoinTable.class);
if (joinTable != null) {
for (JoinColumn joinColumn : joinTable.joinColumns()) {
if (beanTable == null) {
throw new IllegalStateException("Looks like a missing @ManyToOne or @OneToOne on property " + prop.getFullBeanName()+" - no related 'BeanTable'");
}
prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable);
if (!joinColumn.updatable()) {
prop.setDbUpdateable(false);
@@ -188,13 +195,13 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation? See https://ebean.io/docs/trouble-shooting#not-registered";
}
private BeanTable beanTable(DeployBeanPropertyAssoc<?> prop) {
BeanTable assoc = factory.getBeanTable(prop.getPropertyType());
if (assoc == null) {
throw new RuntimeException(errorMsgMissingBeanTable(prop.getPropertyType(), prop.getFullBeanName()));
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(prop.getPropertyType(), prop.getFullBeanName()));
}
return assoc;
}
@@ -223,6 +230,8 @@ public class AnnotationAssocOnes extends AnnotationParser {
if (!"".equals(propAnn.mappedBy())) {
prop.setOneToOneExported();
prop.setOrphanRemoval(propAnn.orphanRemoval());
} else if (propAnn.orphanRemoval()) {
prop.setOrphanRemoval(true);
}
setCascadeTypes(propAnn.cascade(), prop.getCascadeInfo());
@@ -82,12 +82,12 @@ public abstract class AnnotationBase {
T a = null;
Field field = prop.getField();
if (field != null) {
a = AnnotationUtil.findAnnotation(field, annClass);
a = AnnotationUtil.findAnnotation(field, annClass, platform);
}
if (a == null) {
Method method = prop.getReadMethod();
if (method != null) {
a = AnnotationUtil.findAnnotation(method, annClass);
a = AnnotationUtil.findAnnotation(method, annClass, platform);
}
}
return a;
@@ -239,10 +239,9 @@ public class DeployUtil {
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
Class<?> type = prop.getPropertyType();
ScalarType<?> scalarType = typeManager.getJsonScalarType(type, dbType, dbLength, prop.getGenericType());
ScalarType<?> scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength);
if (scalarType == null) {
throw new RuntimeException("No ScalarType for JSON type [" + type + "] [" + dbType + "]");
throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]");
}
prop.setDbType(dbType);
prop.setScalarType(scalarType);
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.el;
/**
* Comparator with no operation for unsortable properties.
*
* @author Roland Praml, FOCONIS AG
*
*/
public class ElComparatorNoop<T> implements ElComparator<T> {
private static final long serialVersionUID = -9060871822183687180L;
@Override
public int compare(T o1, T o2) {
return 0;
}
@Override
public int compareValue(Object value, T o2) {
return 0;
}
}

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