Compare commits

...
Author SHA1 Message Date
rob bygrave fcaf11f262 [maven-release-plugin] prepare release ebean-11.28.2 2018-12-03 22:14:46 +13:00
rob bygrave 05df47c0d7 #1569 - Ebean generates wrong ddl (datetime(6) vs timestamp) for H2 with @History annotation 2018-12-03 22:11:47 +13:00
rob bygrave a8892292fb #1569 - Ebean generates wrong ddl (datetime(6) vs timestamp) for H2 with @History annotation 2018-12-03 22:05:18 +13:00
rob bygrave e876ef8d87 #1559 - @Lob with @ElementCollection 2018-12-03 21:07:33 +13:00
rob bygrave 315b39813b #1568 - @OneToMany with orphanRemoval=true ... with @Cache (and not a @ManyToOne) doesn't remove orphans after cache hit 2018-11-30 23:46:11 +13:00
rob bygrave 6d5e0c4766 Add test only - Add test for L2 cache with orphanRemoval 2018-11-30 23:41:23 +13:00
rob bygrave a2ce17aec0 Add test only - Add test for L2 cache with orphanRemoval 2018-11-30 22:42:36 +13:00
rob bygrave fcffa92c55 #1567 - SqlUpdate addBatch() ... with named parameters errors with PersistenceException: No batched statement found for key 2018-11-30 21:47:23 +13:00
rob bygrave 7fda8079c2 [maven-release-plugin] prepare for next development iteration 2018-11-30 09:55:20 +13:00
rob bygrave cca9bb4427 [maven-release-plugin] prepare release ebean-11.28.1 2018-11-30 09:55:06 +13:00
rob bygrave f45089240a #1565 - Bump to ebean-datasource 4.3.1 ... with option for owner database credentials and InitDatabase 2018-11-30 09:44:07 +13:00
rob bygrave 8252a51545 #1565 - Bump to ebean-datasource 4.3.1 ... with option for owner database credentials and InitDatabase 2018-11-29 10:16:48 +13:00
rob bygrave c440c768cf #1564 - Remove deprecated io.ebeaninternal.server.core.ServletContextListener - migrate to io.ebean.event.ServletContextListener 2018-11-29 10:01:56 +13:00
rob bygrave c6ae501354 #1563 - Remove deprecated noop method ServerConfig.loadTestProperties() - migrate to loadProperties() 2018-11-29 09:59:16 +13:00
rob bygrave 70e2b1c2ae #1562 - Query cache use incorrect with RawExpression - incorrect bind hash 2018-11-29 09:54:24 +13:00
rob bygrave 211f28eea3 [maven-release-plugin] prepare for next development iteration 2018-11-27 14:58:40 +13:00
rob bygrave 174fe46485 [maven-release-plugin] prepare release ebean-11.27.1 2018-11-27 14:58:26 +13:00
rob bygrave 890cc6f66d Bump to 11.27.1-SNAPSHOT 2018-11-27 14:53:06 +13:00
rob bygrave 2f0e5e6e46 #1561 - ENH: Add ScriptRunner API ... for easier running of SQL and DDL scripts (for testing usually) 2018-11-27 14:50:48 +13:00
rob bygrave 54ba78218b #1558 - Bump ebean-migration to 11.11.1 - refer: https://github.com/ebean-orm/ebean-migration/pull/56 2018-11-23 14:36:41 +13:00
rob bygrave ba16cea405 #1556 - Query.setDisableLazyLoading(true) not working on @OneToMany Set (but does on List) 2018-11-23 12:20:18 +13:00
rob bygrave e81a61378c No effective change - javadoc 2018-11-23 11:37:38 +13:00
rob bygrave b4154031cc #1555 - Support .yaml extension (in addition to the existing .yml extension) for application.yaml and application-test.yaml 2018-11-20 11:51:27 +13:00
rob bygrave ecf5b56652 #1555 - Support .yaml extension (in addition to the existing .yml extension) for application.yaml and application-test.yaml 2018-11-20 11:45:45 +13:00
rob bygrave 426c6c8111 #1554 - Improve exception message for BeanNotEnhancedException 2018-11-20 11:33:16 +13:00
rob bygrave 9dbb69f4f4 #1553 - Remove deprecated method Query.setForUpdate(boolean) ... migrate to setForUpdate() 2018-11-19 23:40:00 +13:00
rob bygrave 83776a1eae #1552 - Remove deprecated methods on Transaction setBatch(PersistBatch) .. migrate to setBatchMode(boolean) 2018-11-19 23:36:15 +13:00
rob bygrave 6bc72ae6bb No effective change - format only 2018-11-19 22:25:01 +13:00
Roland PramlandRob Bygrave 4fe2016891 new comparison method Ine (caseInsensitive notEquals) (#1551) 2018-11-19 22:22:46 +13:00
Roland PramlandRob Bygrave 7be4675d3e NEW: ExpressionPath provides access to property and elName (#1550) 2018-11-19 22:16:01 +13:00
rob bygrave 06b71781f5 #1548 add global configuration disableLazyLoading
Add javadoc
2018-11-19 22:08:50 +13:00
rob bygrave 55e4c7bc26 #1548 add global configuration disableLazyLoading
Fix tests
2018-11-19 22:03:51 +13:00
ooknightandRob Bygrave b3584a81b4 add global configuration disableLazyLoading (#1548) 2018-11-19 21:45:05 +13:00
rob bygrave 68da8944ed [maven-release-plugin] prepare for next development iteration 2018-11-14 09:34:02 +13:00
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
rob bygrave 046a9b30b0 [maven-release-plugin] prepare release ebean-11.20.2 2018-08-10 15:54:29 +12:00
Rob BygraveandGitHub afb7679302 #1468 - ENH: Add built-in DDL generation for Postgres table partitioning (by day, week, month and year) (#1469) 2018-08-10 14:52:42 +12:00
rob bygrave db35f0f5ed #1466 - With ebean.dbSchema property (aka default db schema) we should not connection.setSchema() when running migrations 2018-08-09 21:34:23 +12:00
rob bygrave 19756421ad [maven-release-plugin] prepare for next development iteration 2018-08-08 23:34:11 +12:00
rob bygrave 1d678576f7 [maven-release-plugin] prepare release ebean-11.20.1 2018-08-08 23:33:57 +12:00
Rob BygraveandGitHub ded7d44974 Add support for Postgres range partition DDL plus refactor xsd definitions that had got out of sync (#1465)
* Add partition meta mapping

* Fix to allow schema prefix on select dynamic formula

* Add partition meta mapping

* Migration XSD Update

* Fix to allow schema prefix on select dynamic formula

* Bump ebean-migration to 11.8.1
2018-08-08 23:29:29 +12:00
rob bygrave c9497fd654 [maven-release-plugin] prepare for next development iteration 2018-08-08 12:06:03 +12:00
rob bygrave 23a9cf9bb3 [maven-release-plugin] prepare release ebean-11.19.3 2018-08-08 12:05:50 +12:00
rob bygrave 75395a8481 #1464 - NPE in PropertiesWrapper.internalGet(PropertiesWrapper.java:74) running multi-platform DB migration generation with ServerConfig that has no properties
Note that newPlatformConfig() is only used for multi-platform DB migration generation.
2018-08-07 11:55:06 +12:00
rob bygrave a161d0c01b #1463 - NPE in MigrationModel.readMigrations() when running using File resources with no migration directory 2018-08-07 11:36:26 +12:00
rob bygrave 1dd7720605 #1462 - DB Migration generation with Postgis types gives us non-strict geometry column DDL 2018-08-07 11:19:09 +12:00
rob bygrave 3d2f885e87 [maven-release-plugin] prepare for next development iteration 2018-08-06 21:42:56 +12:00
rob bygrave 2b3e485beb [maven-release-plugin] prepare release ebean-11.19.2 2018-08-06 21:42:41 +12:00
rob bygrave d4be0718a8 #1460 - ENH: Add FetchGroup ... as a mechanism to specify select() + fetch() separately from Query
Change to add generics for FetchGroup
2018-08-06 21:30:36 +12:00
rob bygrave 6d0556a120 #1460 - ENH: Add FetchGroup ... as a mechanism to specify select() + fetch() separately from Query
Change to add generics for FetchGroup
2018-08-02 23:18:44 +12:00
rob bygrave ec7e4e4935 #1461 - @OneToMany with @JoinTable without table name does not default join table name 2018-08-02 22:25:48 +12:00
rob bygrave 471605619a #1460 - ENH: Add FetchGroup ... as a mechanism to specify select() + fetch() separately from Query 2018-07-30 23:51:52 +12:00
rob bygrave 02fe956056 #1459 - findNative with join to a @OneToMany ... does not map properly 2018-07-27 12:31:56 +12:00
rob bygrave db13034fec No effective change - update code style in test 2018-07-27 12:31:27 +12:00
rob bygrave c2489beb82 [maven-release-plugin] prepare for next development iteration 2018-07-18 22:25:00 +12:00
rob bygrave 9a424d7d6d [maven-release-plugin] prepare release ebean-11.19.1 2018-07-18 22:24:45 +12:00
rob bygrave 9b74602617 Bump pom to 11.19.1-SNAPSHOT 2018-07-18 22:22:48 +12:00
Roland PramlandRob Bygrave 5c99986476 Migration with procedures (#1451)
DB Migration with helper stored procedures (for MySql and SQL Server)
2018-07-18 22:21:47 +12:00
rob bygrave 7d38933e9d Refactor remove dead code - BindableRequest registerGeneratedVersion()
No effective change
2018-07-18 22:16:15 +12:00
rob bygrave 7668fb11a9 [maven-release-plugin] prepare for next development iteration 2018-07-18 22:05:02 +12:00
rob bygrave eac68166d9 [maven-release-plugin] prepare release ebean-11.18.6 2018-07-18 22:04:48 +12:00
rob bygrave cb0023345f Refactor scalar types for Varchar, LongVarchar and Clob - ScalarTypeStringBase 2018-07-18 21:59:11 +12:00
rob bygrave f0f7feb125 No effective change - code format 2018-07-18 21:49:19 +12:00
Roland PramlandRob Bygrave c8e32253a5 FIX: support for ScalarTypeConverter and @Lob (#1454) 2018-07-18 21:41:46 +12:00
rob bygrave 01541bb208 #1457 - Issue with @ManyToOne with @EmbeddedId where only some columns are insertable or updatable 2018-07-18 21:29:08 +12:00
rob bygrave 2d44d06f36 #1456 - io.ebean.annotation.NotNull not honored by @ManyToOne property 2018-07-18 19:36:41 +12:00
rob bygrave fda930a162 No effective change - fix javadoc title to Ebean 11 2018-07-18 17:01:14 +12:00
Roland PramlandRob Bygrave 5a6cdb9a86 FIX: null handling in ScalarType.toBeanType (#1455) 2018-07-18 16:33:36 +12:00
rob bygrave 5ef7fe8504 [maven-release-plugin] prepare for next development iteration 2018-07-15 16:09:15 +12:00
rob bygrave 11586fb635 [maven-release-plugin] prepare release ebean-11.18.5 2018-07-15 16:09:00 +12:00
rob bygrave d555122067 #1453 - @Formula property included in DDL when on @Inheritance bean and without @Transient 2018-07-15 15:36:29 +12:00
rob bygrave b6d3dc3516 Fix test code only - Tune entity bean needs at least 1 non Id property 2018-07-15 15:01:17 +12:00
rob bygrave bbf54d6196 git push origin masterMerge branch 'FOCONIS-cache-bug-delete' 2018-07-15 14:13:48 +12:00
rob bygrave 74c88ce511 #1452 Fix - Deleting a bean does not invalidate the bean cache (Regression from 11.18.1 - introduced by changes in #1427) 2018-07-15 14:13:29 +12:00
rob bygrave 309bf6a11a Merge branch 'cache-bug-delete' of https://github.com/FOCONIS/ebean into FOCONIS-cache-bug-delete 2018-07-15 13:56:37 +12:00
rob bygrave 02b6ab0f74 No effective change, move null check on orderBy to prior to parse 2018-07-15 13:55:51 +12:00
rob bygrave 0c41993ce7 No effective change, whitespace 2018-07-15 13:55:15 +12:00
rob bygrave d949c48245 git push origin masterMerge branch 'tobias--bugfix/lazy_fetch_with_id_column_name' 2018-07-15 13:52:14 +12:00
rob bygrave 93e603ca25 #1449 Fix for failing lazy fetch when logical property name starts with underscore 2018-07-15 13:49:05 +12:00
rob bygrave 117d22fd67 Merge branch 'bugfix/lazy_fetch_with_id_column_name' of https://github.com/tobias-/ebean into tobias--bugfix/lazy_fetch_with_id_column_name 2018-07-15 13:11:33 +12:00
Roland Praml f13de2c8a6 ADD: Testcase for delete with cache 2018-07-13 19:57:56 +02:00
Mitsch164andRob Bygrave 5b8ef8154a Fix Transaction Flushing when ID Property is Loaded (#1450)
* Fix for unintentianal flush when ID is loaded

* fix duplicateKey Exception

* higher id
2018-07-12 15:10:17 +12:00
Tobias df892f4509 Failing testcase with lazy fetch
For some reason, all entities with an id starting with _ fails when
lazy-fetching due to sort not being properly renamed.
2018-07-11 15:52:56 +02:00
rob bygrave 2755e6d1ec [maven-release-plugin] prepare for next development iteration 2018-07-05 23:40:12 +12:00
rob bygrave 2024193da9 [maven-release-plugin] prepare release ebean-11.18.4 2018-07-05 23:39:56 +12:00
rob bygrave 72788c7428 No effective change - tidy test code 2018-07-05 22:41:28 +12:00
rob bygrave 8789709fb8 No effective change - tidy test code 2018-07-05 22:40:09 +12:00
rob bygrave 588aaf9361 #1447 - Delete query ... should honour cascading delete if defined
See also PR #1445 with the failing test case
2018-07-05 22:37:54 +12:00
Michael BenzandRob Bygrave 68649d0e98 Failing unit test to demonstrate the bug that deleting an entity via … (#1445)
* Failing unit test to demonstrate the bug that deleting an entity via an Ebean query does not respect the cascade setting.
(here a mapped optional OneToOne entity is not deleted using an Ebean query even if the CascadeType.ALL was used)

* moved init and cleanup code to @Before and @After JUnit methods and added another test that shows Ebean.delete() works as expected
2018-07-05 19:52:40 +12:00
rob bygrave f6047d3b73 [maven-release-plugin] prepare for next development iteration 2018-07-04 22:09:27 +12:00
rob bygrave 629e096e28 [maven-release-plugin] prepare release ebean-11.18.3 2018-07-04 22:09:12 +12:00
rob bygrave c981944c8e #1444 - JSON parsing with inheritance - java.lang.UnsupportedOperationException: cannot create entity bean for abstract entity ... 2018-07-04 21:26:17 +12:00
rob bygrave 1ba9f4e9de No effective change - Add simple profile query main 2018-07-04 21:25:36 +12:00
rob bygrave ce02e7ce70 No effective change - tidy tests 2018-07-04 21:25:10 +12:00
rob bygrave 1d9a2ed43a No effective change - tidy DJsonContext 2018-07-02 21:33:59 +12:00
rob bygrave ae81ce5f47 #1443 - Bump ebean-migration to 11.7.1 (which adds JdbcMigration support and "I" run first repeatable migrations) 2018-07-01 20:30:54 +12:00
rob bygrave 9e42f98902 [maven-release-plugin] prepare for next development iteration 2018-06-28 10:27:06 +12:00
rob bygrave ada21729ec [maven-release-plugin] prepare release ebean-11.18.2 2018-06-28 10:26:53 +12:00
rob bygrave 8b7d882b8a #1442 - Add support to set ebean.dbSchema ... (~ default DB schema) with create-all.sql and DB migrations (so it all just works) 2018-06-28 10:23:34 +12:00
Michael BenzandRob Bygrave cf2d9df256 Migration enum drop check constraints (#1435)
* Fix: apply drop checkConstraints properly which were not applied for Enum to String

* Use IF EXISTS to drop constraints

* added TODO and comment about MariaDB supporting CHECK constraints from version 10.2.1 forward compared to MySQL 5.7/8.0 parsing but ignoring CHECK constraints

* updated reference scripts for dropping constraints on enums
2018-06-27 10:38:56 +12:00
rob bygrave 48b05eb358 No effective change - modify logback-test.xml to put the test example logging files into target directory 2018-06-27 10:37:00 +12:00
rob bygrave 0796fc0d88 [maven-release-plugin] prepare for next development iteration 2018-06-27 00:30:37 +12:00
rob bygrave df585644e9 [maven-release-plugin] prepare release ebean-11.18.1 2018-06-27 00:30:23 +12:00
rob bygrave ff3c5b3d82 #1441 - Update ebean-dbmigration.xsd adding missing alterForeignKey element 2018-06-27 00:23:36 +12:00
rob bygrave 1a03e84f0e #1439 - IllegalArgumentException: No rule for io.ebeaninternal.dbmigration.migration.Sql@315df4bb 2018-06-26 22:40:08 +12:00
rob bygrave 49ab17cd34 #1438 - Mapping of @ElementCollection with an Enum that is otherwise not used produces error 2018-06-26 22:11:40 +12:00
rob bygrave 9cd189034c #1437 - or(expr, expr) ... where expr has a ToMany should produce a LEFT JOIN (and instead we only get JOIN) 2018-06-26 21:33:44 +12:00
rob bygrave ed566ca153 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Refactor on PostCommitProcessing with fixes for dependent table invalidation
2018-06-26 00:25:33 +12:00
rob bygrave 9159cec36c No effective change - remove invalid javadoc on Query due to #1434 2018-06-19 16:35:42 +12:00
rob bygrave b58fc2df04 #1436 - PersistenceException: Query threw SQLException:No ScalarType registered for class java.util.Arrays$ArrayList Query was:select id, name from o_customer where id = any(?)
Add test for SqlUpdate
2018-06-19 16:30:21 +12:00
rob bygrave dd8607b072 #1436 - PersistenceException: Query threw SQLException:No ScalarType registered for class java.util.Arrays$ArrayList Query was:select id, name from o_customer where id = any(?) 2018-06-19 15:30:19 +12:00
rob bygrave a5a8765bd0 Update tests only - non native boolean (MySql) and ignore non-named savepoint nested transaction tests with MySql. 2018-06-19 15:24:35 +12:00
rob bygrave ae55b58438 No effective change - remove unused imports 2018-06-19 01:48:45 +12:00
rob bygrave 671d082ad3 #1427 - Map all deleteByIds into a single CacheChangeBeanRemove and use bulk removeAll api to remove from cache 2018-06-19 01:34:28 +12:00
rob bygrave 43e95d81f6 #1427 - RemoteTransactionEvent process dependent tables 2018-06-19 00:50:14 +12:00
rob bygrave 3a64cf6952 #1434 - Remove deprecated API from EbeanServer - finder methods that take explicit transaction. Migrate to use ebeanServer.extended() 2018-06-18 21:34:51 +12:00
rob bygrave 8d976f83a9 #1417 - Breaking API - Remove PersistBatch.INSERT ... migrate to PersistBatch.ALL 2018-06-18 21:15:10 +12:00
rob bygrave 117737c790 #1433 - Remove io.ebeaninternal.api.SpiContainerBootup ... which was replaced by io.ebean.config.AutoConfigure 2018-06-18 20:02:14 +12:00
rob bygrave 085843339f #1432 - Refactor internals for RemoteTransactionEvent / BeanPersistIds ... to not send inserted ids 2018-06-17 23:34:27 +12:00
rob bygrave bf33b3f96f #1431 - Refactor internals for RemoteTransactionEvent binary serialisation 2018-06-17 23:29:11 +12:00
rob bygrave e1d4159453 #1431 - Refactor internals for RemoteTransactionEvent binary serialisation 2018-06-17 22:25:45 +12:00
rob bygrave 4f1f3cadf9 Merge branch 'feature/query-cache-1427' 2018-06-16 16:02:35 +12:00
rob bygrave 01e1a4642b [maven-release-plugin] prepare for next development iteration 2018-06-16 15:26:11 +12:00
rob bygrave 3a75e1f66c [maven-release-plugin] prepare release ebean-11.17.5 2018-06-16 15:25:56 +12:00
rob bygrave 47ac4c5c5b #1430 - Regression (from 11.10.1) - DataSource user not set ... due to PrimaryServer System.setProperty("ebean_db", defaultServerName); 2018-06-16 15:23:41 +12:00
rob bygrave 9c73309dfd #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Update RemoteTransactionEvent, add (back) common binary message handling (Common to TCP, K8s and upcoming binary pubsub)
2018-06-16 15:15:54 +12:00
rob bygrave bd3c99e67f #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Wrap Clock as ClockService to use in TransactionManager and DefaultServer (and allow for changing the Clock implementation for testing).
2018-06-16 00:22:44 +12:00
rob bygrave 5adbebaf6e Merge branch 'master' into feature/query-cache-1427 2018-06-16 00:09:25 +12:00
rob bygrave 6a9ab49ef0 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Propagation across cluster.
2018-06-16 00:09:03 +12:00
rob bygrave 4308a98a0a #1428 - ENH: Use java.time.Clock for @WhenModified and @WhenCreated and allow it to be Replaceable for testing
Adjust such that Clock is on DefaultServer and can be set via ExtendedServer interface
2018-06-15 20:49:14 +12:00
tobias-andRob Bygrave f86f1b2647 Replaceable clock (#1428)
ENH: Use java.time.Clock for @WhenModified and @WhenCreated and allow it to be Replaceable for testing
2018-06-15 20:34:11 +12:00
rob bygrave 96cc8d7605 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Initial work, does not include propagation across cluster.
2018-06-15 17:36:23 +12:00
rob bygrave 59537227b6 #1426 - ENH: Add bitwiseNot expression ... e.g. bitwiseNot("flags", BwFlags.HAS_COLOUR) 2018-06-14 23:28:30 +12:00
rob bygrave 81c6d4601d #1425 - Stateless update with OneToMany with SoftDelete and deleteMissingChildren = true ... does a hard delete 2018-06-14 16:09:45 +12:00
rob bygrave e21839de91 #1424 - Deprecate / Move ... finder methods that take explicit transaction to ExtendedServer API 2018-06-14 11:03:29 +12:00
rob bygrave a61db2ab35 [maven-release-plugin] prepare for next development iteration 2018-06-14 00:18:14 +12:00
rob bygrave bddfa3bc5f [maven-release-plugin] prepare release ebean-11.17.4 2018-06-14 00:18:00 +12:00
Roland PramlandRob Bygrave 9ad460ecd8 FIX: applied history drops were not recognized (#1422) 2018-06-14 00:07:14 +12:00
rob bygrave 50593a7f9d #1423 - Close resources - InputStream of entity bean properties of File type 2018-06-14 00:04:43 +12:00
rob bygrave c088cfa339 #1421 - Nested programatic transaction using auto close / try with resources ... does not end/rollback the parent/outer transaction 2018-06-13 22:22:29 +12:00
rob bygrave e683e25a95 #1420 - ENH: Add a ServiceLoader mechanism to configure ServerConfig - io.ebean.config.ServerConfigProvider 2018-06-13 21:35:26 +12:00
rob bygrave c7052c5bfa #1420 - ENH: Add a ServiceLoader mechanism to configure ServerConfig - io.ebean.config.ServerConfigProvider 2018-06-13 21:30:41 +12:00
rob bygrave 698ee2198d No effective change - refactor DefaultContainer from iterator to forEach 2018-06-12 22:14:33 +12:00
rob bygrave 91c4eca528 #1419 - Refactor - saveAll() and deleteAll() ... when collections are empty skip transaction init check 2018-06-12 22:08:17 +12:00
rob bygrave 81897bd54d #1418 - Add saveAll() and deleteAll() to io.ebean.BeanRepository 2018-06-12 21:40:39 +12:00
rob bygrave 2427630c07 #1400 - Postgres @History table sys_period empty with multiple updates 2018-06-11 22:10:43 +12:00
rob bygrave 3238c0def4 [maven-release-plugin] prepare for next development iteration 2018-06-11 14:27:53 +12:00
rob bygrave 93f75151f0 [maven-release-plugin] prepare release ebean-11.17.3 2018-06-11 14:27:40 +12:00
rob bygrave 32e9b48359 #1414 - Support dynamic aggregate formula without alias e.g. query.select("date, sum(totalKms), sum(hours)") 2018-06-11 14:13:13 +12:00
rob bygrave 258e29d2f0 #1415 - Add asDto(Class<D> dtoClass) ... to ExpressionList to support fluid style 2018-06-11 13:29:13 +12:00
rob bygrave ec2f1f7243 #1414 - Support dynamic aggregate formula without alias e.g. query.select("date, sum(totalKms), sum(hours)") 2018-06-11 13:20:39 +12:00
Rob Bygrave 5775985468 [maven-release-plugin] prepare for next development iteration 2018-06-11 00:12:45 +12:00
Rob Bygrave 4788dd4ec9 [maven-release-plugin] prepare release ebean-11.17.2 2018-06-11 00:12:30 +12:00
Rob Bygrave ca7e5b79b8 #1412 - Fix Resource leaks for properties & yaml files etc - ManifestReader
Not fixing a leak but just changing to use try with resources
2018-06-11 00:04:58 +12:00
Rob Bygrave 90dfbcd7b3 #1412 - Fix Resource leaks for properties & yaml files etc - DefaultContainer.checkDataSource()
The resource was already being closed properly, changing to use try with resources and note that this now throws PersistenceException if we fail to close() on the connection (which I think is an ok change).
2018-06-11 00:02:14 +12:00
Rob Bygrave 6b39bad4da No effective change - use JdbcClose for DatabasePlatformFactory 2018-06-10 23:55:45 +12:00
Rob Bygrave 25dea0b5b0 #1412 - Fix Resource leaks for properties & yaml files etc - DefaultAutoTuneService 2018-06-10 23:48:49 +12:00
Rob Bygrave 0069d2fabc #1412 - Fix Resource leaks for properties & yaml files etc - ExtraDdlXmlReader 2018-06-10 23:46:55 +12:00
Rob Bygrave 98964638ba #1412 - Fix Resource leaks for properties & yaml files etc - MigrationXmlReader 2018-06-10 23:43:41 +12:00
Rob Bygrave 4573a995f1 #1412 - Fix Resource leaks for properties & yaml files etc
No effective change - reformat only
2018-06-10 23:37:28 +12:00
Rob Bygrave 4bc80cc724 #1412 - Fix Resource leaks for properties & yaml files etc - EbeanVersion 2018-06-10 23:36:12 +12:00
Rob Bygrave a2972abe3e #1412 - Fix Resource leaks for properties & yaml files etc - DdlGenerator 2018-06-10 23:33:21 +12:00
Rob Bygrave 54eaf561f4 #1412 - Fix Resource leaks for properties & yaml files etc - Loader 2018-06-10 23:31:16 +12:00
Rob Bygrave 3134dd3165 No effective change - reformat only 2018-06-10 23:23:48 +12:00
Roland PramlandRob Bygrave 8fc6abd480 IOUtils is no longer used, using java 1.7 StandardCharsets makes EncodeUtil also obsolete (#1410) 2018-06-10 22:30:47 +12:00
rob bygrave f031bb72b6 #1408 - Incorrect SQL when @Aggregation formula matches the property - sum(t0.sum(totalKms)) 2018-06-08 17:38:15 +12:00
rob bygrave 42f0b419e8 #1405 - ENH: add findSingleDecimal() and findSingleLong() alias methods for SqlQuery.findSingleAttribute() ... 2018-06-08 02:19:36 +12:00
rob bygrave 7cbae758fc #1407 - ENH: Add SqlQuery RowMapper and RowConsumer ... for raw JDBC ResultSet mapping and consuming 2018-06-08 02:04:38 +12:00
rob bygrave fb32cfa170 #1406 - ENH: Add SqlQuery.findSingleAttributeList() ... for returning lists of scalar values via SqlQuery 2018-06-07 22:39:19 +12:00
rob bygrave ffa8268125 #1405 - ENH: Add SqlQuery.findSingleAttribute() ... for returning single scalar values via SqlQuery 2018-06-07 22:17:01 +12:00
rob bygrave eba1003c19 #1404 - Query.findCount() with query.setDistinct(true) ... doesn't give count distinct SQL query 2018-06-07 00:32:39 +12:00
rob bygrave eca6383dba [maven-release-plugin] prepare for next development iteration 2018-06-01 15:44:03 +12:00
rob bygrave 624d08f46c [maven-release-plugin] prepare release ebean-11.17.1 2018-06-01 15:43:50 +12:00
rob bygrave 14749daf9b Bump to 11.17.1-SNAPSHOT ... as we added a SPI for io.ebeaninternal.api.SpiLogManager (for capturing the io.ebean.SQL log for testing purposes) 2018-06-01 15:42:24 +12:00
rob bygrave 2817875706 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:15:05 +12:00
rob bygrave 3f8d705439 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:10:00 +12:00
rob bygrave e9faef2692 #1401 - FOR UPDATE queries inside a transaction are not honored when beans are already cached (in the persistence context) 2018-06-01 11:33:41 +12:00
rob bygrave d459807af7 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 09:19:26 +12:00
ooknightandRob Bygrave 10ad9ef096 beautify bindNoLog format (#1399) 2018-05-31 20:23:52 +12:00
rob bygrave 11e3649b85 #1398 - Support EQ / "Equal to" predicate on client side encrypted entity property - @Encrypted(dbEncryption = false) 2018-05-31 16:02:49 +12:00
rob bygrave ddc00fedb6 #1397 - @DbForeignKey(noConstraint = true) is ignored on @ManyToMany ... foreign constraints still added 2018-05-31 10:55:58 +12:00
rob bygrave 0aa9208dcc [maven-release-plugin] prepare for next development iteration 2018-05-30 03:36:38 +12:00
rob bygrave e3b1996dc9 [maven-release-plugin] prepare release ebean-11.16.1 2018-05-30 03:36:24 +12:00
rob bygrave 24ca744e06 Bump version to 11.16.1-SNAPSHOT 2018-05-30 03:35:13 +12:00
rob bygrave 7f9ce5bdd4 #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:38 +12:00
rob bygrave 23e5de0819 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement 2018-05-30 03:32:23 +12:00
rob bygrave c23508819f #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:00 +12:00
rob bygrave af030e3da4 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement
ENH: Add merge() API (using default merge options) to EbeanServer, Ebean and BeanRepository enhancement
2018-05-30 03:31:32 +12:00
rob bygrave d8e67d09a9 #1393 - MergeOptionsBuilder build() return MergeOptions interface type 2018-05-29 15:30:39 +12:00
rob bygrave 66c08e663e #1392 - MergeHandler, change regex Pattern to be static 2018-05-29 15:25:38 +12:00
rob bygrave 9a096e272e #1392 - MergeOptionsBuilder setClientGeneratedIds() ... should not be required if the Id property does not have Identity/Sequence generator assigned 2018-05-29 15:24:32 +12:00
rob bygrave 21e87e12cb Adjust test for #1391 ... such that OrganizationTreeNode has at least one db column as part of insert (for PG) 2018-05-29 13:38:17 +12:00
rob bygrave 7c6d7dd4d0 Merge branch 'FOCONIS-fix-create-reference-bean' 2018-05-29 13:28:32 +12:00
Roland Praml c5329e9db0 reformatting 2018-05-28 17:05:52 +02:00
chrishartlandRoland Praml bccb9f8ddb Fix/create reference with descriptor using inheritance (#13)
* FIXED: Discriminator must be ask for inherited classes

* Fixed: Compile

* Refactoring and Testing
2018-05-28 16:54:53 +02:00
rob bygrave c57af29fca [maven-release-plugin] prepare for next development iteration 2018-05-29 00:44:36 +12:00
rob bygrave 37b6a321ab [maven-release-plugin] prepare release ebean-11.15.12 2018-05-29 00:44:22 +12:00
rob bygrave 896f7d9ea8 No effective change - improve error message when entity not registered to include entity annotation question 2018-05-29 00:38:41 +12:00
rob bygrave 141a0d9358 #1390 - ENH: Support explicit @Id @GeneratedValue ... mapping (where absence of @GeneratedValue means externally supplied id values) 2018-05-29 00:35:31 +12:00
Roland PramlandRob Bygrave 5a7f24da5d FIX: jsonRead of transient maps (#1389)
FIX: jsonRead of transient maps - to skip the read of start_array - Avoid error "Unexpected token start_object - expecting start_array "
2018-05-28 23:06:45 +12:00
rob bygrave 8ea86d6c6d [maven-release-plugin] prepare for next development iteration 2018-05-27 19:19:52 +12:00
rob bygrave fb5eeb60b1 [maven-release-plugin] prepare release ebean-11.15.11 2018-05-27 19:19:37 +12:00
rob bygrave 33ec8abb9d No effective change - update test remove unused exception 2018-05-27 17:38:33 +12:00
rob bygrave bbd7baf1be #1388 - AutoTune - tuned query includes "null" as fetched property 2018-05-27 17:33:26 +12:00
rob bygrave 2e36402a76 #1387 - Revert #1374 and expose io.ebean.event.ServletContextListener (as public API) 2018-05-27 15:54:32 +12:00
rob bygrave fa4af4ef98 Revert "#1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused"
This reverts commit 95737cd3c5.
2018-05-27 15:41:51 +12:00
rob bygrave ed6a9e2dbb #1386 - ENH: For DbMigration generation - add support for setting version, name, generatePendingDrop, applyPrefix, ddl header 2018-05-27 15:37:45 +12:00
rob bygrave 42d83705b7 #1384 - Change MigrationXmlWriter to not write <!DOCTYPE xml> (as it is not needed/expected now) 2018-05-24 00:15:25 +12:00
rob bygrave ae58d9223a No effective change - add test for PrimaryKeyJoinColumn - insertWithoutExtra 2018-05-24 00:07:30 +12:00
rob bygrave a9a89938f9 #1368 - Remove unused resource - default-create-table.sql ... which is in ebean-migration 2018-05-24 00:06:18 +12:00
rob bygrave 4b91f5fe0b #1380 - Make javax.xml an optional dependency (as it is not part of JDK from 9 onwards) 2018-05-21 01:06:26 +12:00
rob bygrave 85d3caf079 #1367 - Modularized project cannot locate ebean.properties file on Java 10 2018-05-21 00:29:04 +12:00
rob bygrave f6d8c8b618 [maven-release-plugin] prepare for next development iteration 2018-05-21 00:13:59 +12:00
rob bygrave d0fa3f0cb1 [maven-release-plugin] prepare release ebean-11.15.10 2018-05-21 00:13:43 +12:00
Roland PramlandRob Bygrave 41a31c1aa0 trigger based history will recreate view properly and handle default … (#1370)
* trigger based history will recreate view properly and handle default value also for history view

* HistoryTriggers are generated at the end

* Reference Script

* MYSQL: lock all tables at once
2018-05-20 17:19:10 +12:00
Roland PramlandRob Bygrave ab301f970c FIX: correct caching when collection with different inherited beans is put to the cache (#1379)
* FIX: correct cahcing when collection with different inherited beans is put to the cache

* removed some sysouts
2018-05-20 16:53:56 +12:00
rob bygrave d1b7b082a2 [maven-release-plugin] prepare for next development iteration 2018-05-17 12:50:26 +12:00
rob bygrave 5fb3a1e019 [maven-release-plugin] prepare release ebean-11.15.9 2018-05-17 12:50:12 +12:00
rob bygrave cb0dee998b #1375 - Bug: RawSql parsing fails when using placeholders 2018-05-17 12:48:08 +12:00
Roland PramlandRob Bygrave 53b606289d FIX: use LinkedHashMap in enum types to get deterministic ordering (#1378) 2018-05-17 12:26:37 +12:00
rob bygrave 574d5e215b [maven-release-plugin] prepare for next development iteration 2018-05-14 22:19:37 +12:00
rob bygrave 508f2129f5 [maven-release-plugin] prepare release ebean-11.15.8 2018-05-14 22:19:21 +12:00
rob bygrave cdfcbeec43 #1377 - Bump dependency - jsnakeyaml 1.19 to 1.21 2018-05-14 22:17:27 +12:00
rob bygrave 0e9782e6b3 #1376 - Bump dependency - jackson-core from 2.9.0 to 2.9.5 2018-05-14 22:15:58 +12:00
rob bygrave 927fa5f7d3 [maven-release-plugin] prepare for next development iteration 2018-05-14 22:10:34 +12:00
rob bygrave 53a80c7dd1 [maven-release-plugin] prepare release ebean-11.15.7 2018-05-14 22:10:18 +12:00
rob bygrave 89789f3347 #1372 - Missing @OneToOne mapping yields unusable error messages.
Restore mapping.
2018-05-14 21:33:11 +12:00
rob bygrave 9dc5ca602c #1372 - Missing @OneToOne mapping yields unusable error messages. 2018-05-14 21:32:48 +12:00
rob bygrave 95737cd3c5 #1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused 2018-05-14 21:23:13 +12:00
Roland PramlandRob Bygrave 0a49fc9b62 FIX: NPE when fetching inherited beans from cache (#1373) 2018-05-14 21:12:42 +12:00
rob bygrave f8c80c7fd2 [maven-release-plugin] prepare for next development iteration 2018-05-10 17:22:18 +12:00
747 changed files with 24438 additions and 5551 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
+27 -15
View File
@@ -9,15 +9,20 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.6</version>
<version>11.28.2</version>
<packaging>jar</packaging>
<name>ebean</name>
<url>http://ebean-orm.github.io/</url>
<properties>
<jackson-core.version>2.9.5</jackson-core.version>
<snakeyaml.version>1.21</snakeyaml.version>
</properties>
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.15.6</tag>
<tag>ebean-11.28.2</tag>
</scm>
<profiles>
@@ -89,7 +94,7 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
<version>${snakeyaml.version}</version>
</dependency>
<!--
@@ -112,13 +117,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.11</version>
<version>4.3</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>3.2.2</version>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
@@ -130,7 +135,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.5.4</version>
<version>11.11.1</version>
</dependency>
<dependency>
@@ -143,14 +148,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
<version>${jackson-core.version}</version>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
<version>${jackson-core.version}</version>
<scope>provided</scope>
</dependency>
@@ -187,7 +192,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212</version>
<version>42.2.2</version>
<scope>provided</scope>
</dependency>
@@ -224,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>
@@ -233,7 +238,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
<version>1.4.197</version>
<scope>provided</scope>
</dependency>
@@ -266,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>
@@ -297,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>
@@ -355,7 +367,7 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<doctitle>Ebean 10</doctitle>
<doctitle>Ebean 11</doctitle>
<overview>src/main/java/io/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
@@ -2,6 +2,8 @@ package io.ebean;
import io.ebean.bean.EntityBean;
import java.util.Collection;
/**
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
* <p>
@@ -115,6 +117,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
db().save(bean);
}
/**
* Save all the beans in the collection.
*/
public int saveAll(Collection<T> bean) {
return db().saveAll(bean);
}
/**
* Update this entity.
*
@@ -154,6 +163,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
return db().delete(bean);
}
/**
* Delete all the beans in the collection.
*/
public int deleteAll(Collection<T> beans) {
return db().deleteAll(beans);
}
/**
* Delete a bean permanently without soft delete.
* <p>
@@ -167,6 +183,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
return db().deletePermanent(bean);
}
/**
* Merge this entity using the default merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#merge(Object)
*/
public void merge(T bean) {
db().merge(bean);
}
/**
* Merge this entity using the specified merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#merge(Object, MergeOptions)
*/
public void merge(T bean, MergeOptions options) {
db().merge(bean, options);
}
/**
* Refreshes this entity from the database.
*
+20
View File
@@ -2,7 +2,9 @@ package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.BeanNotEnhancedException;
import io.ebean.config.ServerConfig;
import io.ebean.datasource.DataSourceConfigurationException;
import io.ebean.plugin.Property;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
@@ -160,6 +162,15 @@ public final class Ebean {
defaultServer = getWithCreate(defaultName.trim());
}
}
} catch (BeanNotEnhancedException e) {
throw e;
} catch (DataSourceConfigurationException e) {
String msg = "Configuration error creating DataSource for the default EbeanServer." +
" This typically means a missing application-test.yaml or missing ebean-test-config dependency." +
" See https://ebean.io/docs/trouble-shooting#datasource";
throw new DataSourceConfigurationException(msg, e);
} catch (Throwable e) {
logger.error("Error trying to create the default EbeanServer", e);
throw new RuntimeException(e);
@@ -638,6 +649,15 @@ public final class Ebean {
serverMgr.getDefaultServer().updateAll(beans);
}
/**
* Merge the bean using the default merge options.
*
* @param bean The bean to merge
*/
public static void merge(Object bean) {
serverMgr.getDefaultServer().merge(bean);
}
/**
* Merge the bean using the given merge options.
*
+21 -404
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.
@@ -808,411 +804,18 @@ public interface EbeanServer {
<T> T getReference(Class<T> beanType, Object id);
/**
* Return the number of 'top level' or 'root' entities this query should return.
*
* @see Query#findCount()
* @see Query#findFutureCount()
*/
<T> int findCount(Query<T> query, Transaction transaction);
/**
* Return the Id values of the query as a List.
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
* Return a QueryIterator for the query.
* Return the extended API for EbeanServer.
* <p>
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
* to findIterate(). The reason is that those methods automatically take care of
* closing the queryIterator (and the underlying jdbc statement and resultSet).
* The extended API has the options for executing queries that take an explicit
* transaction as an argument.
* </p>
* <p>
* This is similar to findEach in that not all the result beans need to be held
* in memory at the same time and as such is good for processing large queries.
* </p>
*
* @see Query#findIterate()
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Nonnull
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEach((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
* Compared to findEach() this provides the ability to stop processing the query
* results early by returning false for the Predicate.
* </p>
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEachWhile((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
*
* boolean carryOnProcessing = ...
* return carryOnProcessing;
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
/**
* Return versions of a @History entity bean.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
* Typically we only need to use the extended API when we do NOT want to use the
* usual ThreadLocal based mechanism to obtain the current transaction but instead
* supply the transaction explicitly.
* </p>
*/
@Nonnull
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
* Execute a query returning a list of beans.
* <p>
* Generally you are able to use {@link Query#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @see Query#findList()
*/
@Nonnull
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the row count on
* @param transaction the transaction (can be null).
* @return a Future object for the row count query
* @see Query#findFutureCount()
*/
@Nonnull
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the fetch Id's on
* @param transaction the transaction (can be null).
* @return a Future object for the list of Id's
* @see Query#findFutureIds()
*/
@Nonnull
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
* Execute find list query in a background thread returning a FutureList object.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a timeout).
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
*
* @param query the query to execute in the background
* @param transaction the transaction (can be null).
* @return a Future object for the list result of the query
* @see Query#findFutureList()
*/
@Nonnull
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
* The benefit of using this over findList() is that it provides functionality to get the
* total row count etc.
* </p>
* <p>
* If maxRows is not set on the query prior to calling findPagedList() then a
* PersistenceException is thrown.
* </p>
* <p>
* <pre>{@code
*
* PagedList<Order> pagedList = Ebean.find(Order.class)
* .setFirstRow(50)
* .setMaxRows(20)
* .findPagedList();
*
* // fetch the total row count in the background
* pagedList.loadRowCount();
*
* List<Order> orders = pagedList.getList();
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
* Execute the query returning a set of entity beans.
* <p>
* Generally you are able to use {@link Query#findSet()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* Set<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute
* @param transaction the transaction to use (can be null).
* @return the set of fetched beans.
* @see Query#findSet()
*/
@Nonnull
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
* Execute the query returning the entity beans in a Map.
* <p>
* Generally you are able to use {@link Query#findMap()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the map of fetched beans.
* @see Query#findMap()
*/
@Nonnull
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* Execute the query returning a list of values for a single property.
* <p>
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .select("name")
* .orderBy().asc("name")
* .findSingleAttributeList();
*
* }</pre>
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .orderBy().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
* @see Query#findSingleAttributeList()
*/
@Nonnull
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
* <p>
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
* @see Query#findOne()
*/
@Nullable
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* 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>
*
* @param query the query used for the delete
* @param transaction the transaction to use (can be null)
* @param <T> the type of entity bean to fetch.
* @return the number of beans/rows that were deleted
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the update query returning the number of rows updated.
* <p>
* The update query must be created using {@link #update(Class)}.
* </p>
*
* @param query the update query to execute
* @param transaction the optional transaction to use for the update (can be null)
* @param <T> the type of entity bean
* @return The number of rows updated
*/
<T> int update(Query<T> query, Transaction transaction);
/**
* Execute the sql query returning a list of MapBean.
* <p>
* Generally you are able to use {@link SqlQuery#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
@Nonnull
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* Execute the SqlQuery iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
/**
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
/**
* Execute the sql query returning a single MapBean or null.
* <p>
* This will throw a PersistenceException if the query found more than one
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findOne()
*/
@Nullable
SqlRow findOne(SqlQuery query, Transaction transaction);
ExtendedServer extended();
/**
* Either Insert or Update the bean depending on its state.
@@ -1628,6 +1231,13 @@ public interface EbeanServer {
*/
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* Merge the bean using the default merge options (no paths specified, default delete).
*
* @param bean The bean to merge
*/
void merge(Object bean);
/**
* Merge the bean using the given merge options.
*
@@ -1832,6 +1442,13 @@ public interface EbeanServer {
*/
JsonContext json();
/**
* Return a ScriptRunner for running SQL or DDL scripts.
* <p/>
* Intended to use mostly in testing to run seed SQL scripts or truncate table scripts etc.
*/
ScriptRunner script();
/**
* Return the Document store.
*/
+13 -12
View File
@@ -1,11 +1,11 @@
package io.ebean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to determine the ebean version. (
* @author Roland Praml, FOCONIS AG
@@ -13,31 +13,32 @@ import org.slf4j.LoggerFactory;
*/
public class EbeanVersion {
private EbeanVersion() {
}
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
private static String version = "unknown";
static {
try {
Properties prop = new Properties();
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
if (in != null) {
prop.load(in);
in.close();
version = prop.getProperty("version");
try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
if (in != null) {
prop.load(in);
in.close();
version = prop.getProperty("version");
}
}
logger.info("ebean version: {}", version);
} catch (IOException e) {
logger.warn("Could not determine ebean version: {}", e.getMessage());
}
}
/**
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
*/
public static String getVersion() {
return version;
}
}
@@ -133,11 +133,22 @@ public interface ExpressionFactory {
*/
Expression ieq(String propertyName, String value);
/**
* Case Insensitive Not Equal To - property not equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
Expression ine(String propertyName, String value);
/**
* Case Insensitive Equal To that allows for named parameter use.
*/
Expression ieqObject(String propertyName, Object value);
/**
* Case Insensitive Not Equal To that allows for named parameter use.
*/
Expression ineObject(String propertyName, Object value);
/**
* Between - property between the two given values.
*/
@@ -120,6 +120,14 @@ public interface ExpressionList<T> {
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
* We effectively use the underlying ORM query to build the SQL and then execute
* and map it into DTO beans.
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
@@ -158,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.
*
@@ -166,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.
*
@@ -404,6 +432,11 @@ public interface ExpressionList<T> {
*/
Query<T> select(String properties);
/**
* Apply the fetchGroup which defines what part of the object graph to load.
*/
Query<T> select(FetchGroup<T> fetchGroup);
/**
* Set whether this query uses DISTINCT.
* <p>
@@ -716,6 +749,12 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> ieq(String propertyName, String value);
/**
* Case Insensitive Not Equal To - property not equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
ExpressionList<T> ine(String propertyName, String value);
/**
* Between - property between the two given values.
*/
@@ -1023,6 +1062,20 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> bitwiseAll(String propertyName, long flags);
/**
* Add expression for the given bit flags to be NOT set.
* <p>
* <pre>{@code
*
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
*
* }</pre>
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
ExpressionList<T> bitwiseNot(String propertyName, long flags);
/**
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
* <p>
+452
View File
@@ -0,0 +1,452 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The extended API for EbeanServer.
* <p>
* This provides the finder methods that take an explicit transaction rather than obtaining
* the transaction from the usual mechanism (which is ThreadLocal based).
* </p>
* <p>
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
* the use of the mechanism to get the current transaction and instead explicitly supply
* the transaction to use.
* </p>
* <p>
* Note that in all cases the transaction supplied can be null and in this case the EbeanServer
* will use the normal mechanism to obtain the transaction to use.
* </p>
*/
public interface ExtendedServer {
/**
* Return the NOW time from the Clock.
*/
long clockNow();
/**
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
* <p>
* Note that we only expect to change the Clock for testing purposes.
* </p>
*/
void setClock(Clock clock);
/**
* Return the number of 'top level' or 'root' entities this query should return.
*
* @see Query#findCount()
* @see Query#findFutureCount()
*/
<T> int findCount(Query<T> query, Transaction transaction);
/**
* Return the Id values of the query as a List.
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
* Return a QueryIterator for the query.
* <p>
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
* to findIterate(). The reason is that those methods automatically take care of
* closing the queryIterator (and the underlying jdbc statement and resultSet).
* </p>
* <p>
* This is similar to findEach in that not all the result beans need to be held
* in memory at the same time and as such is good for processing large queries.
* </p>
*
* @see Query#findIterate()
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Nonnull
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEach((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
* Compared to findEach() this provides the ability to stop processing the query
* results early by returning false for the Predicate.
* </p>
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEachWhile((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
*
* boolean carryOnProcessing = ...
* return carryOnProcessing;
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
/**
* Return versions of a @History entity bean.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
* Execute a query returning a list of beans.
* <p>
* Generally you are able to use {@link Query#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @see Query#findList()
*/
@Nonnull
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the row count on
* @param transaction the transaction (can be null).
* @return a Future object for the row count query
* @see Query#findFutureCount()
*/
@Nonnull
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the fetch Id's on
* @param transaction the transaction (can be null).
* @return a Future object for the list of Id's
* @see Query#findFutureIds()
*/
@Nonnull
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
* Execute find list query in a background thread returning a FutureList object.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a timeout).
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
*
* @param query the query to execute in the background
* @param transaction the transaction (can be null).
* @return a Future object for the list result of the query
* @see Query#findFutureList()
*/
@Nonnull
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
* The benefit of using this over findList() is that it provides functionality to get the
* total row count etc.
* </p>
* <p>
* If maxRows is not set on the query prior to calling findPagedList() then a
* PersistenceException is thrown.
* </p>
* <p>
* <pre>{@code
*
* PagedList<Order> pagedList = Ebean.find(Order.class)
* .setFirstRow(50)
* .setMaxRows(20)
* .findPagedList();
*
* // fetch the total row count in the background
* pagedList.loadRowCount();
*
* List<Order> orders = pagedList.getList();
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
* Execute the query returning a set of entity beans.
* <p>
* Generally you are able to use {@link Query#findSet()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* Set<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute
* @param transaction the transaction to use (can be null).
* @return the set of fetched beans.
* @see Query#findSet()
*/
@Nonnull
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
* Execute the query returning the entity beans in a Map.
* <p>
* Generally you are able to use {@link Query#findMap()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the map of fetched beans.
* @see Query#findMap()
*/
@Nonnull
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* Execute the query returning a list of values for a single property.
* <p>
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .select("name")
* .orderBy().asc("name")
* .findSingleAttributeList();
*
* }</pre>
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .orderBy().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
* @see Query#findSingleAttributeList()
*/
@Nonnull
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
* <p>
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
* @see Query#findOne()
*/
@Nullable
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* 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>
*
* @param query the query used for the delete
* @param transaction the transaction to use (can be null)
* @param <T> the type of entity bean to fetch.
* @return the number of beans/rows that were deleted
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the update query returning the number of rows updated.
* <p>
* The update query must be created using {@link EbeanServer#update(Class)}.
* </p>
*
* @param query the update query to execute
* @param transaction the optional transaction to use for the update (can be null)
* @param <T> the type of entity bean
* @return The number of rows updated
*/
<T> int update(Query<T> query, Transaction transaction);
/**
* Execute the sql query returning a list of MapBean.
* <p>
* Generally you are able to use {@link SqlQuery#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
@Nonnull
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* Execute the SqlQuery iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
/**
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
/**
* Execute the sql query returning a single MapBean or null.
* <p>
* This will throw a PersistenceException if the query found more than one
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findOne()
*/
@Nullable
SqlRow findOne(SqlQuery query, Transaction transaction);
}
+114
View File
@@ -0,0 +1,114 @@
package io.ebean;
import javax.annotation.Nonnull;
/**
* Defines what part of the object graph to load (select and fetch clauses).
* <p>
* Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative
* to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load"
* that can be defined separately from the query and combined with other FetchGroups.
* </p>
*
* <h3>Select example</h3>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* <h3>Select and fetch example</h3>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* <h3>Combining FetchGroups</h3>
* <p>
* FetchGroups can be combined together to form another FetchGroup.
* </p>
* <pre>{@code
*
* FetchGroup<Address> FG_ADDRESS = FetchGroup.of(Address.class)
* .select("line1, line2, city")
* .fetch("country", "name")
* .build();
*
* FetchGroup<Customer> FG_CUSTOMER = FetchGroup.of(Customer.class)
* .select("name, version")
* .fetch("billingAddress", FG_ADDRESS)
* .build();
*
*
* Customer.query()
* .select(FG_CUSTOMER)
* .findList();
*
* }</pre>
*
* @param <T> The bean type the Fetch group can be applied to
*/
public interface FetchGroup<T> {
/**
* Return the FetchGroup with the given select clause.
* <p>
* We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause.
* </p>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* @param select The select clause of the FetchGroup
*
* @return The FetchGroup with the given select clause
*/
@Nonnull
static <T> FetchGroup<T> of(Class<T> cls, String select) {
return XServiceProvider.fetchGroupOf(cls, select);
}
/**
* Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to.
* <p>
* We chain select() with one or more fetch() clauses to define the object graph to load.
* </p>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to
*/
@Nonnull
static <T> FetchGroupBuilder<T> of(Class<T> cls) {
return XServiceProvider.fetchGroupOf(cls);
}
}
@@ -0,0 +1,93 @@
package io.ebean;
import javax.annotation.Nonnull;
/**
* Builds a FetchGroup by adding fetch clauses.
* <p>
* We add select() and fetch() clauses to define the object graph we want to load.
* </p>
*
* <pre>{@code
*
* FetchGroup fetchGroup = FetchGroup
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .where()
* ...
* .findList();
*
* }</pre>
*/
public interface FetchGroupBuilder<T> {
/**
* Specify specific properties to select (top level properties).
*/
@Nonnull
FetchGroupBuilder<T> select(String select);
/**
* Fetch all the properties at the given path.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path);
/**
* Fetch the path with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path using a query join with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path lazily with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path including specified properties.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path, String properties);
/**
* Fetch the path including all its properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path);
/**
* Fetch the path including specified properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, String properties);
/**
* Fetch the path including all its properties lazily.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path);
/**
* Fetch the path including specified properties lazily.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path, String properties);
/**
* Build and return the FetchGroup.
*/
@Nonnull
FetchGroup<T> build();
}
@@ -8,11 +8,20 @@ import java.util.Set;
*/
public class MergeOptionsBuilder {
private static final MOptions DEFAULT_OPTIONS = new MOptions();
private Set<String> paths = new LinkedHashSet<>();
private boolean clientGeneratedIds;
private boolean deletePermanent = true;
private boolean deletePermanent;
/**
* Return the default options.
*/
public static MergeOptions defaultOptions() {
return DEFAULT_OPTIONS;
}
/**
* Add a path that will included in the merge.
@@ -47,7 +56,7 @@ public class MergeOptionsBuilder {
/**
* Build and return the MergeOptions instance.
*/
public MOptions build() {
public MergeOptions build() {
return new MOptions(paths, clientGeneratedIds, deletePermanent);
}
@@ -57,12 +66,19 @@ public class MergeOptionsBuilder {
private final boolean deletePermanent;
private final Set<String> paths;
private MOptions(){
this.clientGeneratedIds = false;
this.paths = new LinkedHashSet<>();
this.deletePermanent = false;
}
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
this.paths = paths;
this.clientGeneratedIds = clientGeneratedIds;
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);
}
/**
@@ -76,7 +76,6 @@ class PrimaryServer {
if (defaultServerName == null) {
defaultServerName = "db";
}
System.setProperty("ebean_db", defaultServerName);
return defaultServerName;
}
+25 -17
View File
@@ -397,6 +397,11 @@ public interface Query<T> {
*/
Query<T> select(String fetchProperties);
/**
* Apply the fetchGroup which defines what part of the object graph to load.
*/
Query<T> select(FetchGroup<T> fetchGroup);
/**
* Specify a path to fetch eagerly including specific properties.
* <p>
@@ -602,8 +607,6 @@ public interface Query<T> {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
* @see EbeanServer#findIds(Query, Transaction)
*/
@Nonnull
<A> List<A> findIds();
@@ -740,8 +743,6 @@ public interface Query<T> {
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
@Nonnull
List<T> findList();
@@ -759,8 +760,6 @@ public interface Query<T> {
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
@Nonnull
Set<T> findSet();
@@ -782,8 +781,6 @@ public interface Query<T> {
* .findMap();
*
* }</pre>
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@Nonnull
<K> Map<K, T> findMap();
@@ -927,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>
@@ -1488,15 +1505,6 @@ public interface Query<T> {
*/
String getGeneratedSql();
/**
* Deprecated - migrate to forUpdate().
*
* executed the select with "for update" which should lock the record
* "on read"
*/
@Deprecated
Query<T> setForUpdate(boolean forUpdate);
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
+40
View File
@@ -0,0 +1,40 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from o_customer order by name desc";
*
* Ebean.createSqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*/
@FunctionalInterface
public interface RowConsumer {
/**
* Read the data from the ResultSet and process it.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
void accept(ResultSet resultSet, int rowNum) throws SQLException;
}
+62
View File
@@ -0,0 +1,62 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to map raw JDBC ResultSet to objects.
* <p>
* This provides a low level mapping option with direct use of JDBC ResultSet
* with the option of having logic in the mapping. For example, only map some
* columns depending on the values read from other columns.
* </p>
* <p>
* For straight mapping into beans then DtoQuery would be the first choice as
* it can automatically map the ResultSet into beans.
* </p>
*
* <pre>{@code
*
* //
* // A mapper from ResultSet into our CustomerDto bean
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* @Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
* String name = rset.getString(2);
* String status = rset.getString(3);
*
* return new CustomerDto(id, name, status);
* }
* }
*
*
* //
* // Then use the mapper
* //
*
* String sql = "select id, name, status from o_customer where name = ?";
*
* CustomerDto rob = Ebean.createSqlQuery(sql)
* .setParameter(1, "Rob")
* .findOne(CUSTOMER_MAPPER);
*
*
* }</pre>
*
* @param <T> The type the row data is mapped into.
*/
@FunctionalInterface
public interface RowMapper<T> {
/**
* Read the data from the ResultSet and map to the return type.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
T map(ResultSet resultSet, int rowNum) throws SQLException;
}
+65
View File
@@ -0,0 +1,65 @@
package io.ebean;
import java.net.URL;
import java.util.Map;
/**
* Runs DDL and SQL scripts.
* <p/>
* Typically these are scripts used for testing such as seed SQL scripts or truncate SQL scripts.
* <p/>
* Scripts are executed in their own transaction and committed on successful completion.
*
* <h3>Example of simple use</h3>
* <pre>{@code
*
* EbeanServer server = Ebean.getDefaultServer();
* server.script().run("/scripts/test-script.sql");
*
* }</pre>
*
*
* <h3>Example using place holders in the script</h3>
* <pre>{@code
*
* Map<String,String> placeholders = new HashMap<>();
* placeholders.put("tableName", "e_basic");
*
* EbeanServer server = Ebean.getDefaultServer();
* server.script().run("/scripts/test-script.sql");
*
* }</pre>
*/
public interface ScriptRunner {
/**
* Run a script given the resource path (that should start with "/").
*/
void run(String path);
/**
* Run a script given the resource path (that should start with "/") and place holders.
*
* <pre>{@code
*
* Map<String,String> placeholders = new HashMap<>();
* placeholders.put("tableName", "e_basic");
*
* EbeanServer server = Ebean.getDefaultServer();
* server.script().run("/scripts/test-script.sql");
*
* }</pre>
*/
void run(String path, Map<String, String> placeholderMap);
/**
* Run a DDL or SQL script given the resource.
*/
void run(URL resource);
/**
* Run a DDL or SQL script given the resource and place holders.
*/
void run(URL resource, Map<String, String> placeholderMap);
}
+107
View File
@@ -3,6 +3,7 @@ package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -77,12 +78,118 @@ public interface SqlQuery extends Serializable {
@Nullable
SqlRow findOne();
/**
* Execute the query returning a single result using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> T findOne(RowMapper<T> mapper);
/**
* Execute the query returning a list using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> List<T> findList(RowMapper<T> mapper);
/**
* Execute the query reading each row from ResultSet using the RowConsumer.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet
* and is good for processing very large results where (unlike findList) we don't
* hold all the results in memory but instead can process row by row.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from customer order by name desc";
*
* Ebean.createSqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*
* @param consumer Used to read and process each ResultSet row.
*/
void findEachRow(RowConsumer consumer);
/**
* Execute the query returning an optional row.
*/
@Nonnull
Optional<SqlRow> findOneOrEmpty();
/**
* Execute the query returning a single scalar attribute.
* <pre>@{code
*
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
*
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
* .setParameter(1, 2)
* .findSingleAttribute(BigDecimal.class);
*
* }</pre>
*
* <p>
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
* </p>
*
* @param attributeType The type of the returned value
*/
<T> T findSingleAttribute(Class<T> attributeType);
/**
* Execute the query returning a single BigDecimal value.
* <p>
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
* </p>
*/
BigDecimal findSingleDecimal();
/**
* Execute the query returning a single Long value.
* <p>
* This is an alias for <code>findSingleAttribute(Long.class)</code>
* </p>
*/
Long findSingleLong();
/**
* Execute the query returning a list of scalar attribute values.
*
* <pre>{@code
*
* String sql =
* " select (unit_price * order_qty) " +
* " from o_order_detail " +
* " where unit_price > ? " +
* " order by (unit_price * order_qty) desc";
*
* //
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
* .setParameter(1, 3)
* .findSingleAttributeList(BigDecimal.class);
*
* }</pre>
*
* <p>
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
* </p>
*
* @param attributeType The type of the returned value
*/
<T> List<T> findSingleAttributeList(Class<T> attributeType);
/**
* The same as bind for named parameters.
*/
+58 -7
View File
@@ -19,15 +19,17 @@ package io.ebean;
* <p>
* <pre>{@code
*
* // example that uses 'named' parameters
* String s = "UPDATE f_topic set post_count = :count where id = :id";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
* // example that uses 'named' parameters
*
* int modifiedCount = Ebean.execute(update);
* String s = "UPDATE f_topic set post_count = :count where id = :id";
*
* String msg = "There were " + modifiedCount + " rows updated";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
*
* int modifiedCount = update.execute();
*
* String msg = "There were " + modifiedCount + " rows updated";
*
* }</pre>
* <p>
@@ -57,6 +59,33 @@ package io.ebean;
*
* txn.commit();
* }
* }</pre>
* <p>
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
* </p>
* <pre>{@code
*
* try (Transaction txn = Ebean.beginTransaction()) {
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.addBatch();
*
* int[] rows = insert.executeBatch();
*
* txn.commit();
* }
*
* }</pre>
*
@@ -69,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.
@@ -83,6 +117,23 @@ 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.
*
* @return The row counts for each of the batched statements.
*/
int[] executeBatch();
/**
* Add the statement to batch processing to then later execute via executeBatch().
*/
void addBatch();
/**
* Return the generated key value.
*/
+12 -21
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>
@@ -358,25 +358,13 @@ public interface Transaction extends AutoCloseable {
*/
void setBatchMode(boolean useBatch);
/**
* The JDBC batch mode to use for this transaction.
* <p>
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
* and this would be useful if the request cascades to detail beans.
* </p>
*
* @param persistBatchMode the batch mode to use for this transaction
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
*/
void setBatch(PersistBatch persistBatchMode);
/**
* Return the batch mode at the transaction level.
*/
PersistBatch getBatch();
boolean isBatchMode();
/**
* Set the JDBC batch mode to use for a save() or delete() request.
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
@@ -385,16 +373,19 @@ public interface Transaction extends AutoCloseable {
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
* <p>
* This is effectively already turned on for all platforms apart from older Sql Server.
* </p>
*
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
*/
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
void setBatchOnCascade(boolean batchMode);
/**
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
* Return the batch mode at the request level.
*/
PersistBatch getBatchOnCascade();
boolean isBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
+7
View File
@@ -432,4 +432,11 @@ public final class TxScope {
return this;
}
public boolean isBatchMode() {
return PersistBatch.ALL.equals(batch);
}
public boolean isBatchOnCascade() {
return PersistBatch.ALL.equals(batchOnCascade);
}
}
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.service.SpiFetchGroupService;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebean.service.SpiRawSqlService;
@@ -15,6 +16,16 @@ class XServiceProvider {
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
private static SpiFetchGroupService fetchGroupService = initSpiFetchGroupService();
private static SpiFetchGroupService initSpiFetchGroupService() {
Iterator<SpiFetchGroupService> loader = ServiceLoader.load(SpiFetchGroupService.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiFetchGroupService?");
}
private static SpiRawSqlService initRawSql() {
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
@@ -47,4 +58,17 @@ class XServiceProvider {
return profileLocationFactory;
}
/**
* Return the FetchGroup with the given select clause.
*/
static <T> FetchGroup<T> fetchGroupOf(Class<T> cls, String select) {
return fetchGroupService.of(cls, select);
}
/**
* Return the FetchGroupBuilder with the given select clause.
*/
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
return fetchGroupService.of(cls);
}
}
@@ -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) {
+54
View File
@@ -0,0 +1,54 @@
package io.ebean.cache;
import java.util.Set;
/**
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
* <p>
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
* modified since the query cache entry was cached. If any dependent tables have since been modified
* the query cache entry is treated as invalid.
* </p>
*/
public class QueryCacheEntry {
private final Object value;
private final Set<String> dependentTables;
private final long timestamp;
/**
* Create with dependent tables and timestamp.
*
* @param value The query result being cached
* @param dependentTables The extra tables the query is dependent on (joins to)
* @param timestamp The timestamp that the query uses to check for modifications
*/
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
this.value = value;
this.dependentTables = dependentTables;
this.timestamp = timestamp;
}
/**
* Return the actual query result.
*/
public Object getValue() {
return value;
}
/**
* Return the tables the query result is dependent on.
*/
public Set<String> getDependentTables() {
return dependentTables;
}
/**
* Return the timestamp used to check for modifications on the dependent tables.
*/
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Used to validate that a query cache entry is still valid based on dependent tables.
*/
public interface QueryCacheEntryValidate {
/**
* Return true if the entry is still valid based on dependent tables.
*/
boolean isValid(QueryCacheEntry queryCacheEntry);
}
+65
View File
@@ -0,0 +1,65 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Configuration used to create ServerCache instances.
*/
public class ServerCacheConfig {
private final ServerCacheType type;
private final String cacheKey;
private final ServerCacheOptions cacheOptions;
private final CurrentTenantProvider tenantProvider;
private final QueryCacheEntryValidate queryCacheEntryValidate;
public ServerCacheConfig(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
this.type = type;
this.cacheKey = cacheKey;
this.cacheOptions = cacheOptions;
this.tenantProvider = tenantProvider;
this.queryCacheEntryValidate = queryCacheEntryValidate;
}
/**
* Return the cache type.
*/
public ServerCacheType getType() {
return type;
}
/**
* Return the name of the cache.
*/
public String getCacheKey() {
return cacheKey;
}
/**
* Return the tuning options.
*/
public ServerCacheOptions getCacheOptions() {
return cacheOptions;
}
/**
* Return the current tenant provider.
*/
public CurrentTenantProvider getTenantProvider() {
return tenantProvider;
}
/**
* Return the service that provides validation for query cache entries.
*/
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}
/**
* Return true if the cache is a query cache.
*/
public boolean isQueryCache() {
return type == ServerCacheType.QUERY;
}
}
+10 -3
View File
@@ -1,7 +1,5 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Defines method for constructing caches for beans and queries.
*/
@@ -10,6 +8,15 @@ public interface ServerCacheFactory {
/**
* Create the cache for the given type with options.
*/
ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions);
ServerCache createCache(ServerCacheConfig config);
/**
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
* <p>
* This is used if a ServerCacheNotifyPlugin is not supplied.
* </p>
*
* @param listener The listener that should be used to process the notification events.
*/
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
}
@@ -0,0 +1,29 @@
package io.ebean.cache;
import java.util.Set;
/**
* Notification event that dependent tables have been modified.
* <p>
* This is sent to other interested servers (in the cluster).
* </p>
*/
public class ServerCacheNotification {
private final long modifyTimestamp;
private final Set<String> dependentTables;
public ServerCacheNotification(long modifyTimestamp, Set<String> dependentTables) {
this.modifyTimestamp = modifyTimestamp;
this.dependentTables = dependentTables;
}
public long getModifyTimestamp() {
return modifyTimestamp;
}
public Set<String> getDependentTables() {
return dependentTables;
}
}
+12
View File
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Interface for both listening to notification changes and sending them to other members of the cluster.
*/
public interface ServerCacheNotify {
/**
* Notify other server cache members of the table modifications or process the notifications.
*/
void notify(ServerCacheNotification notification);
}
@@ -0,0 +1,17 @@
package io.ebean.cache;
import io.ebean.config.ServerConfig;
/**
* Plugin that provides a ServerCacheNotify implementation.
* <p>
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
* </p>
*/
public interface ServerCacheNotifyPlugin {
/**
* Create a ServerCacheNotify implementation given the server configuration.
*/
ServerCacheNotify create(ServerConfig serverConfig);
}
+12 -5
View File
@@ -123,7 +123,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
private void initClear() {
synchronized (this) {
if (map == null) {
if (modifyListening) {
if (!disableLazyLoad && modifyListening) {
lazyLoadCollection(true);
} else {
map = new LinkedHashMap<>();
@@ -135,7 +135,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
private void init() {
synchronized (this) {
if (map == null) {
lazyLoadCollection(false);
if (disableLazyLoad) {
map = new LinkedHashMap<>();
} else {
lazyLoadCollection(false);
}
}
}
}
@@ -273,14 +277,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
checkReadOnly();
init();
if (modifyListening) {
Object oldBean = map.put(key, value);
E oldBean = map.put(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.put(key, value);
}
return map.put(key, value);
}
@Override
@@ -295,8 +301,9 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
modifyRemoval(oldBean);
}
}
} else {
map.putAll(puts);
}
map.putAll(puts);
}
@Override
+7 -3
View File
@@ -116,7 +116,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
private void initClear() {
synchronized (this) {
if (set == null) {
if (modifyListening) {
if (!disableLazyLoad && modifyListening) {
lazyLoadCollection(true);
} else {
set = new LinkedHashSet<>();
@@ -128,7 +128,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
private void init() {
synchronized (this) {
if (set == null) {
lazyLoadCollection(true);
if (disableLazyLoad) {
set = new LinkedHashSet<>();
} else {
lazyLoadCollection(true);
}
}
}
}
@@ -377,7 +381,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
@@ -0,0 +1,15 @@
package io.ebean.config;
/**
* Throw when an processing an entity bean that is not bytecode enhanced.
*
* Refer: https://ebean.io/docs/trouble-shooting#not-enhanced
*/
public class BeanNotEnhancedException extends IllegalStateException {
private static final long serialVersionUID = 3008101919425876799L;
public BeanNotEnhancedException(String msg) {
super(msg);
}
}
@@ -0,0 +1,15 @@
package io.ebean.config;
/**
* Throw when an processing thinks a bean is not registered.
*
* Refer: https://ebean.io/docs/trouble-shooting#not-registered
*/
public class BeanNotRegisteredException extends IllegalStateException {
private static final long serialVersionUID = -1411298126011136552L;
public BeanNotRegisteredException(String msg) {
super(msg);
}
}
@@ -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.
*/
@@ -102,6 +67,16 @@ public class DbMigrationConfig {
*/
protected Map<String, String> runPlaceholderMap;
/**
* DB schema used for the migration (and testing).
*/
protected String dbSchema;
/**
* Set to true if we consider this the 'default schema' (Postgres schema that matches DB username)
*/
protected boolean defaultDbSchema;
/**
* DB user used to run the DB migration.
*/
@@ -218,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).
@@ -373,6 +302,39 @@ public class DbMigrationConfig {
this.dbPassword = dbPassword;
}
/**
* Return the DB schema to use (for migration, testing etc).
*/
public String getDbSchema() {
String schema = readEnvironment("ddl.migration.schema");
if (schema != null) {
return schema;
}
return dbSchema;
}
/**
* Set the Db schema to use.
*/
public void setDbSchema(String dbSchema) {
this.dbSchema = dbSchema;
}
/**
* Set the Db schema if it hasn't already been defined.
*/
public void setDefaultDbSchema(String dbSchema) {
this.defaultDbSchema = true;
this.dbSchema = dbSchema;
}
/**
* Return true if this is considered the default DB schema (Postgres schema matching DB username).
*/
public boolean isDefaultDbSchema() {
return defaultDbSchema;
}
/**
* Return migration versions that should be added to history without running.
*/
@@ -402,9 +364,20 @@ public class DbMigrationConfig {
* You can use placeholders like ${version} or ${timestamp} in properties file.
*/
public String getDdlHeader() {
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
return ddlHeader;
}
/**
* Set the header prepended to the DDL.
*/
public void setDdlHeader(String ddlHeader) {
this.ddlHeader = ddlHeader;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
@@ -450,20 +423,15 @@ 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);
runMigration = properties.getBoolean("migration.run", runMigration);
metaTable = properties.get("migration.metaTable", metaTable);
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
dbSchema = properties.get("migration.dbSchema", dbSchema);
//Do not set user and pass from "datasource.db.username"
//There is a null test in MigrationRunner::getConnection to handle this
@@ -475,48 +443,6 @@ public class DbMigrationConfig {
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
dbPassword = properties.get("migration.dbpassword", adminPwd);
ddlHeader = properties.get("ddl.header", ddlHeader);
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
}
/**
* 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;
}
/**
@@ -550,6 +476,10 @@ public class DbMigrationConfig {
runnerConfig.setRunPlaceholders(runPlaceholders);
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setDbSchema(getDbSchema());
if (defaultDbSchema) {
runnerConfig.setSetCurrentSchema(false);
}
runnerConfig.setClassLoader(classLoader);
if (patchInsertOn != null) {
runnerConfig.setPatchInsertOn(patchInsertOn);
+112 -24
View File
@@ -14,6 +14,7 @@ import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.properties.PropertiesLoader;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
@@ -30,9 +31,9 @@ import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -57,22 +58,24 @@ import java.util.ServiceLoader;
* <pre>{@code
*
* ServerConfig c = new ServerConfig();
* c.setName("db");
*
* // read the ebean.properties and load
* // those settings into this serverConfig object
* c.loadFromProperties();
*
* // add any classes found in the app.data package
* c.addPackage("com.myapp.domain");
*
* // register as the 'Default' server
* c.setDefaultServer(true);
* // explicitly register the entity beans to avoid classpath scanning
* c.addClass(Customer.class);
* c.addClass(User.class);
*
* EbeanServer server = EbeanServerFactory.create(c);
*
* }</pre>
*
* <p>
* Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can
* be used to apply configuration to the ServerConfig.
* </p>
*
* @author emcgreal
* @author rbygrave
* @see EbeanServerFactory
@@ -226,6 +229,8 @@ public class ServerConfig {
private int persistBatchSize = 20;
private boolean disableLazyLoading;
/**
* The default batch size for lazy loading
*/
@@ -293,6 +298,11 @@ public class ServerConfig {
*/
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
/**
* Optional - the database schema that should be used to own the tables etc.
*/
private String dbSchema;
/**
* The db migration config (migration resource path etc).
*/
@@ -360,6 +370,11 @@ public class ServerConfig {
*/
private String uuidStateFile = "ebean-uuid.state";
/**
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
*/
private Clock clock = Clock.systemUTC();
private List<IdGenerator> idGenerators = new ArrayList<>();
private List<BeanFindController> findControllers = new ArrayList<>();
private List<BeanPersistController> persistControllers = new ArrayList<>();
@@ -493,6 +508,11 @@ public class ServerConfig {
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* When true we do not need explicit GeneratedValue mapping.
*/
private boolean idGeneratorAutomatic = true;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -500,6 +520,20 @@ public class ServerConfig {
}
/**
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
*/
public Clock getClock() {
return clock;
}
/**
* Set the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
*/
public void setClock(final Clock clock) {
this.clock = clock;
}
/**
* Return the slow query time in millis.
*/
@@ -895,6 +929,22 @@ public class ServerConfig {
this.queryBatchSize = queryBatchSize;
}
/**
* Return true if lazy loading is disabled on queries by default.
*/
public boolean isDisableLazyLoading() {
return disableLazyLoading;
}
/**
* Set to true to disable lazy loading by default.
* <p>
* It can be turned on per query via {@link Query#setDisableLazyLoading(boolean)}.
*/
public void setDisableLazyLoading(boolean disableLazyLoading) {
this.disableLazyLoading = disableLazyLoading;
}
/**
* Return the default batch size for lazy loading of beans and collections.
*/
@@ -1093,6 +1143,25 @@ public class ServerConfig {
this.profilingConfig = profilingConfig;
}
/**
* Return the DB schema to use.
*/
public String getDbSchema() {
return dbSchema;
}
/**
* Set the DB schema to use. This specifies to use this schema for:
* <ul>
* <li>Running Database migrations - Create and use the DB schema</li>
* <li>Testing DDL - Create-all.sql DDL execution creates and uses schema</li>
* <li>Testing Docker - Set default schema on connection URL</li>
* </ul>
*/
public void setDbSchema(String dbSchema) {
this.dbSchema = dbSchema;
}
/**
* Return the DB migration configuration.
*/
@@ -1914,6 +1983,15 @@ public class ServerConfig {
* Return the UUID state file.
*/
public String getUuidStateFile() {
if (uuidStateFile == null || uuidStateFile.isEmpty()) {
// by default, add servername...
uuidStateFile = name + "-uuid.state";
// and store it in the user's home directory
String homeDir = System.getProperty("user.home");
if (homeDir != null && homeDir.isEmpty()) {
uuidStateFile = homeDir + "/.ebean/" + uuidStateFile;
}
}
return uuidStateFile;
}
@@ -1967,7 +2045,7 @@ public class ServerConfig {
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
* as it is often the only thing we need to configure for migrations.
*/
public void setRunMigration(boolean runMigration){
public void setRunMigration(boolean runMigration) {
migrationConfig.setRunMigration(runMigration);
}
@@ -2685,20 +2763,6 @@ public class ServerConfig {
return list;
}
/**
* Deprecated - this does nothing now, we always try to read test configuration.
* <p>
* Load settings from test-ebean.properties and do nothing if the properties is not found.
* <p>
* This is typically used when test-ebean.properties is put into the test class path and used
* to configure Ebean for running tests.
* </p>
*/
@Deprecated
public void loadTestProperties() {
// do nothing now ... as we always try to read test configuration and that should only
}
/**
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
*/
@@ -2737,6 +2801,10 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
dbSchema = p.get("dbSchema", dbSchema);
if (dbSchema != null) {
migrationConfig.setDefaultDbSchema(dbSchema);
}
profilingConfig.loadSettings(p, name);
migrationConfig.loadSettings(p, name);
platformConfig.loadSettings(p);
@@ -2772,6 +2840,7 @@ public class ServerConfig {
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -2831,6 +2900,7 @@ public class ServerConfig {
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
jodaLocalTimeMode = p.get("jodaLocalTimeMode", jodaLocalTimeMode);
disableLazyLoading = p.getBoolean("disableLazyLoading", disableLazyLoading);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
queryBatchSize = p.getInt("queryBatchSize", queryBatchSize);
@@ -3074,9 +3144,10 @@ public class ServerConfig {
* @return A copy of the PlatformConfig with overridden properties
*/
public PlatformConfig newPlatformConfig(String propertiesPath, String platformPrefix) {
if (properties == null) {
properties = new Properties();
}
PropertiesWrapper p = new PropertiesWrapper(propertiesPath, platformPrefix, properties, classLoadConfig);
PlatformConfig config = new PlatformConfig(platformConfig);
config.loadSettings(p);
return config;
@@ -3109,6 +3180,23 @@ public class ServerConfig {
this.mappingLocations = mappingLocations;
}
/**
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
* Identity or Sequence generated values. When true Id properties are automatically
* assigned Identity or Sequence without the GeneratedValue mapping.
*/
public boolean isIdGeneratorAutomatic() {
return idGeneratorAutomatic;
}
/**
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
* mapping before they are assigned Identity or Sequence generation based on platform.
*/
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -0,0 +1,39 @@
package io.ebean.config;
/**
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
* <p>
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
* </p>
* <p>
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
* spring specific configuration. When we are not using a DI container we may use this mechanism to
* explicitly register the entity beans and avoid classpath scanning.
* </p>
* <pre>{@code
*
* public class EbeanConfigProvider implements ServerConfigProvider {
*
* @Override
* public void apply(ServerConfig config) {
*
* // register the entity bean classes explicitly
* config.addClass(Customer.class);
* config.addClass(User.class);
* ...
* }
* }
*
* }</pre>
*/
public interface ServerConfigProvider {
/**
* Apply the configuration to the ServerConfig.
* <p>
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
* </p>
*/
void apply(ServerConfig config);
}
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PartitionMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.CustomDbTypeMapping;
@@ -16,6 +17,7 @@ import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
/**
@@ -50,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.
@@ -62,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.
*/
@@ -176,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;
@@ -453,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.
*/
@@ -523,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.
@@ -658,6 +677,39 @@ public class DatabasePlatform {
return persistBatchOnCascade;
}
/**
* Create the DB schema if it does not exist.
*/
public void createSchemaIfNotExists(String dbSchema, Connection connection) throws SQLException {
if (!schemaExists(dbSchema, connection)) {
Statement query = connection.createStatement();
try {
logger.info("create schema:{}", dbSchema);
query.executeUpdate("create schema " + dbSchema);
} finally {
JdbcClose.close(query);
}
}
}
/**
* Return true if the schema exists.
*/
public boolean schemaExists(String dbSchema, Connection connection) throws SQLException {
ResultSet schemas = connection.getMetaData().getSchemas();
try {
while (schemas.next()) {
String schema = schemas.getString(1);
if (schema.equalsIgnoreCase(dbSchema)) {
return true;
}
}
} finally {
JdbcClose.close(schemas);
}
return false;
}
/**
* Return true if the table exists.
*/
@@ -672,6 +724,20 @@ public class DatabasePlatform {
}
}
/**
* Return true if partitions exist for the given table.
*/
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
return true;
}
/**
* Return the SQL to create an initial partition for the given table.
*/
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String singlePrimaryKey) {
return null;
}
/**
* Escapes the like string for this DB-Platform
*/
@@ -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;
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PartitionMode;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -11,6 +12,10 @@ import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
/**
@@ -116,4 +121,32 @@ public class PostgresPlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("select count(*) from pg_inherits i WHERE i.inhparent = ?::regclass")) {
statement.setString(1, table);
try (ResultSet resultSet = statement.executeQuery()) {
return resultSet.next() && resultSet.getInt(1) > 0;
}
}
}
/**
* Return SQL using built in partition helper functions to create some initial partitions.
*
* 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 = "";
}
if (pkey == null) {
pkey = "";
}
return "select partition('" + mode.name().toLowerCase() + "','" + tableName + "','" + pkey + "','" + property + "',1);";
}
}
@@ -18,26 +18,26 @@ import java.util.Set;
*/
class LoadContext {
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
/**
* Map we are loading the properties into.
*/
private final Map<String, String> map = new LinkedHashMap<>();
private final Map<String, String> map = new LinkedHashMap<>();
/**
* Names of resources/files that were loaded.
*/
private final Set<String> loadedResources = new LinkedHashSet<>();
private final Set<String> loadedResources = new LinkedHashSet<>();
/**
* Return the input stream (maybe null) for the given source.
*/
InputStream resource(String resourcePath, Loader.Source source) {
InputStream resource(String resourcePath, Loader.Source source) {
InputStream is = null;
if (source == Loader.Source.RESOURCE) {
is = getClass().getResourceAsStream("/" + resourcePath);
is = resourceStream(resourcePath);
if (is != null) {
loadedResources.add(resourcePath);
}
@@ -46,43 +46,52 @@ class LoadContext {
if (file.exists()) {
try {
is = new FileInputStream(file);
loadedResources.add("file:"+resourcePath);
loadedResources.add("file:" + resourcePath);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
}
}
return is;
}
return is;
}
private InputStream resourceStream(String resourcePath) {
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
if (is == null) {
// search the module path for top level resource
is = ClassLoader.getSystemResourceAsStream(resourcePath);
}
return is;
}
/**
* Add a property entry.
*/
void put(String key, String val) {
if (val != null) {
val = val.trim();
void put(String key, String val) {
if (val != null) {
val = val.trim();
}
map.put(key, val);
}
map.put(key, val);
}
/**
* Evaluate all the expressions and return as a Properties object.
*/
Properties eval() {
Properties eval() {
log.info("loaded properties from {}", loadedResources);
log.info("loaded properties from {}", loadedResources);
Properties properties = new Properties();
Properties properties = new Properties();
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = PropertyEval.eval(entry.getValue());
properties.setProperty(key, value);
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = PropertyEval.eval(entry.getValue());
properties.setProperty(key, value);
}
return properties;
}
return properties;
}
/**
* Read the special properties that can point to an external properties source.
@@ -16,10 +16,10 @@ import java.util.Properties;
*/
class Loader {
private static final Logger log = LoggerFactory.getLogger(Loader.class);
private static final Logger log = LoggerFactory.getLogger(Loader.class);
enum Source {
RESOURCE,
enum Source {
RESOURCE,
FILE
}
@@ -27,7 +27,7 @@ class Loader {
private YamlLoader yamlLoader;
Loader() {
Loader() {
String skipYaml = System.getProperty("ebeanSkipYaml");
if (!"true".equals(skipYaml)) {
try {
@@ -44,25 +44,26 @@ class Loader {
/**
* Load the configuration with the expected ordering.
*/
void load() {
void load() {
loadMain(Source.RESOURCE);
// external file configuration overrides the resources configuration
loadMain(Source.FILE);
loadViaSystemProperty();
loadViaIndirection();
loadViaSystemProperty();
loadViaIndirection();
// test configuration (if found) overrides main configuration
// test configuration (if found) overrides main configuration
// we should only find these resources when running tests
loadTest();
}
loadTest();
}
/**
* Load test configuration.
*/
private void loadTest() {
loadProperties("application-test.properties", Source.RESOURCE);
loadYaml("application-test.yaml", Source.RESOURCE);
loadYaml("application-test.yml", Source.RESOURCE);
loadProperties("test-ebean.properties", Source.RESOURCE);
}
@@ -83,13 +84,14 @@ class Loader {
* Load the main configuration for the given source.
*/
private void loadMain(Source source) {
loadYaml("application.yaml", source);
loadYaml("application.yml", source);
loadProperties("application.properties", source);
loadProperties("ebean.properties", source);
}
private void loadViaSystemProperty() {
String fileName = System.getenv("EBEAN_PROPS_FILE");
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName != null) {
@@ -99,7 +101,7 @@ class Loader {
}
void loadWithExtensionCheck(String fileName) {
if (fileName.endsWith("yml")) {
if (fileName.endsWith("yaml") || fileName.endsWith("yml")) {
loadYaml(fileName, Source.FILE);
} else if (fileName.endsWith("properties")) {
loadProperties(fileName, Source.FILE);
@@ -112,59 +114,66 @@ class Loader {
* Evaluate all the configuration entries and return as properties.
*/
Properties eval() {
return loadContext.eval();
}
return loadContext.eval();
}
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
try {
yamlLoader.load(resource(resourcePath, source));
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
try {
try (InputStream is = resource(resourcePath, source)) {
yamlLoader.load(is);
}
} catch (Exception e) {
log.warn("Failed to read yml from:" + resourcePath, e);
}
}
}
}
}
void loadProperties(String resourcePath, Source source) {
InputStream is = resource(resourcePath, source);
if (is != null) {
loadProperties(is);
}
}
void loadProperties(String resourcePath, Source source) {
try {
try (InputStream is = resource(resourcePath, source)) {
if (is != null) {
loadProperties(is);
}
}
} catch (Exception e) {
log.warn("Failed to read properties from:" + resourcePath, e);
}
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
private void loadProperties(InputStream is) {
private void loadProperties(InputStream is) {
if (is != null) {
try {
Properties properties = new Properties();
properties.load(is);
put(properties);
} catch (IOException e) {
throw new RuntimeException("Failed to load properties?", e);
} finally {
close(is);
}
}
}
if (is != null) {
try {
Properties properties = new Properties();
properties.load(is);
put(properties);
} catch (IOException e) {
throw new RuntimeException("Failed to load properties?", e);
} finally {
close(is);
}
}
}
private void put(Properties properties) {
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String property = properties.getProperty(key);
loadContext.put(key, property);
}
}
private void put(Properties properties) {
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String property = properties.getProperty(key);
loadContext.put(key, property);
}
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
log.warn("Error closing input stream for properties", e);
}
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
log.warn("Error closing input stream for properties", e);
}
}
}
@@ -18,14 +18,14 @@ public class PropertiesLoader {
* <p>Firstly loads from main resources</p>
* <pre>
* - application.properties
* - application.yml
* - application.yaml
* - ebean.properties
* </pre>
* <p>
* <p>Then loads from local files</p>
* <pre>
* - application.properties
* - application.yml
* - application.yaml
* - ebean.properties
* </pre>
* <p>
@@ -41,7 +41,7 @@ public class PropertiesLoader {
* <p>Loads from test resources</p>
* <pre>
* - application-test.properties
* - application-test.yml
* - application-test.yaml
* - test-ebean.properties
* </pre>
*/
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
/**
@@ -93,6 +94,43 @@ public interface DbMigration {
*/
void setStrictMode(boolean strictMode);
/**
* Set to true to include a generated header comment in the DDL script.
*/
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
/**
* Set this to false to exclude the builtin support for table partitioning (with @DbPartition).
*/
void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning);
/**
* Set the header that is included in the generated DDL script.
*/
void setHeader(String header);
/**
* Set the prefix for the version. Set this to "V" for use with Flyway.
*/
void setApplyPrefix(String applyPrefix);
/**
* Set the version of the migration to be generated.
*/
void setVersion(String version);
/**
* Set the name of the migration to be generated.
*/
void setName(String name);
/**
* Generate a migration for the version specified that contains pending drops.
*
* @param generatePendingDrop The version of a prior migration that holds pending drops.
*/
void setGeneratePendingDrop(String generatePendingDrop);
/**
* Add an additional platform to write the migration DDL.
* <p>
@@ -110,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>
@@ -1,4 +1,4 @@
package io.ebeaninternal.server.core;
package io.ebean.event;
import io.ebeaninternal.server.lib.ShutdownManager;
@@ -7,8 +7,14 @@ import javax.servlet.ServletContextEvent;
/**
* Listens for webserver server starting and stopping events.
* <p>
* Register this listener in the web.xml configuration file. This will listen
* for startup and shutdown events.
* This should be used when the deployment is into a servlet container where the webapp
* can be shutdown or redeployed without the JVM stopping.
* </p>
* <p>
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
* runnable war or when using a servlet container that only contains the single webapp and
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
* hook that Ebean registers.
* </p>
*/
public class ServletContextListener implements javax.servlet.ServletContextListener {
@@ -9,6 +9,10 @@ import io.ebean.event.BeanQueryAdapter;
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
/**
* Information and methods on BeanDescriptors made available to plugins.
@@ -18,6 +22,7 @@ public interface BeanType<T> {
/**
* Return the short name of the bean type.
*/
@Nonnull
String getName();
/**
@@ -28,11 +33,13 @@ public interface BeanType<T> {
/**
* Return the full name of the bean type.
*/
@Nonnull
String getFullName();
/**
* Return the class type this BeanDescriptor describes.
*/
@Nonnull
Class<T> getBeanType();
/**
@@ -43,6 +50,7 @@ public interface BeanType<T> {
/**
* Return all the properties for this bean type.
*/
@Nonnull
Collection<? extends Property> allProperties();
/**
@@ -197,6 +205,28 @@ public interface BeanType<T> {
*/
boolean hasInheritance();
/**
* Return true if this object is the root level object in its entity
* inheritance.
*/
boolean isInheritanceRoot();
/**
* Returns all direct children of this beantype
*/
List<BeanType<?>> getInheritanceChildren();
/**
* Returns the parent in inheritance hiearchy
*/
BeanType<?> getInheritanceParent();
/**
* Visit all children recursively
* @param visitor
*/
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
/**
* Return the discriminator column.
*/
@@ -73,4 +73,14 @@ public interface ExpressionPath {
* Return the Id values for the given bean value.
*/
Object[] getAssocIdValues(EntityBean bean);
/**
* Return the underlying bean property.
*/
public Property getProperty();
/**
* The ElPrefix plus name.
*/
String getElName();
}
@@ -1,5 +1,7 @@
package io.ebean.plugin;
import javax.annotation.Nonnull;
/**
* Property of a entity bean that can be read.
*/
@@ -8,8 +10,15 @@ public interface Property {
/**
* Return the name of the property.
*/
@Nonnull
String getName();
/**
* Return the type of the property.
*/
@Nonnull
Class<?> getPropertyType();
/**
* Return the value of the property on the given bean.
*/
@@ -0,0 +1,26 @@
package io.ebean.service;
import io.ebean.FetchGroup;
import io.ebean.FetchGroupBuilder;
/**
* Service that parses FetchGroup expressions.
*/
public interface SpiFetchGroupService {
/**
* Return the FetchGroup with the given select clause.
*
* @param beanType The type of entity bean the fetch group is for
* @param select The properties to select (top level properties)
*/
<T> FetchGroup<T> of(Class<T> beanType, String select);
/**
* Create and return a FetchGroupBuilder starting with a select() clause.
*
* @param beanType The type of entity bean the fetch group is for
* @return The FetchGroupBuilder to add additional select and fetch clauses
*/
<T> FetchGroupBuilder<T> of(Class<T> beanType);
}
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
}
Props getProps(String path) {
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
return props;
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
}
public Collection<Props> getPathProps() {
@@ -0,0 +1,47 @@
package io.ebeaninternal.api;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
/**
* Context used to read binary format messages.
*/
public class BinaryReadContext {
private final DataInputStream in;
/**
* Create with protocol 0 and byte data.
*/
public BinaryReadContext(byte[] byteData) {
this(new DataInputStream(new ByteArrayInputStream(byteData)));
}
/**
* Create with protocol version and DataInputStream data.
*/
public BinaryReadContext(DataInputStream in) {
this.in = in;
}
public DataInputStream in() {
return in;
}
public boolean readBoolean() throws IOException {
return in.readBoolean();
}
public int readInt() throws IOException {
return in.readInt();
}
public String readUTF() throws IOException {
return in.readUTF();
}
public long readLong() throws IOException {
return in.readLong();
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.api;
import java.io.IOException;
/**
* Messages that can be sent in binary form.
* <p>
* Mainly RemoteTransactionEvent which is sent to cluster members.
* </p>
*/
public interface BinaryWritable {
int TYPE_BEANIUD = 1;
int TYPE_TABLEIUD = 2;
int TYPE_CACHE = 3;
int TYPE_TABLEMOD = 4;
/**
* Write message in binary format.
*/
void writeBinary(BinaryWriteContext out) throws IOException;
}
@@ -0,0 +1,49 @@
package io.ebeaninternal.api;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Context used to write binary message (like RemoteTransactionEvent).
*/
public class BinaryWriteContext {
private final DataOutputStream out;
private long counter;
public BinaryWriteContext(DataOutputStream out) {
this.out = out;
}
/**
* Return the number of message parts that have been written.
*/
public long counter() {
return counter;
}
/**
* Return the output stream to write to.
*/
public DataOutputStream os() {
return out;
}
/**
* Start a message part with a given type code.
*/
public DataOutputStream start(int type) throws IOException {
counter++;
out.writeBoolean(true);
out.writeInt(type);
return out;
}
/**
* End of message parts.
*/
public void end() throws IOException {
out.writeBoolean(false);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import java.io.Serializable;
@@ -154,6 +155,10 @@ public class BindParams implements Serializable {
public void setParameter(int position, Object value) {
Param p = getParam(position);
if (value instanceof Collection) {
// use of postgres ANY with positioned parameter
value = new MultiValueWrapper((Collection)value);
}
p.setInValue(value);
}
@@ -168,13 +168,8 @@ public class LoadBeanRequest extends LoadRequest {
// necessary but allow processing to continue until it is accessed by client code
Object id = desc.getId(ebi.getOwner());
if (!loadedIds.contains(id)) {
if (desc.isSoftDelete()) {
// assume this is logically deleted (hence not found)
desc.setSoftDeleteValue(ebi.getOwner());
} else {
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
ebi.setLazyLoadFailure(id);
}
// assume this is logically deleted (hence not found)
desc.markAsDeleted(ebi.getOwner());
}
}
}
@@ -25,7 +25,7 @@ public class NaturalKeyQueryData<T> {
private List<Pairs.Entry> inPairs;
// IN clause - only one allowed
private Collection<?> inValues;
private List<Object> inValues;
private String inProperty;
// normal EQ expressions
@@ -52,36 +52,36 @@ public class NaturalKeyQueryData<T> {
/**
* Match for In Pairs expression. We only allow one IN clause.
*/
public boolean matchInPairs(Pairs pairs) {
public List<Pairs.Entry> matchInPairs(String property0, String property1, List<Pairs.Entry> inPairs) {
if (hasIn) {
// only 1 IN allowed (to project naturalIds)
return false;
return null;
}
if (matchProperty(pairs.getProperty0()) && matchProperty(pairs.getProperty1())) {
if (matchProperty(property0) && matchProperty(property1)) {
this.hasIn = true;
this.inProperty0 = pairs.getProperty0();
this.inProperty1 = pairs.getProperty1();
this.inPairs = pairs.getEntries();
return true;
this.inProperty0 = property0;
this.inProperty1 = property1;
this.inPairs = new ArrayList<>(inPairs); // will be modified
return this.inPairs;
}
return false;
return null;
}
/**
* Match for IN expression. We only allow one IN clause.
*/
public boolean matchIn(String propName, Collection<?> sourceValues) {
public List<Object> matchIn(String propName, List<Object> sourceValues) {
if (hasIn) {
// only 1 IN allowed (to project naturalIds)
return false;
return null;
}
if (matchProperty(propName)) {
this.hasIn = true;
this.inProperty = propName;
this.inValues = sourceValues;
return true;
this.inValues = new ArrayList<>(sourceValues);
return this.inValues;
}
return false;
return null;
}
/**
@@ -1,7 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import java.util.ArrayList;
@@ -38,9 +37,9 @@ public class ScopeTrans {
*/
private final ArrayList<Class<? extends Throwable>> rollbackFor;
private PersistBatch restoreBatch;
private Boolean restoreBatch;
private PersistBatch restoreBatchOnCascade;
private Boolean restoreBatchOnCascade;
private int restoreBatchSize;
@@ -53,6 +52,10 @@ public class ScopeTrans {
*/
private boolean rolledBack;
/**
* Flag set when nested commit has occurred.
*/
private boolean nestedCommit;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
@@ -64,20 +67,20 @@ public class ScopeTrans {
if (transaction != null) {
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
restoreBatch = transaction.getBatch();
restoreBatchOnCascade = transaction.getBatchOnCascade();
restoreBatch = transaction.isBatchMode();
restoreBatchOnCascade = transaction.isBatchOnCascade();
restoreBatchSize = transaction.getBatchSize();
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
restoreBatchFlushOnQuery = transaction.isBatchFlushOnQuery();
}
if (txScope.isBatchSet()) {
transaction.setBatch(txScope.getBatch());
transaction.setBatchMode(txScope.isBatchMode());
}
if (!txScope.isFlushOnQuery()) {
transaction.setBatchFlushOnQuery(false);
}
if (txScope.isBatchOnCascadeSet()) {
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
transaction.setBatchOnCascade(txScope.isBatchOnCascade());
}
if (txScope.isBatchSizeSet()) {
transaction.setBatchSize(txScope.getBatchSize());
@@ -113,7 +116,6 @@ public class ScopeTrans {
complete();
}
/**
* Complete the transaction programmatically. Try to commit.
*/
@@ -124,7 +126,7 @@ public class ScopeTrans {
}
public void end() {
if (created) {
if (created || !nestedCommit) {
transaction.end();
}
}
@@ -133,9 +135,10 @@ public class ScopeTrans {
if (created) {
transaction.commit();
} else {
nestedCommit = true;
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
transaction.setBatchMode(restoreBatch);
}
if (restoreBatchOnCascade != null) {
transaction.setBatchOnCascade(restoreBatchOnCascade);
@@ -1,18 +0,0 @@
package io.ebeaninternal.api;
/**
* Plugin API available to invoke something prior to container bootup.
* <p>
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
* we have a test DB docker container running and setup ready to go.
* </p>
*/
public interface SpiContainerBootup {
/**
* Run something at bootup prior to the container starting.
*
* For example, start DB docker container(s).
*/
void bootup();
}
@@ -37,6 +37,7 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
/**
* Return the label for the query.
*/
@Override
String getLabel();
/**
@@ -2,8 +2,11 @@ package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.ExtendedServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.bean.BeanCollectionLoader;
@@ -29,7 +32,12 @@ import java.util.function.Predicate;
/**
* Service Provider extension to EbeanServer.
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the log manager.
*/
SpiLogManager log();
/**
* Return the server extended Json context.
@@ -229,6 +237,31 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void scopedTransactionExit(Object returnOrThrowable, int opCode);
/**
* SqlQuery find single attribute.
*/
<T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find single attribute list.
*/
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find one with mapper.
*/
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find list with mapper.
*/
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find each with consumer.
*/
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
/**
* DTO findList query.
*/
@@ -268,4 +301,19 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Return true if a row for the bean type and id exists.
*/
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
/**
* Add to JDBC batch for later execution.
*/
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
/**
* Execute the batched statement.
*/
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
/**
* Execute the sql update regardless of transaction batch mode.
*/
int executeNow(SpiSqlUpdate sqlUpdate);
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.api;
/**
* Log manager for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogManager {
/**
* Return the SQL logger.
*/
SpiLogger sql();
/**
* Return the TXN logger.
*/
SpiLogger txn();
/**
* Return the Summary logger.
*/
SpiLogger sum();
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.api;
/**
* Logger for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogger {
/**
* Is debug logging enabled.
*/
boolean isDebug();
/**
* Is trace logging enabled.
*/
boolean isTrace();
/**
* Log a debug level message.
*/
void debug(String msg);
/**
* Log a trace level message.
*/
void trace(String msg);
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.api;
/**
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
*/
public interface SpiLoggerFactory {
/**
* Create the logger given the name.
*/
SpiLogger create(String name);
}
@@ -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.
@@ -644,9 +657,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
boolean isBeanCachePut();
/**
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
* Return true if we must hit the DB (Cache reload or select for update).
*/
boolean isBeanCacheReload();
boolean isForceHitDatabase();
/**
* Return the cache mode for using the bean cache (Get and Put).
@@ -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;
@@ -101,6 +100,11 @@ public interface SpiTransaction extends Transaction {
*/
String getId();
/**
* Return the start timestamp for the transaction (JVM side).
*/
long getStartMillis();
/**
* Return true if this transaction has updateAllLoadedProperties set.
* If null is returned the server default is used (set on ServerConfig).
@@ -179,7 +183,7 @@ public interface SpiTransaction extends Transaction {
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
boolean isBatchThisRequest(PersistRequest.Type type);
boolean isBatchThisRequest();
/**
* Return the BatchControl used to batch up persist requests.
@@ -3,12 +3,10 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
@@ -30,6 +28,11 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.translate(message, cause);
}
@Override
public long getStartMillis() {
return transaction.getStartMillis();
}
@Override
public void setLabel(String label) {
transaction.setLabel(label);
@@ -246,23 +249,18 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public void setBatch(PersistBatch persistBatchMode) {
transaction.setBatch(persistBatchMode);
public boolean isBatchMode() {
return transaction.isBatchMode();
}
@Override
public PersistBatch getBatch() {
return transaction.getBatch();
public void setBatchOnCascade(boolean batchMode) {
transaction.setBatchOnCascade(batchMode);
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
transaction.setBatchOnCascade(batchOnCascadeMode);
}
@Override
public PersistBatch getBatchOnCascade() {
return transaction.getBatchOnCascade();
public boolean isBatchOnCascade() {
return transaction.isBatchOnCascade();
}
@Override
@@ -356,8 +354,8 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
return transaction.isBatchThisRequest(type);
public boolean isBatchThisRequest() {
return transaction.isBatchThisRequest();
}
@Override
@@ -3,10 +3,13 @@ package io.ebeaninternal.api;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.transaction.DeleteByIdMap;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
@@ -27,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;
}
@@ -69,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() {
@@ -92,26 +100,33 @@ 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);
}
/**
* Build and return the cache changeSet.
*/
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
if (eventBeans != null) {
eventBeans.notifyCache(changeSet);
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
if (changeSet == null && deleteByIdMap == null && eventTables == null) {
return null;
}
if (changeSet == null) {
changeSet = new CacheChangeSet(manager.clockNowMillis());
}
if (eventTables != null && !eventTables.isEmpty()) {
// notify cache with table based changes
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
for (TransactionEventTable.TableIUD tableIUD : eventTables.values()) {
dm.cacheNotify(tableIUD, changeSet);
}
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache(changeSet);
@@ -123,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,10 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.event.BulkTableEvent;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
@@ -12,7 +9,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public final class TransactionEventTable implements Serializable {
public final class TransactionEventTable implements Serializable, BinaryWritable {
private static final long serialVersionUID = 2236555729767483264L;
@@ -23,20 +20,13 @@ public final class TransactionEventTable implements Serializable {
return "TransactionEventTable " + map.values();
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
for (TableIUD tableIud : map.values()) {
tableIud.writeBinaryMessage(msgList);
tableIud.writeBinary(out);
}
}
public void readBinaryMessage(DataInput dataInput) throws IOException {
TableIUD tableIud = TableIUD.readBinaryMessage(dataInput);
map.put(tableIud.getTableName(), tableIud);
}
public void add(TransactionEventTable table) {
for (TableIUD iud : table.values()) {
@@ -47,7 +37,6 @@ public final class TransactionEventTable implements Serializable {
public void add(String table, boolean insert, boolean update, boolean delete) {
table = table.toUpperCase();
add(new TableIUD(table, insert, update, delete));
}
@@ -67,7 +56,7 @@ public final class TransactionEventTable implements Serializable {
return map.values();
}
public static class TableIUD implements Serializable, BulkTableEvent {
public static class TableIUD implements Serializable, BulkTableEvent, BinaryWritable {
private static final long serialVersionUID = -1958317571064162089L;
@@ -83,7 +72,7 @@ public final class TransactionEventTable implements Serializable {
this.delete = delete;
}
public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException {
public static TableIUD readBinaryMessage(BinaryReadContext dataInput) throws IOException {
String table = dataInput.readUTF();
boolean insert = dataInput.readBoolean();
@@ -93,17 +82,13 @@ public final class TransactionEventTable implements Serializable {
return new TableIUD(table, insert, update, delete);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
BinaryMessage msg = new BinaryMessage(table.length() + 10);
DataOutputStream os = msg.getOs();
os.writeInt(BinaryMessage.TYPE_TABLEIUD);
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
DataOutputStream os = out.start(TYPE_TABLEIUD);
os.writeUTF(table);
os.writeBoolean(insert);
os.writeBoolean(update);
os.writeBoolean(delete);
msgList.add(msg);
}
@Override
@@ -13,7 +13,7 @@ public interface TxnProfileEventCodes {
String EVT_INSERT = "i";
String EVT_UPDATE = "u";
String EVT_DELETE = "d";
String EVT_SOFT_DELETE = "ds";
String EVT_DELETE_SOFT = "ds";
String EVT_DELETE_PERMANENT = "dp";
String EVT_ORMUPDATE = "uo";
String FIND_UPDATE = "uq";
@@ -1,11 +1,16 @@
package io.ebeaninternal.dbmigration;
import io.ebean.Transaction;
import io.ebean.config.DbMigrationConfig;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.model.CurrentModel;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
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;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import io.ebeaninternal.server.deploy.PartitionMeta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,6 +43,8 @@ public class DdlGenerator {
private final boolean createOnly;
private final boolean jaxbPresent;
private final boolean ddlCommitOnCreateIndex;
private final String dbSchema;
private final ScriptTransform scriptTransform;
private CurrentModel currentModel;
private String dropAllContent;
@@ -48,6 +55,7 @@ public class DdlGenerator {
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
this.generateDdl = serverConfig.isDdlGenerate();
this.createOnly = serverConfig.isDdlCreateOnly();
this.dbSchema = serverConfig.getDbSchema();
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
this.runDdl = false;
@@ -56,6 +64,7 @@ public class DdlGenerator {
this.runDdl = serverConfig.isDdlRun();
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
}
this.scriptTransform = createScriptTransform(serverConfig.getMigrationConfig());
}
/**
@@ -85,98 +94,164 @@ public class DdlGenerator {
* Run the DDL drop and DDL create scripts if properties have been set.
*/
protected void runDdl() {
if (runDdl) {
Connection connection = null;
try {
runInitSql();
runDropSql();
runCreateSql();
runSeedSql();
} catch (IOException e) {
String msg = "Error reading drop/create script from file system";
throw new RuntimeException(msg, e);
connection = obtainConnection();
runDdlWith(connection);
} finally {
JdbcClose.rollback(connection);
JdbcClose.close(connection);
}
}
}
private void runDdlWith(Connection connection) {
try {
if (dbSchema != null) {
createSchemaIfRequired(connection);
}
runInitSql(connection);
runDropSql(connection);
runCreateSql(connection);
runSeedSql(connection);
} catch (IOException e) {
throw new RuntimeException("Error reading drop/create script from file system", e);
}
}
private Connection obtainConnection() {
try {
return server.getPluginApi().getDataSource().getConnection();
} catch (SQLException e) {
throw new PersistenceException("Failed to obtain connection to run DDL", e);
}
}
private void createSchemaIfRequired(Connection connection) {
try {
for (String schema : dbSchema.split(",")) {
server.getDatabasePlatform().createSchemaIfNotExists(schema, connection);
}
} catch (SQLException e) {
throw new PersistenceException("Failed to create DB Schema", e);
}
}
/**
* Execute all the DDL statements in the script.
*/
public int runScript(boolean expectErrors, String content, String scriptName) {
public int runScript(Connection connection, boolean expectErrors, String content, String scriptName) {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
Transaction transaction = server.createTransaction();
Connection connection = transaction.getConnection();
try {
if (expectErrors) {
connection.setAutoCommit(true);
} else if (ddlCommitOnCreateIndex) {
runner.setCommitOnCreateIndex();
}
int count = runner.runAll(content, connection);
int count = runner.runAll(scriptTransform.transform(content), connection);
if (expectErrors) {
connection.setAutoCommit(false);
}
transaction.commit();
connection.commit();
return count;
} catch (SQLException e) {
throw new PersistenceException("Failed to run script", e);
} finally {
transaction.end();
JdbcClose.rollback(connection);
}
}
protected void runDropSql() throws IOException {
protected void runDropSql(Connection connection) throws IOException {
if (!createOnly) {
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
runScript(connection, false, extraApply, "extra-ddl");
}
}
if (dropAllContent == null) {
dropAllContent = readFile(getDropFileName());
}
runScript(true, dropAllContent, getDropFileName());
runScript(connection, true, dropAllContent, getDropFileName());
}
}
protected void runCreateSql() throws IOException {
protected void runCreateSql(Connection connection) throws IOException {
if (createAllContent == null) {
createAllContent = readFile(getCreateFileName());
}
runScript(false, createAllContent, getCreateFileName());
runScript(connection, false, createAllContent, getCreateFileName());
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
if (currentModel.isTablePartitioning()) {
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
runScript(connection, false, extraPartitioning, "builtin-partitioning-ddl");
}
}
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
runScript(connection, false, extraApply, "extra-ddl");
}
if (currentModel.isTablePartitioning()) {
checkInitialTablePartitions(connection);
}
}
}
protected void runInitSql() throws IOException {
runResourceScript(server.getServerConfig().getDdlInitSql());
/**
* Check if table partitions exist and if not create some. The expectation is that
* extra-ddl.xml should have some partition initialisation but this helps people get going.
*/
private void checkInitialTablePartitions(Connection connection) {
DatabasePlatform databasePlatform = server.getDatabasePlatform();
try {
StringBuilder sb = new StringBuilder();
for (MTable table : currentModel.getPartitionedTables()) {
String tableName = table.getName();
if (!databasePlatform.tablePartitionsExist(connection, tableName)) {
log.info("No table partitions for table {}", tableName);
PartitionMeta meta = table.getPartitionMeta();
String initPart = databasePlatform.tablePartitionInit(tableName, meta.getMode(), meta.getProperty(), table.singlePrimaryKey());
sb.append(initPart).append("\n");
}
}
String initialPartitionSql = sb.toString();
if (!initialPartitionSql.isEmpty()) {
runScript(connection, false, initialPartitionSql, "initial table partitions");
}
} catch (SQLException e) {
log.error("Error checking initial table partitions", e);
}
}
protected void runSeedSql() throws IOException {
runResourceScript(server.getServerConfig().getDdlSeedSql());
protected void runInitSql(Connection connection) throws IOException {
runResourceScript(connection, server.getServerConfig().getDdlInitSql());
}
protected void runResourceScript(String sqlScript) throws IOException {
protected void runSeedSql(Connection connection) throws IOException {
runResourceScript(connection, server.getServerConfig().getDdlSeedSql());
}
protected void runResourceScript(Connection connection, String sqlScript) throws IOException {
if (sqlScript != null) {
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
if (is != null) {
String content = readContent(new InputStreamReader(is));
runScript(false, content, sqlScript);
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
if (is != null) {
String content = readContent(new InputStreamReader(is));
runScript(connection, false, content, sqlScript);
}
}
}
}
@@ -273,8 +348,15 @@ public class DdlGenerator {
buf.append(s).append("\n");
}
return buf.toString();
}
}
/**
* Create the ScriptTransform for placeholder key/value replacement.
*/
private ScriptTransform createScriptTransform(DbMigrationConfig config) {
return ScriptTransform.build(config.getRunPlaceholders(), config.getRunPlaceholderMap());
}
}
@@ -10,6 +10,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
@@ -57,7 +58,7 @@ import java.util.List;
*
* DbMigration migration = DbMigration.create();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* migration.setPlatform(Platform.POSTGRES);
*
* migration.generateMigration();
*
@@ -65,7 +66,7 @@ import java.util.List;
*/
public class DefaultDbMigration implements DbMigration {
protected static final Logger logger = LoggerFactory.getLogger(DefaultDbMigration.class);
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration");
private static final String initialVersion = "1.0";
@@ -84,6 +85,8 @@ public class DefaultDbMigration implements DbMigration {
protected DatabasePlatform databasePlatform;
private boolean vanillaPlatform;
protected List<Pair> platforms = new ArrayList<>();
protected ServerConfig serverConfig;
@@ -91,6 +94,14 @@ public class DefaultDbMigration implements DbMigration {
protected DbConstraintNaming constraintNaming;
protected Boolean strictMode;
protected Boolean includeGeneratedFileComment;
protected String header;
protected String applyPrefix;
protected String version;
protected String name;
protected String generatePendingDrop;
protected boolean includeBuiltInPartitioning = true;
/**
* Create for offline migration generation.
@@ -148,6 +159,41 @@ public class DefaultDbMigration implements DbMigration {
this.strictMode = strictMode;
}
@Override
public void setApplyPrefix(String applyPrefix) {
this.applyPrefix = applyPrefix;
}
@Override
public void setVersion(String version) {
this.version = version;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
@Override
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
@Override
public void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning) {
this.includeBuiltInPartitioning = includeBuiltInPartitioning;
}
@Override
public void setHeader(String header) {
this.header = header;
}
/**
* Set the specific platform to generate DDL for.
* <p>
@@ -156,6 +202,7 @@ public class DefaultDbMigration implements DbMigration {
*/
@Override
public void setPlatform(Platform platform) {
vanillaPlatform = true;
setPlatform(getPlatform(platform));
}
@@ -219,6 +266,7 @@ public class DefaultDbMigration implements DbMigration {
* migration.generateMigration();
*
* }</pre>
*
* @return the generated migration or null
*/
@Override
@@ -240,7 +288,13 @@ public class DefaultDbMigration implements DbMigration {
try {
Request request = createRequest();
if (platforms.isEmpty()) {
generateExtraDdl(request.migrationDir, databasePlatform);
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();
@@ -257,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.
*/
@@ -270,21 +342,28 @@ 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>
*/
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException {
if (dbPlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
if (tablePartitioning && includeBuiltInPartitioning) {
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning());
}
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
}
}
private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
}
}
@@ -295,7 +374,7 @@ public class DefaultDbMigration implements DbMigration {
*/
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
String fullName = repeatableMigrationName(script.getName());
String fullName = repeatableMigrationName(script.isInit(), script.getName());
logger.info("writing repeatable script {}", fullName);
@@ -306,8 +385,16 @@ public class DefaultDbMigration implements DbMigration {
}
}
private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
private String repeatableMigrationName(boolean init, String scriptName) {
StringBuilder sb = new StringBuilder();
if (init) {
sb.append("I__");
} else {
sb.append("R__");
}
sb.append(scriptName.replace(' ', '_'));
sb.append(migrationConfig.getApplySuffix());
return sb.toString();
}
/**
@@ -368,6 +455,10 @@ public class DefaultDbMigration implements DbMigration {
this.current = currentModel.read();
}
boolean isTablePartitioning() {
return current.isTablePartitioning();
}
/**
* Return the migration for the pending drops for a given version.
*/
@@ -429,7 +520,7 @@ public class DefaultDbMigration implements DbMigration {
if (nextDrop != null) {
return nextDrop;
}
return migrationConfig.getGeneratePendingDrop();
return generatePendingDrop;
}
/**
@@ -439,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));
@@ -474,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);
}
}
@@ -493,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;
@@ -506,17 +596,81 @@ public class DefaultDbMigration implements DbMigration {
if (server == null) {
setServer(Ebean.getDefaultServer());
}
if (databasePlatform == null && platforms.isEmpty()) {
// not explicitly set not set a list of platforms so
// default to the platform of the default server
if (vanillaPlatform || databasePlatform == null) {
// not explicitly set so use the platform of the server
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
logger.trace("set platform to {}", databasePlatform.getName());
}
if (strictMode != null && migrationConfig != null) {
migrationConfig.setStrictMode(strictMode);
if (migrationConfig != null) {
if (strictMode != null) {
migrationConfig.setStrictMode(strictMode);
}
if (applyPrefix != null) {
migrationConfig.setApplyPrefix(applyPrefix);
}
if (header != null) {
migrationConfig.setDdlHeader(header);
}
}
}
/**
* 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.
*/
@@ -578,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
@@ -18,7 +18,9 @@ public class DdlWrite {
private final DdlBuffer applyForeignKeys;
private final DdlBuffer applyHistory;
private final DdlBuffer applyHistoryView;
private final DdlBuffer applyHistoryTrigger;
private final DdlBuffer dropAllForeignKeys;
@@ -39,7 +41,8 @@ public class DdlWrite {
this.applyDropDependencies = new BaseDdlBuffer(configuration);
this.apply = new BaseDdlBuffer(configuration);
this.applyForeignKeys = new BaseDdlBuffer(configuration);
this.applyHistory = new BaseDdlBuffer(configuration);
this.applyHistoryView = new BaseDdlBuffer(configuration);
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
this.dropAll = new BaseDdlBuffer(configuration);
}
@@ -61,7 +64,8 @@ public class DdlWrite {
public boolean isApplyEmpty() {
return apply.getBuffer().isEmpty()
&& applyForeignKeys.getBuffer().isEmpty()
&& applyHistory.getBuffer().isEmpty()
&& applyHistoryView.getBuffer().isEmpty()
&& applyHistoryTrigger.getBuffer().isEmpty()
&& applyDropDependencies.getBuffer().isEmpty();
}
@@ -90,10 +94,17 @@ public class DdlWrite {
}
/**
* Return the buffer that apply history DDL is written to.
* Return the buffer that apply history-view DDL is written to.
*/
public DdlBuffer applyHistory() {
return applyHistory;
public DdlBuffer applyHistoryView() {
return applyHistoryView;
}
/**
* Return the buffer that apply history-trigger DDL is written to.
*/
public DdlBuffer applyHistoryTrigger() {
return applyHistoryTrigger;
}
/**
@@ -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));
}
}
@@ -42,6 +42,12 @@ import java.util.Map;
*/
public class BaseTableDdl implements TableDdl {
enum HistorySupport {
NONE,
SQL2011,
TRIGGER_BASED
}
protected final DbConstraintNaming naming;
protected final NamingConvention namingConvention;
@@ -78,22 +84,23 @@ public class BaseTableDdl implements TableDdl {
private boolean strictMode;
private final boolean sql2011History;
private final HistorySupport historySupport;
/**
* Helper class that is used to execute the migration ddl before and after the migration action.
*/
private class DdlMigrationHelp {
private List<String> before;
private List<String> after;
private String tableName;
private String columnName;
private String defaultValue;
private final List<String> before;
private final List<String> after;
private final String tableName;
private final String columnName;
private final String defaultValue;
private final boolean withHistory;
/**
* Constructor for DdlMigrationHelp when adding a NEW column.
*/
DdlMigrationHelp(String tableName, Column column) {
DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
this.tableName = tableName;
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
@@ -105,6 +112,7 @@ public class BaseTableDdl implements TableDdl {
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
this.withHistory = withHistory;
}
/**
@@ -119,6 +127,7 @@ public class BaseTableDdl implements TableDdl {
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
// here we add the platform's default update script
withHistory = isTrue(alter.isWithHistory());
if (alter.getBefore().isEmpty() && alterNotNull) {
if (defaultValue == null) {
handleStrictError(tableName, columnName);
@@ -134,6 +143,10 @@ public class BaseTableDdl implements TableDdl {
if (!before.isEmpty()) {
buffer.end();
}
if (!before.isEmpty() && withHistory) {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
for (String ddlScript : before) {
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
buffer.endOfStatement();
@@ -141,6 +154,9 @@ public class BaseTableDdl implements TableDdl {
}
void writeAfter(DdlBuffer buffer) throws IOException {
if (!after.isEmpty() && withHistory) {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
// here we run post migration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
@@ -198,7 +214,11 @@ public class BaseTableDdl implements TableDdl {
this.platformDdl.configure(serverConfig);
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
this.sql2011History = hist != null && hist.isStandardsBased();
if (hist == null) {
this.historySupport = HistorySupport.NONE;
} else {
this.historySupport = hist.isStandardsBased() ? HistorySupport.SQL2011 : HistorySupport.TRIGGER_BASED;
}
}
/**
@@ -237,15 +257,19 @@ public class BaseTableDdl implements TableDdl {
useSequence = (IdType.SEQUENCE == useDbIdentityType);
}
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);
writeCompoundUniqueConstraints(apply, createTable);
if (!pk.isEmpty()) {
// defined on the columns
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
if (partitionMode == null || !platformDdl.suppressPrimaryKeyOnPartition()) {
writePrimaryKeyConstraint(apply, createTable.getPkName(), toColumnNames(pk));
}
}
if (platformDdl.isInlineForeignKeys()) {
writeInlineForeignKeys(writer, createTable);
@@ -253,6 +277,9 @@ public class BaseTableDdl implements TableDdl {
apply.newLine().append(")");
addTableCommentInline(apply, createTable);
if (partitionMode != null) {
platformDdl.addTablePartition(apply, partitionMode, createTable.getPartitionColumn());
}
apply.endOfStatement();
addComments(apply, createTable);
@@ -700,8 +727,14 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generateEpilog(DdlWrite write) throws IOException {
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
if (!regenerateHistoryTriggers.isEmpty()) {
platformDdl.lockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
}
platformDdl.unlockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
}
platformDdl.generateEpilog(write);
}
@@ -722,15 +755,15 @@ public class BaseTableDdl implements TableDdl {
String tableName = addColumn.getTableName();
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), tableName, column, false);
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
}
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// make same changes to the history table
String historyTable = historyTable(tableName);
for (Column column : columns) {
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
alterTableAddColumn(writer.apply(), historyTable, column, true);
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
}
}
@@ -771,7 +804,8 @@ public class BaseTableDdl implements TableDdl {
String tableName = dropColumn.getTableName();
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// also drop from the history table
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
@@ -813,12 +847,15 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(alterColumn.getComment())) {
alterColumnComment(writer, alterColumn);
}
if (hasValue(alterColumn.getDropCheckConstraint())) {
dropCheckConstraint(writer, alterColumn, alterColumn.getDropCheckConstraint());
}
boolean alterCheckConstraint = hasValue(alterColumn.getCheckConstraint());
if (alterCheckConstraint) {
// drop constraint before altering type etc
dropCheckConstraint(writer, alterColumn);
dropCheckConstraint(writer, alterColumn, alterColumn.getCheckConstraintName());
}
boolean alterBaseAttributes = false;
if (hasValue(alterColumn.getType())) {
@@ -872,8 +909,9 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
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();
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
alterHistoryColumn.setColumnName(alter.getColumnName());
@@ -894,9 +932,9 @@ public class BaseTableDdl implements TableDdl {
}
}
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) throws IOException {
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), alter.getCheckConstraintName());
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
@@ -923,7 +961,8 @@ public class BaseTableDdl implements TableDdl {
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && !sql2011History) {
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
// apply same type change to matching column in the history table
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
writer.apply().append(ddl).endOfStatement();
@@ -979,8 +1018,8 @@ public class BaseTableDdl implements TableDdl {
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
}
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) throws IOException {
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
if (!onHistoryTable) {
help.writeBefore(buffer);
}
@@ -69,7 +69,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
*/
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
}
@@ -154,7 +154,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String baseTableName = table.getName();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryView();
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
createHistoryTable(apply, table);
@@ -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) {
@@ -210,19 +210,19 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
.endOfStatement().end();
}
/**
* Create or replace the with_history view with explicit columns.
*/
protected void createWithHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
apply.append("create or replace view ").append(update.getBaseTable()).append(viewSuffix).append(" as select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getBaseTable()).append(" union all select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getHistoryTable()).endOfStatement().end();
/**
* For postgres/h2/mysql we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
}
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
@@ -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 {
@@ -28,10 +28,18 @@ public class DbTriggerUpdate {
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyBuffer() {
return writer.applyHistory();
public DdlBuffer historyViewBuffer() {
return writer.applyHistoryView();
}
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyTriggerBuffer() {
return writer.applyHistoryTrigger();
}
/**
* Return the appropriate drop dependency buffer for the current mode.
*/
@@ -15,6 +15,8 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
public H2HistoryDdl() {
this.sysPeriodType = "timestamp";
this.currentTimestamp = "now()";
}
@Override
@@ -27,7 +29,7 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
String baseTableName = table.getName();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryTrigger();
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName);
}
@@ -35,7 +37,9 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.historyBuffer();
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
dropTriggers(buffer, update.getBaseTable());
addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable());
}
@@ -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);
}
}
}
@@ -17,7 +17,8 @@ public class HistoryTableUpdate {
ADD,
DROP,
INCLUDE,
EXCLUDE
EXCLUDE,
ALTER
}
private static class Column {
@@ -7,6 +7,7 @@ import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import java.io.IOException;
import java.util.Collection;
/**
* MySql specific DDL.
@@ -37,9 +38,20 @@ public class MySqlDdl extends PlatformDdl {
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
}
/**
* It is rather complex to delete a column on MySql as there must not exist any foreign keys.
* That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
}
@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
// drop constraint not supported
// drop constraint not supported in MySQL 5.7 and 8.0 but starting with MariaDB 10.2.1 CHECK is evaluated
// TODO: Implement for MariaDB >= 10.2.1
return null;
}
@@ -59,7 +71,7 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
// use alter
return "alter table " + tableName + " alter " + columnName + " " + suffix;
@@ -113,10 +125,39 @@ public class MySqlDdl extends PlatformDdl {
}
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
// alter comment currently not supported as it requires to repeat whole column definition
}
/**
* Locks all tables for triggers that have to be updated.
*/
@Override
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
if (!tables.isEmpty()) {
buffer.append("lock tables ");
int i = 0;
for (String table : tables) {
if (i > 0) {
buffer.append(", ");
}
buffer.append(table).append(" write");
i++;
}
buffer.endOfStatement();
}
}
/**
* Unlocks all tables for triggers that have to be updated.
*/
@Override
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
buffer.append("unlock tables").endOfStatement();
}
}
@@ -34,19 +34,19 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.historyBuffer();
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
String baseTable = update.getBaseTable();
buffer.append("lock tables ").append(baseTable).append(" write").endOfStatement();
dropTriggers(buffer, baseTable);
addBeforeUpdate(updateTriggerName(baseTable), update);
addBeforeDelete(deleteTriggerName(baseTable), update);
buffer.append("unlock tables").endOfStatement();
}
private void addBeforeUpdate(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
DdlBuffer apply = update.historyTriggerBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before update on ").append(update.getBaseTable())
@@ -59,7 +59,7 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
DdlBuffer apply = update.historyTriggerBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before delete on ").append(update.getBaseTable())
@@ -20,10 +20,12 @@ import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.IdentityType;
import io.ebeaninternal.dbmigration.model.MTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
@@ -76,14 +78,16 @@ public class PlatformDdl {
protected String dropIndexIfExists = "drop index if exists ";
protected String alterColumn = "alter column";
protected String dropConstraint = "drop constraint";
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.
@@ -246,6 +256,9 @@ public class PlatformDdl {
* Convert the standard type to the platform specific type.
*/
public String convert(String type, boolean identity) {
if (type == null) {
return null;
}
if (type.contains("[]")) {
return convertArrayType(type);
}
@@ -418,7 +431,7 @@ public class PlatformDdl {
* Drop a unique constraint from the table.
*/
public String alterTableDropConstraint(String tableName, String constraintName) {
return "alter table " + tableName + " " + dropConstraint + " " + maxConstraintName(constraintName);
return "alter table " + tableName + " " + dropConstraintIfExists + " " + maxConstraintName(constraintName);
}
/**
@@ -442,32 +455,38 @@ public class PlatformDdl {
.append(" ").append(addColumn).append(" ").append(column.getName())
.append(" ").append(convertedType);
if (!onHistoryTable) {
if (defaultValue != null) {
// Add default value also to history table if it is not excluded
if (defaultValue != null) {
if (!onHistoryTable || !isTrue(column.isHistoryExclude())) {
buffer.append(" default ");
buffer.append(defaultValue);
}
}
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
buffer.append(addColumnSuffix);
buffer.endOfStatement();
// check constraints cannot be added in one statement for h2
if (!StringHelper.isNull(column.getCheckConstraint())) {
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(),
column.getCheckConstraint());
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();
}
/**
@@ -487,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;
}
/**
@@ -515,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;
}
@@ -641,4 +660,37 @@ public class PlatformDdl {
}
return name;
}
/**
* Mysql-specific: Locks all tables for triggers that have to be updated.
*/
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
/**
* Mysql-specific: Unlocks all tables for triggers that have to be updated.
*/
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;
}
public void addTablePartition(DdlBuffer apply, String partitionMode, String partitionColumn) throws IOException {
// only supported by postgres initially
}
}
@@ -1,6 +1,9 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import java.io.IOException;
/**
* Postgres specific DDL.
@@ -16,6 +19,11 @@ public class PostgresDdl extends PlatformDdl {
this.columnSetNull = "drop not null";
}
@Override
public boolean suppressPrimaryKeyOnPartition() {
return true;
}
@Override
protected String convertArrayType(String logicalArrayType) {
return NativeDbArray.logicalToNative(logicalArrayType);
@@ -38,4 +46,9 @@ public class PostgresDdl extends PlatformDdl {
}
return columnDefn;
}
@Override
public void addTablePartition(DdlBuffer apply, String partitionMode, String partitionColumn) throws IOException {
apply.append(" partition by range (").append(partitionColumn).append(")");
}
}
@@ -62,7 +62,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String procedureName = procedureName(baseTableName);
String triggerName = triggerName(baseTableName);
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryTrigger();
apply
.append("create trigger ").append(triggerName).newLine()
.append(" before update or delete on ").append(baseTableName).newLine()
@@ -79,13 +79,21 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createOrReplaceFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
apply
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
.append("begin").newLine();
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine();
apply.append("declare").newLine()
.append(" lowerTs timestamptz;").newLine()
.append(" upperTs timestamptz;").newLine();
apply.append("begin").newLine()
.append(" lowerTs = lower(OLD.sys_period);").newLine()
.append(" upperTs = greatest(lowerTs + '1 microsecond',current_timestamp);").newLine();
apply
.append(" if (TG_OP = 'UPDATE') then").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
.append(" NEW.").append(sysPeriod).append(" = tstzrange(upperTs,null);").newLine()
.append(" return new;").newLine();
apply
.append(" elsif (TG_OP = 'DELETE') then").newLine();
@@ -107,7 +115,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String historyTable = historyTableName(table.getName());
List<String> columnNames = columnNamesForApply(table);
createOrReplaceFunction(writer.applyHistory(), procedureName, historyTable, columnNames);
createOrReplaceFunction(writer.applyHistoryTrigger(), procedureName, historyTable, columnNames);
}
@Override
@@ -116,21 +124,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String procedureName = procedureName(update.getBaseTable());
recreateHistoryView(update);
createOrReplaceFunction(update.historyBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
/**
* For postgres we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
private void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view with specific columns specified (the columns generally are not dropped until later)
createWithHistoryView(update);
createOrReplaceFunction(update.historyTriggerBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
@Override
@@ -138,7 +132,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
appendColumnNames(buffer, columns, "");
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), ").append(currentTimestamp).append("), ");
buffer.append(") values (tstzrange(lowerTs,upperTs), ");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import java.io.IOException;
@@ -137,13 +139,7 @@ public class SqlServerDdl extends PlatformDdl {
// a rather complex statement.
StringBuilder sb = new StringBuilder();
if (DdlHelp.isDropDefault(defaultValue)) {
sb.append("delimiter $$\n");
sb.append("DECLARE @Tmp nvarchar(200);");
sb.append("select @Tmp = t1.name from sys.default_constraints t1\n");
sb.append(" join sys.columns t2 on t1.object_id = t2.default_object_id\n");
sb.append(" where t1.parent_object_id = OBJECT_ID('").append(tableName)
.append("') and t2.name = '").append(columnName).append("';\n");
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
sb.append("EXEC usp_ebean_drop_default_constraint ").append(tableName).append(", ").append(columnName);
} else {
sb.append("alter table ").append(tableName);
sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
@@ -200,25 +196,50 @@ public class SqlServerDdl extends PlatformDdl {
/**
* It is rather complex to delete a column on SqlServer as there must not exist any references
* (constraints, default values, indices and foreign keys). The list is not yet complete, as
* indices over multiple columns will not yet deleted.
* (This may be changed to delete all refering objects by using the sys.* tables later)
* (constraints, default values, indices and 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("-- drop column ").append(tableName).append(".").append(columnName).endOfStatement();
buffer.append(alterTableDropUniqueConstraint(tableName, naming.uniqueConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(alterColumnDefaultValue(tableName, columnName, DdlHelp.DROP_DEFAULT));
buffer.endOfStatement();
buffer.append(alterTableDropConstraint(tableName, naming.checkConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(dropIndex(naming.indexName(tableName, columnName), tableName));
buffer.endOfStatement();
buffer.append(alterTableDropForeignKey(tableName, naming.foreignKeyConstraintName(tableName, columnName)));
buffer.endOfStatement();
super.alterTableDropColumn(buffer, tableName, columnName);
buffer.append("EXEC usp_ebean_drop_column ").append(tableName).append(", ").append(columnName).endOfStatement();
}
/**
* This writes the multi value datatypes needed for {@link MultiValueBind}
*/
@Override
public void generateProlog(DdlWrite write) throws IOException {
super.generateProlog(write);
generateTVPDefinitions(write, "bigint");
generateTVPDefinitions(write, "float");
generateTVPDefinitions(write, "bit");
generateTVPDefinitions(write, "date");
generateTVPDefinitions(write, "time");
//generateTVPDefinitions(write, "datetime2");
generateTVPDefinitions(write, "uniqueidentifier");
generateTVPDefinitions(write, "nvarchar(max)");
}
private void generateTVPDefinitions(DdlWrite write, String definition) throws IOException {
int pos = definition.indexOf('(');
String name = pos == -1 ? definition : definition.substring(0, pos);
dropTVP(write.dropAll(), name);
//TVPs are included in "I__create_procs.sql"
//createTVP(write.apply(), name, definition);
}
private void dropTVP(DdlBuffer ddl, String name) throws IOException {
ddl.append("if exists (select name from sys.types where name = 'ebean_").append(name)
.append("_tvp') drop type ebean_").append(name).append("_tvp").endOfStatement();
}
private void createTVP(DdlBuffer ddl, String name, String definition) throws IOException {
ddl.append("if not exists (select name from sys.types where name = 'ebean_").append(name)
.append("_tvp') create type ebean_").append(name).append("_tvp as table (c1 ").append(definition).append(")")
.endOfStatement();
}
}
@@ -31,19 +31,24 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
enableSystemVersioning(writer, baseTable);
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistory();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
private String getHistoryTable(String baseTable) {
String historyTable = baseTable + "_history"; // history must contain schema, otherwise you'll get
// Setting SYSTEM_VERSIONING to ON failed because history table 'xxx_history' is not specified in two-part name format.
if (historyTable.indexOf('.') == -1) {
historyTable = "dbo." +historyTable; // so add the default schema, if none was specified.
}
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=").append(historyTable).append("))").endOfStatement();
return historyTable;
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistoryView();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=")
.append(getHistoryTable(baseTable)).append("))").endOfStatement();
DdlBuffer drop = writer.dropAll();
drop.append("IF OBJECT_ID('").append(baseTable).append("', 'U') IS NOT NULL alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
@@ -53,7 +58,7 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryView();
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
// drop default constraints
@@ -76,7 +81,15 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
}
@Override
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException {
public void updateTriggers(DdlWrite writer, HistoryTableUpdate baseTable) throws IOException {
// SQL Server 2016 does not need triggers
DdlBuffer apply = writer.applyHistoryView();
String baseTableName = baseTable.getBaseTable();
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = off (history_table=")
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
apply.append("-- history migration goes here").newLine();
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = on (history_table=")
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
}
}

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